From 928421199baadf02ce595d7ba8f446c859aec5ac Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 07:10:46 +0000 Subject: [PATCH 1/4] Revert "Merge pull request #12 from divo12/cursor/company-brain-mode-9b0f" This reverts commit ed897acda1fb738215a8bd1a8610f190bf1624fd. Note: The actual changes were already reverted by 3eb932e which reverted PR #13 (which was built on top of #12). This commit maintains the explicit revert history as requested. Co-authored-by: divo12 From dbf14e4b74e153d2f57dcac969addc5ac7f3acaa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 06:29:13 +0000 Subject: [PATCH 2/4] feat: add company mode for organizational knowledge base Add first-class company run mode that uses Stratiki home (~/.stratiki) for organization-wide knowledge. Company mode uses the existing book infrastructure and connectors but with a dedicated home directory. - Add 'company' to OpenWikiRunMode type alongside 'personal' and 'code' - Add stratikiHomeDir and stratikiCompanyWikiDir paths in config - Update CLI command parsing to accept company mode via positional or --mode - Update book commands to support --mode company flag - Update getRunModeCwd to return company wiki dir for company mode - Add ensureStratikiHome() function for company mode directory setup - Update README with three-mode table showing company mode - Add tests for company mode parsing and book commands - Update help text to document company mode usage Co-authored-by: divo12 --- README.md | 11 +- package.json | 1 + pnpm-lock.yaml | 281 ++++++++++++++++++++++++++++++ src/book/lease.ts | 5 +- src/book/packet.ts | 4 +- src/cli/cli.tsx | 2 +- src/cli/commands.ts | 67 +++++-- src/cli/run-mode.ts | 12 +- src/cli/runners.ts | 28 ++- src/config/openwiki-home.ts | 41 +++++ test/book/lease.test.ts | 5 +- test/book/manifest.test.ts | 38 ++++ test/book/packet.test.ts | 29 ++- test/book/refresh-planner.test.ts | 18 +- test/cli/commands.test.ts | 4 +- 15 files changed, 499 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index a33936ace..a872e29e6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ OpenWiki is a CLI that writes and maintains a wiki for your codebase or your per **OpenWiki gives you:** - **Agent-written docs** that stay accurate, generated by a [Deep Agents](https://github.com/langchain-ai/deepagentsjs) documentation agent. -- **Two modes:** a `code` wiki for a repository, or a `personal` wiki for your own knowledge. +- **Three modes:** a `code` wiki for a repository, a `company` brain for organizational knowledge, or a `personal` wiki for your own knowledge. - **Thirteen model providers** out of the box, from OpenAI and Anthropic to Bedrock, Gemini, and any OpenAI-compatible gateway. - **Coding-agent integrations** for Codex and Claude Code, using the host's model and repository tools. - **Grounded Claims** that track material facts back to versioned source evidence and surface them when the evidence changes. @@ -102,14 +102,15 @@ Before an update, OpenWiki checks those evidence versions. If source lines chang Grounded Claims currently apply to repository code wikis and repository evidence. Connector-derived facts, including LangSmith-only observations, are not claimed. -## Two modes +## Three modes -OpenWiki runs in one of two modes. Bare `openwiki`, `openwiki --init`, and `openwiki --update` default to **code** mode; add the `personal` positional (or `--mode personal`) for the personal brain. +Stratiki runs in one of three modes. Bare `stratiki`, `stratiki --init`, and `stratiki --update` default to **code** mode; add the `company` or `personal` positional (or `--mode company` / `--mode personal`) for the company or personal brain. | Mode | Documents | Writes to | Get started | | -------------------- | ---------------------- | ----------------------- | -------------------------- | -| **Code** _(default)_ | The current repository | `openwiki/` in the repo | `openwiki --init` | -| **Personal** | Your connected sources | `~/.openwiki/wiki` | `openwiki personal --init` | +| **Code** _(default)_ | The current repository | `openwiki/` in the repo | `stratiki --init` | +| **Company** | Company knowledge | `~/.stratiki/wiki` | `stratiki company --init` | +| **Personal** | Your connected sources | `~/.openwiki/wiki` | `stratiki personal --init` | By default the CLI stays open after a run so you can send follow-up messages. Add `-p` / `--print` for a one-shot, non-interactive run that prints the final output and exits. `--init` and `--update` auto-exit on success in an interactive terminal, so the same command works one-shot or interactively. diff --git a/package.json b/package.json index 492693efa..ac7962f23 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ } }, "devDependencies": { + "@changesets/cli": "^3.0.1", "@eslint/js": "^10.0.1", "@types/better-sqlite3": "^7.6.12", "@types/jsdom": "^28.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44a7b741d..81fa593dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,9 @@ importers: specifier: ^4.4.3 version: 4.4.3 devDependencies: + '@changesets/cli': + specifier: ^3.0.1 + version: 3.0.1 '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.6.0) @@ -292,9 +295,78 @@ packages: '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@changesets/apply-release-plan@8.0.0': + resolution: {integrity: sha512-kUd2pbf1w5/AYmBMb0Tt+rkIPCjFJdT0SZMrkOjJT/WV/QbtmvkyB5jkV0oaNPheavprZk+SfUiozUti7TIL2w==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/assemble-release-plan@7.0.0': + resolution: {integrity: sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/changelog-git@1.0.0': + resolution: {integrity: sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/cli@3.0.1': + resolution: {integrity: sha512-3IVpRSgiKDj2aRbBHKtWTXSRH2/iR3bWxau9iQUoEsZjDhRIj9nhl90k7FWMxZAJvLgJBbgMq8+qS0xQsenYsQ==} + engines: {node: ^22.11 || ^24 || >=26, npm: '>=10.9.0', pnpm: '>=10.0.0', yarn: '>=4.5.2'} + hasBin: true + + '@changesets/config@4.0.0': + resolution: {integrity: sha512-mw95/YrkOuhZZxfnVAA4bSXOFUi+KlhzOBTM8C4x777NhUU6HWIl9Z+K+nME+E4PVsv5NQVQwTfiHihAS1A/ow==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/errors@1.0.0': + resolution: {integrity: sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/format@0.1.2': + resolution: {integrity: sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/get-dependents-graph@3.0.0': + resolution: {integrity: sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/git@4.0.0': + resolution: {integrity: sha512-uIEswpPUgzBBqrC0qg13byNaPorzhf05LE2T+gRizEKCXvMsJC6NPJp5iNDSV/gYj4Viqh09UiOF5E7XWeH5lQ==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/parse@1.0.0': + resolution: {integrity: sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/pre@3.0.0': + resolution: {integrity: sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/read@1.0.0': + resolution: {integrity: sha512-8TdE2PwG6yArPt5Ozej83Z6iHz1G8BDBKvIEKZ455MccR4K3GNbLR2XqjBxa+5yLFnEd3xAOPXYboBg1pt8stQ==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/should-skip-package@1.0.0': + resolution: {integrity: sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/types@7.0.0': + resolution: {integrity: sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==} + engines: {node: ^22.11 || ^24 || >=26} + + '@changesets/write@1.0.1': + resolution: {integrity: sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA==} + engines: {node: ^22.11 || ^24 || >=26} + '@chevrotain/types@11.1.2': resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@clack/core@1.4.3': + resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.7.0': + resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} + engines: {node: '>= 20.12.0'} + '@csstools/color-helpers@6.1.0': resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} engines: {node: '>=20.19.0'} @@ -673,6 +745,18 @@ packages: peerDependencies: '@langchain/core': ^1.0.0 + '@manypkg/find-root@3.1.0': + resolution: {integrity: sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==} + engines: {node: '>=20.0.0'} + + '@manypkg/get-packages@3.1.0': + resolution: {integrity: sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==} + engines: {node: '>=20.0.0'} + + '@manypkg/tools@2.1.2': + resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} + engines: {node: '>=20.0.0'} + '@mermaid-js/parser@1.2.0': resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} @@ -708,6 +792,10 @@ packages: '@oxc-project/types@0.139.0': resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@pnpm/deps.graph-sequencer@1100.0.1': + resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} + engines: {node: '>=22.13'} + '@posthog/core@1.40.0': resolution: {integrity: sha512-oGDbIwlTquNwdHbEL5ZLEkuW4UFkkEanfx3QAxDgyVbISv+OAA6YGQwrvo0JD3MUJEbJZvyh8XsX+WYDGw9XHw==} @@ -1180,6 +1268,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1662,9 +1754,18 @@ packages: fast-sha256@1.3.0: resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.5: resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1807,6 +1908,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-id@4.2.1: + resolution: {integrity: sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==} + hasBin: true + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -1930,6 +2035,9 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} @@ -1973,6 +2081,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} @@ -2015,6 +2126,9 @@ packages: ws: optional: true + launch-editor@2.14.1: + resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} + layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -2498,6 +2612,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -2526,6 +2644,9 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -3140,8 +3261,124 @@ snapshots: '@cfworker/json-schema@4.1.1': {} + '@changesets/apply-release-plan@8.0.0': + dependencies: + '@changesets/config': 4.0.0 + '@changesets/format': 0.1.2 + '@changesets/git': 4.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + import-meta-resolve: 4.2.0 + jsonc-parser: 3.3.1 + semver: 7.8.5 + + '@changesets/assemble-release-plan@7.0.0': + dependencies: + '@changesets/errors': 1.0.0 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + semver: 7.8.5 + + '@changesets/changelog-git@1.0.0': + dependencies: + '@changesets/types': 7.0.0 + + '@changesets/cli@3.0.1': + dependencies: + '@changesets/apply-release-plan': 8.0.0 + '@changesets/assemble-release-plan': 7.0.0 + '@changesets/changelog-git': 1.0.0 + '@changesets/config': 4.0.0 + '@changesets/errors': 1.0.0 + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/git': 4.0.0 + '@changesets/pre': 3.0.0 + '@changesets/read': 1.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + '@changesets/write': 1.0.1 + '@clack/prompts': 1.7.0 + '@manypkg/get-packages': 3.1.0 + '@pnpm/deps.graph-sequencer': 1100.0.1 + cac: 7.0.0 + import-meta-resolve: 4.2.0 + launch-editor: 2.14.1 + package-manager-detector: 1.8.0 + semver: 7.8.5 + tinyexec: 1.3.0 + + '@changesets/config@4.0.0': + dependencies: + '@changesets/get-dependents-graph': 3.0.0 + '@changesets/should-skip-package': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.5 + + '@changesets/errors@1.0.0': {} + + '@changesets/format@0.1.2': + dependencies: + package-manager-detector: 1.8.0 + tinyexec: 1.3.0 + + '@changesets/get-dependents-graph@3.0.0': + dependencies: + '@changesets/types': 7.0.0 + semver: 7.8.5 + + '@changesets/git@4.0.0': + dependencies: + '@changesets/errors': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.5 + tinyexec: 1.3.0 + + '@changesets/parse@1.0.0': + dependencies: + '@changesets/types': 7.0.0 + yaml: 2.9.0 + + '@changesets/pre@3.0.0': + dependencies: + '@changesets/errors': 1.0.0 + '@changesets/types': 7.0.0 + '@manypkg/get-packages': 3.1.0 + + '@changesets/read@1.0.0': + dependencies: + '@changesets/git': 4.0.0 + '@changesets/parse': 1.0.0 + '@changesets/types': 7.0.0 + + '@changesets/should-skip-package@1.0.0': + dependencies: + '@changesets/types': 7.0.0 + + '@changesets/types@7.0.0': {} + + '@changesets/write@1.0.1': + dependencies: + '@changesets/format': 0.1.2 + '@changesets/types': 7.0.0 + human-id: 4.2.1 + '@chevrotain/types@11.1.2': {} + '@clack/core@1.4.3': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.7.0': + dependencies: + '@clack/core': 1.4.3 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + '@csstools/color-helpers@6.1.0': {} '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -3450,6 +3687,21 @@ snapshots: '@langchain/core': 1.2.4(openai@6.45.0(@aws-sdk/credential-provider-node@3.972.63)(@smithy/signature-v4@5.6.2)(ws@8.21.0)(zod@4.4.3))(ws@8.21.0) zod: 4.4.3 + '@manypkg/find-root@3.1.0': + dependencies: + '@manypkg/tools': 2.1.2 + + '@manypkg/get-packages@3.1.0': + dependencies: + '@manypkg/find-root': 3.1.0 + '@manypkg/tools': 2.1.2 + + '@manypkg/tools@2.1.2': + dependencies: + jju: 1.4.0 + tinyglobby: 0.2.17 + yaml: 2.9.0 + '@mermaid-js/parser@1.2.0': dependencies: '@chevrotain/types': 11.1.2 @@ -3499,6 +3751,8 @@ snapshots: '@oxc-project/types@0.139.0': {} + '@pnpm/deps.graph-sequencer@1100.0.1': {} + '@posthog/core@1.40.0': dependencies: '@posthog/types': 1.393.0 @@ -4015,6 +4269,8 @@ snapshots: bytes@3.1.2: {} + cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4548,8 +4804,18 @@ snapshots: fast-sha256@1.3.0: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.5: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -4707,6 +4973,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-id@4.2.1: {} + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -4813,6 +5081,8 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + jju@1.4.0: {} + jose@6.2.3: {} js-tiktoken@1.0.21: @@ -4868,6 +5138,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonc-parser@3.3.1: {} + jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 @@ -4914,6 +5186,11 @@ snapshots: openai: 6.45.0(@aws-sdk/credential-provider-node@3.972.63)(@smithy/signature-v4@5.6.2)(ws@8.21.0)(zod@4.4.3) ws: 8.21.0 + launch-editor@2.14.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.10.0 + layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -5366,6 +5643,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.10.0: {} + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -5406,6 +5685,8 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + sisteransi@1.0.5: {} + slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.3 diff --git a/src/book/lease.ts b/src/book/lease.ts index 8d188ff83..e0269d566 100644 --- a/src/book/lease.ts +++ b/src/book/lease.ts @@ -88,7 +88,10 @@ function isLeaseContents(value: unknown): value is LeaseContents { ); } -async function mkdirAndWrite(filePath: string, contents: LeaseContents): Promise { +async function mkdirAndWrite( + filePath: string, + contents: LeaseContents, +): Promise { await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, `${JSON.stringify(contents, null, 2)}\n`, "utf8"); } diff --git a/src/book/packet.ts b/src/book/packet.ts index ad306bd51..35376920b 100644 --- a/src/book/packet.ts +++ b/src/book/packet.ts @@ -134,7 +134,9 @@ function toRelativeDisplayPath(wikiDir: string, filePath: string): string { } function extractTitle(raw: string, filePath: string): string { - const frontMatterTitle = raw.match(/^---[\s\S]*?^title:\s*(.+)$/mu)?.[1]?.trim(); + const frontMatterTitle = raw + .match(/^---[\s\S]*?^title:\s*(.+)$/mu)?.[1] + ?.trim(); if (frontMatterTitle !== undefined && frontMatterTitle.length > 0) { return stripQuotes(frontMatterTitle); } diff --git a/src/cli/cli.tsx b/src/cli/cli.tsx index 3e0791331..4d781573e 100644 --- a/src/cli/cli.tsx +++ b/src/cli/cli.tsx @@ -90,7 +90,7 @@ async function runStandardCommand( } else if (command.kind === "cron") { await runCronCommand(command); } else if (command.kind === "book") { - await runBookCommand(command); + await runBookCommand(command, command.mode); } else if (command.kind === "ingest") { await runIngestCommand(command); } else if (command.kind === "visualize") { diff --git a/src/cli/commands.ts b/src/cli/commands.ts index af0baed18..5837322d6 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -23,7 +23,7 @@ export type HelpRow = { description: string; }; -export type OpenWikiRunMode = "personal" | "code"; +export type OpenWikiRunMode = "personal" | "code" | "company"; type CronTarget = Extract; export type HelpContent = { @@ -147,6 +147,7 @@ export type CliCommand = action: "context" | "init" | "refresh" | "status"; exitCode: 0; force: boolean; + mode: OpenWikiRunMode; name: string | null; query: string | null; } @@ -719,6 +720,7 @@ function parseMcpCommand(argv: string[]): CliCommand { /** * Parses `stratiki book` subcommands: init, status, refresh, context. + * Accepts --mode to specify company mode (defaults to code mode for repo book). */ function parseBookCommand(argv: string[]): CliCommand { const action = argv[0]; @@ -741,7 +743,9 @@ function parseBookCommand(argv: string[]): CliCommand { let force = false; let name: string | null = null; let query: string | null = null; + let mode: OpenWikiRunMode = "code"; let sawName = false; + let sawMode = false; for (let index = 1; index < argv.length; index += 1) { const arg = argv[index]; @@ -781,6 +785,36 @@ function parseBookCommand(argv: string[]): CliCommand { continue; } + if (arg === "--mode" || arg.startsWith("--mode=")) { + if (sawMode) { + return { + kind: "error", + exitCode: 1, + message: "--mode may only be specified once.", + }; + } + const value = + arg === "--mode" ? argv[index + 1] : arg.slice("--mode=".length); + if (!value || value.startsWith("-")) { + return { + kind: "error", + exitCode: 1, + message: "--mode requires a mode (personal, code, or company).", + }; + } + if (!isOpenWikiRunMode(value)) { + return { + kind: "error", + exitCode: 1, + message: `Invalid mode: ${value}. Expected personal, code, or company.`, + }; + } + mode = value; + sawMode = true; + if (arg === "--mode") index += 1; + continue; + } + if (action === "context" && !arg.startsWith("-")) { query = query === null ? arg : `${query} ${arg}`; continue; @@ -795,7 +829,7 @@ function parseBookCommand(argv: string[]): CliCommand { }; } - return { action, exitCode: 0, force, kind: "book", name, query }; + return { action, exitCode: 0, force, kind: "book", mode, name, query }; } /** @@ -912,7 +946,7 @@ function parseRunCommand( return { kind: "error", exitCode: 1, - message: "--mode requires personal or code.", + message: "--mode requires personal, code, or company.", }; } @@ -920,7 +954,7 @@ function parseRunCommand( return { kind: "error", exitCode: 1, - message: `Invalid mode: ${nextArg}. Expected personal or code.`, + message: `Invalid mode: ${nextArg}. Expected personal, code, or company.`, }; } @@ -942,7 +976,7 @@ function parseRunCommand( return { kind: "error", exitCode: 1, - message: `Invalid mode: ${rawMode}. Expected personal or code.`, + message: `Invalid mode: ${rawMode}. Expected personal, code, or company.`, }; } @@ -1112,7 +1146,7 @@ function resolveExplicitMode( function isOpenWikiRunMode( value: string | undefined, ): value is OpenWikiRunMode { - return value === "personal" || value === "code"; + return value === "personal" || value === "code" || value === "company"; } /** @@ -1178,7 +1212,8 @@ export const helpContent: HelpContent = { "stratiki [--init|--update] [message]", "stratiki code [--init|--update] [message]", "stratiki personal [--init|--update] [message]", - "stratiki --mode [--init|--update] [message]", + "stratiki company [--init|--update] [message]", + "stratiki --mode [--init|--update] [message]", "stratiki [--language ] [--init|--update] [message]", "stratiki [--modelId ]", "stratiki [--modelId ] [message]", @@ -1187,7 +1222,7 @@ export const helpContent: HelpContent = { "stratiki auth configure [--force]", "stratiki auth tools ", "stratiki ingest [--scheduled] [--print] [--modelId ]", - "stratiki book init [--name ] [--force]", + "stratiki book init [--name ] [--force] [--mode ]", "stratiki cron list", "stratiki cron pause all", "stratiki cron resume all", @@ -1204,6 +1239,10 @@ export const helpContent: HelpContent = { description: "Run OpenWiki for the current repository, writing docs under repo openwiki/ and using GitHub Actions for recurrence.", }, + { + label: "stratiki company", + description: `Run Stratiki as your company brain over configured sources, writing to the Stratiki home.`, + }, { label: "stratiki personal", description: `Run OpenWiki as your local personal brain over configured sources, writing to ${openWikiLocalWikiDisplayPath}.`, @@ -1233,9 +1272,10 @@ export const helpContent: HelpContent = { "Run ingestion and wiki update runs for one connector, one source instance, or all configured sources.", }, { - label: "stratiki book init [--name ] [--force]", + label: + "stratiki book init [--name ] [--force] [--mode ]", description: - "Seed the workspace System Book manifest (openwiki/book.config.json) with the U1-U7 coverage ontology.", + "Seed the workspace System Book manifest with the U1-U7 coverage ontology. Defaults to code mode (repo openwiki/); use --mode company for Stratiki home.", }, { label: "stratiki cron list", @@ -1294,9 +1334,9 @@ export const helpContent: HelpContent = { "Update existing Stratiki documentation. Defaults to code mode; use personal to update the local personal brain.", }, { - label: "--mode ", + label: "--mode ", description: - "Choose the personal brain (local, over configured sources) or the code brain (repository docs).", + "Choose the personal brain (local, over configured sources), the code brain (repository docs), or the company brain (company knowledge).", }, { label: "-l, --language ", @@ -1350,10 +1390,11 @@ export const helpContent: HelpContent = { examples: [ "stratiki", "stratiki --init", + "stratiki company --init", "stratiki personal --init", "stratiki code --init", "stratiki --update", - "stratiki --update --mode personal", + "stratiki --update --mode company", 'openwiki "What can you do?"', 'openwiki -p "Summarize what OpenWiki can do"', "stratiki --modelId gpt-5.5", diff --git a/src/cli/run-mode.ts b/src/cli/run-mode.ts index 4b0330be5..60214a3ca 100644 --- a/src/cli/run-mode.ts +++ b/src/cli/run-mode.ts @@ -1,5 +1,8 @@ import type { OpenWikiOutputMode } from "../agent/types.js"; -import { openWikiLocalWikiDir } from "../config/openwiki-home.js"; +import { + openWikiLocalWikiDir, + stratikiCompanyWikiDir, +} from "../config/openwiki-home.js"; import type { CliCommand, OpenWikiRunMode } from "./commands.js"; /** @@ -30,7 +33,8 @@ export function shouldPrintStartupError( /** * Resolves the working directory for a run: the code runtime's cwd in `code` - * mode, otherwise the local wiki directory. + * mode, otherwise the local wiki directory for `personal` or company wiki for + * `company`. * * @param codeRuntimeCwd Working directory used in `code` mode. * @@ -41,7 +45,9 @@ export function getRunModeCwd( mode: OpenWikiRunMode, codeRuntimeCwd = process.cwd(), ): string { - return mode === "code" ? codeRuntimeCwd : openWikiLocalWikiDir; + if (mode === "code") return codeRuntimeCwd; + if (mode === "company") return stratikiCompanyWikiDir; + return openWikiLocalWikiDir; } /** diff --git a/src/cli/runners.ts b/src/cli/runners.ts index 6316ede7b..8253742c7 100644 --- a/src/cli/runners.ts +++ b/src/cli/runners.ts @@ -21,6 +21,8 @@ import { CONNECTOR_IDS, isConnectorId } from "../connectors/registry.js"; import { openWikiBookDbPath, openWikiHomeDir, + stratikiHomeDir, + stratikiBookDbPath, } from "../config/openwiki-home.js"; import { resolveConfiguredProvider } from "../config/constants.js"; import { @@ -46,7 +48,7 @@ import { } from "../telemetry/index.js"; import { exportStaticVisualizer } from "../visualize/static-export.js"; import { runVisualizeServer } from "../visualize/server.js"; -import type { CliCommand } from "./commands.js"; +import type { CliCommand, OpenWikiRunMode } from "./commands.js"; import { isDebugMode } from "./debug.js"; import { getAuthFix, getAuthFixSteps } from "./diagnostics/auth-fix.js"; import { getErrorDiagnostics } from "./diagnostics/error-diagnostics.js"; @@ -390,18 +392,25 @@ export function writePrintErrorDiagnostics(error: unknown): void { /** * Dispatches `stratiki book` subcommands: init, status, refresh, context. + * Company mode uses the Stratiki home; other modes default to repo openwiki/. */ export async function runBookCommand( command: Extract, + mode: OpenWikiRunMode = "code", ): Promise { - const bookDir = path.join(process.cwd(), "openwiki"); + const bookDir = + mode === "company" + ? path.join(stratikiHomeDir, "openwiki") + : path.join(process.cwd(), "openwiki"); + const bookDbPath = + mode === "company" ? stratikiBookDbPath : openWikiBookDbPath; if (command.action === "init") { await initBookManifest(bookDir, command); return; } if (command.action === "status") { - await printBookStatus(bookDir); + await printBookStatus(bookDir, bookDbPath); return; } if (command.action === "context") { @@ -409,7 +418,7 @@ export async function runBookCommand( return; } - await refreshBook(bookDir); + await refreshBook(bookDir, bookDbPath); } async function initBookManifest( @@ -450,9 +459,12 @@ async function initBookManifest( * Prints episode-store counts plus per-source tier/staleness derived from * the workspace manifest. */ -async function printBookStatus(bookDir: string): Promise { +async function printBookStatus( + bookDir: string, + bookDbPath: string, +): Promise { const manifest = await loadManifestOrThrow(bookDir); - const store = await EpisodeStore.open(openWikiBookDbPath); + const store = await EpisodeStore.open(bookDbPath); try { process.stdout.write(`Workspace: ${manifest.name}\n`); @@ -491,7 +503,7 @@ async function printBookStatus(bookDir: string): Promise { * sources under a single-writer lease so concurrent refreshes cannot * interleave wiki edits. */ -async function refreshBook(bookDir: string): Promise { +async function refreshBook(bookDir: string, bookDbPath: string): Promise { const leasePath = path.join(openWikiHomeDir, "refresh.lock"); const lease = BookLease.at(leasePath); const acquired = await lease.acquire(); @@ -507,7 +519,7 @@ async function refreshBook(bookDir: string): Promise { try { const manifest = await loadManifestOrThrow(bookDir); const latestByConnector = new Map(); - const store = await EpisodeStore.open(openWikiBookDbPath); + const store = await EpisodeStore.open(bookDbPath); try { for (const episode of store.listRecent(1000)) { if (!latestByConnector.has(episode.connectorId)) { diff --git a/src/config/openwiki-home.ts b/src/config/openwiki-home.ts index 5849441c9..a6dce6647 100644 --- a/src/config/openwiki-home.ts +++ b/src/config/openwiki-home.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { restrictDirToCurrentUser } from "../platform/windows-acl.js"; export const OPENWIKI_CONFIG_DIR_ENV_KEY = "OPENWIKI_CONFIG_DIR"; +export const STRATIKI_HOME_ENV_KEY = "STRATIKI_HOME"; export function resolveOpenWikiHomeDir( environment: NodeJS.ProcessEnv = process.env, @@ -36,9 +37,41 @@ export function getOpenWikiHomeDisplayPath( : "~/.openwiki"; } +export function resolveStratikiHomeDir( + environment: NodeJS.ProcessEnv = process.env, +): string { + const configuredDir = environment[STRATIKI_HOME_ENV_KEY]?.trim(); + + if (!configuredDir) { + return path.join(os.homedir(), ".stratiki"); + } + + if (configuredDir === "~") { + return path.join(os.homedir()); + } + + if (configuredDir.startsWith("~/") || configuredDir.startsWith("~\\")) { + return path.resolve(os.homedir(), configuredDir.slice(2)); + } + + return path.resolve(configuredDir); +} + +export function getStratikiHomeDisplayPath( + environment: NodeJS.ProcessEnv = process.env, +): string { + return environment[STRATIKI_HOME_ENV_KEY]?.trim() + ? resolveStratikiHomeDir(environment) + : "~/.stratiki"; +} + export const openWikiHomeDir = resolveOpenWikiHomeDir(); +export const stratikiHomeDir = resolveStratikiHomeDir(); export const openWikiHomeDisplayPath = getOpenWikiHomeDisplayPath(); +export const stratikiHomeDisplayPath = getStratikiHomeDisplayPath(); export const openWikiConnectorsDir = path.join(openWikiHomeDir, "connectors"); +export const stratikiCompanyWikiDir = path.join(stratikiHomeDir, "wiki"); +export const stratikiBookDbPath = path.join(stratikiHomeDir, "book.db"); export const openWikiConversationHistoryDir = path.join( openWikiHomeDir, "conversation_history", @@ -48,6 +81,7 @@ export const openWikiBookDbPath = path.join(openWikiHomeDir, "book.db"); export const openWikiSkillsDir = path.join(openWikiHomeDir, "skills"); export const openWikiConnectorsDisplayPath = `${openWikiHomeDisplayPath}/connectors`; export const openWikiLocalWikiDisplayPath = `${openWikiHomeDisplayPath}/wiki`; +export const stratikiCompanyWikiDisplayPath = `${stratikiHomeDisplayPath}/wiki`; export const openWikiSkillsDisplayPath = `${openWikiHomeDisplayPath}/skills`; export const openWikiEnvDisplayPath = `${openWikiHomeDisplayPath}/.env`; @@ -81,6 +115,13 @@ export async function ensureOpenWikiHome(): Promise { await mkdir(openWikiSkillsDir, { recursive: true, mode: 0o700 }); } +export async function ensureStratikiHome(): Promise { + await mkdir(stratikiHomeDir, { recursive: true, mode: 0o700 }); + await chmodIfExists(stratikiHomeDir, 0o700); + await restrictDirToCurrentUser(stratikiHomeDir); + await mkdir(stratikiCompanyWikiDir, { recursive: true, mode: 0o700 }); +} + export async function ensureConnectorHome(connectorId: string): Promise { assertSafeConnectorId(connectorId); await ensureOpenWikiHome(); diff --git a/test/book/lease.test.ts b/test/book/lease.test.ts index 0fa781ede..dd56a8744 100644 --- a/test/book/lease.test.ts +++ b/test/book/lease.test.ts @@ -81,7 +81,10 @@ describe("BookLease", () => { // Simulate a foreign holder by writing its lease directly. await writeFile( leasePath, - JSON.stringify({ acquiredAtIso: new Date().toISOString(), owner: "other" }), + JSON.stringify({ + acquiredAtIso: new Date().toISOString(), + owner: "other", + }), "utf8", ); const impostor = BookLease.at(leasePath, "impostor"); diff --git a/test/book/manifest.test.ts b/test/book/manifest.test.ts index 5fd3d9b39..9d52eb0b8 100644 --- a/test/book/manifest.test.ts +++ b/test/book/manifest.test.ts @@ -179,4 +179,42 @@ describe("book init name bound", () => { const command = parseCommand(["book", "init", "--name", "x".repeat(120)]); expect(command.kind).toBe("book"); }); + + test("book init accepts --mode company", async () => { + const { parseCommand } = await import("../../src/cli/commands.js"); + const command = parseCommand(["book", "init", "--mode", "company"]); + expect(command).toMatchObject({ + kind: "book", + action: "init", + mode: "company", + }); + }); + + test("book status defaults to code mode", async () => { + const { parseCommand } = await import("../../src/cli/commands.js"); + const command = parseCommand(["book", "status"]); + expect(command).toMatchObject({ + kind: "book", + action: "status", + mode: "code", + }); + }); + + test("book context accepts query and --mode company", async () => { + const { parseCommand } = await import("../../src/cli/commands.js"); + const command = parseCommand([ + "book", + "context", + "--mode", + "company", + "test", + "query", + ]); + expect(command).toMatchObject({ + kind: "book", + action: "context", + mode: "company", + query: "test query", + }); + }); }); diff --git a/test/book/packet.test.ts b/test/book/packet.test.ts index 34e3262c2..bf59b2994 100644 --- a/test/book/packet.test.ts +++ b/test/book/packet.test.ts @@ -27,8 +27,10 @@ afterEach(async () => { describe("ContextIndex", () => { test("indexes nested markdown and ranks matching pages", async () => { const wikiDir = await createWiki({ - "architecture/overview.md": "---\ntitle: Architecture\n---\nThe deploy pipeline uses blue-green releases.", - "quickstart.md": "# Quickstart\nRun stratiki init to generate the book. The deploy pipeline is documented elsewhere.", + "architecture/overview.md": + "---\ntitle: Architecture\n---\nThe deploy pipeline uses blue-green releases.", + "quickstart.md": + "# Quickstart\nRun stratiki init to generate the book. The deploy pipeline is documented elsewhere.", "claims/notes.md": "Unrelated content about coffee preferences.", }); const index = await ContextIndex.buildFromDirectory(wikiDir); @@ -39,9 +41,13 @@ describe("ContextIndex", () => { expect(entries.length).toBeGreaterThanOrEqual(2); expect(entries.map((entry) => entry.path)).toContain("/quickstart.md"); // Titles come from front matter or the first heading. - const architecture = entries.find((entry) => entry.path === "/architecture/overview.md"); + const architecture = entries.find( + (entry) => entry.path === "/architecture/overview.md", + ); expect(architecture?.title).toBe("Architecture"); - const quickstart = entries.find((entry) => entry.path === "/quickstart.md"); + const quickstart = entries.find( + (entry) => entry.path === "/quickstart.md", + ); expect(quickstart?.title).toBe("Quickstart"); } finally { index.close(); @@ -50,16 +56,17 @@ describe("ContextIndex", () => { test("skips dot-directories like .claims", async () => { const wikiDir = await createWiki({ - ".claims/secret-page.md": "Internal claim metadata should never be searchable.", + ".claims/secret-page.md": + "Internal claim metadata should never be searchable.", "public.md": "# Public page\nVisible knowledge.", }); const index = await ContextIndex.buildFromDirectory(wikiDir); try { expect(index.search("claim metadata")).toEqual([]); - expect(index.search("visible knowledge").map((entry) => entry.path)).toEqual([ - "/public.md", - ]); + expect( + index.search("visible knowledge").map((entry) => entry.path), + ).toEqual(["/public.md"]); } finally { index.close(); } @@ -81,7 +88,11 @@ describe("ContextIndex", () => { describe("renderPacket", () => { test("renders provenance-first markdown", () => { const packet = renderPacket("deploy pipeline", [ - { excerpt: ">>blue-green<< releases", path: "/arch.md", title: "Architecture" }, + { + excerpt: ">>blue-green<< releases", + path: "/arch.md", + title: "Architecture", + }, ]); expect(packet).toContain("# Context packet"); diff --git a/test/book/refresh-planner.test.ts b/test/book/refresh-planner.test.ts index 9d023b8e0..de1a1e5f2 100644 --- a/test/book/refresh-planner.test.ts +++ b/test/book/refresh-planner.test.ts @@ -40,14 +40,20 @@ describe("planRefresh", () => { NOW, ); - expect(due.map((decision) => decision.entry.connectorId)).toEqual(["github"]); - expect(deferred.map((decision) => decision.entry.connectorId)).toEqual(["linear"]); + expect(due.map((decision) => decision.entry.connectorId)).toEqual([ + "github", + ]); + expect(deferred.map((decision) => decision.entry.connectorId)).toEqual([ + "linear", + ]); // 168h window minus 0.5h elapsed rounds up to a whole remaining hour. expect(deferred[0]?.hoursRemaining).toBe(168); }); test("a source past its tier window is due with the elapsed reason", () => { - const oldIngest = new Date(NOW.getTime() - 25 * 60 * 60 * 1000).toISOString(); + const oldIngest = new Date( + NOW.getTime() - 25 * 60 * 60 * 1000, + ).toISOString(); const { due } = planRefresh( { hackernews: "daily" }, new Map([["hackernews", episode(oldIngest, "hackernews")]]), @@ -61,7 +67,11 @@ describe("planRefresh", () => { test("an unparseable last-ingest timestamp defers to due, never silently stalls", () => { const broken = episode("not-a-timestamp"); - const { due, deferred } = planRefresh({ rss: "cold" }, new Map([["rss", broken]]), NOW); + const { due, deferred } = planRefresh( + { rss: "cold" }, + new Map([["rss", broken]]), + NOW, + ); expect(deferred).toEqual([]); expect(due).toHaveLength(1); diff --git a/test/cli/commands.test.ts b/test/cli/commands.test.ts index 6715810d9..f67d51617 100644 --- a/test/cli/commands.test.ts +++ b/test/cli/commands.test.ts @@ -816,7 +816,9 @@ describe("parseCommand — --mode option forms and conflicts", () => { expect(result.kind).toBe("error"); if (result.kind === "error") { - expect(result.message).toMatch(/--mode requires personal or code/u); + expect(result.message).toMatch( + /--mode requires personal, code, or company/u, + ); } }); From 3dc671a1e718e46902d693d1b8862e799e2fecbf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 06:35:23 +0000 Subject: [PATCH 3/4] fix: use lazy getters for Stratiki paths to avoid homedir() during test init Convert Stratiki path exports to getter functions to defer homedir() calls until runtime. This prevents test failures when tests mock os.homedir(). Co-authored-by: divo12 --- src/cli/run-mode.ts | 4 ++-- src/cli/runners.ts | 8 ++++---- src/config/openwiki-home.ts | 41 +++++++++++++++++++++++++++++-------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/cli/run-mode.ts b/src/cli/run-mode.ts index 60214a3ca..6494fbb50 100644 --- a/src/cli/run-mode.ts +++ b/src/cli/run-mode.ts @@ -1,7 +1,7 @@ import type { OpenWikiOutputMode } from "../agent/types.js"; import { openWikiLocalWikiDir, - stratikiCompanyWikiDir, + getStratikiCompanyWikiDir, } from "../config/openwiki-home.js"; import type { CliCommand, OpenWikiRunMode } from "./commands.js"; @@ -46,7 +46,7 @@ export function getRunModeCwd( codeRuntimeCwd = process.cwd(), ): string { if (mode === "code") return codeRuntimeCwd; - if (mode === "company") return stratikiCompanyWikiDir; + if (mode === "company") return getStratikiCompanyWikiDir(); return openWikiLocalWikiDir; } diff --git a/src/cli/runners.ts b/src/cli/runners.ts index 8253742c7..c86d402d0 100644 --- a/src/cli/runners.ts +++ b/src/cli/runners.ts @@ -21,8 +21,8 @@ import { CONNECTOR_IDS, isConnectorId } from "../connectors/registry.js"; import { openWikiBookDbPath, openWikiHomeDir, - stratikiHomeDir, - stratikiBookDbPath, + getStratikiHomeDir, + getStratikiBookDbPath, } from "../config/openwiki-home.js"; import { resolveConfiguredProvider } from "../config/constants.js"; import { @@ -400,10 +400,10 @@ export async function runBookCommand( ): Promise { const bookDir = mode === "company" - ? path.join(stratikiHomeDir, "openwiki") + ? path.join(getStratikiHomeDir(), "openwiki") : path.join(process.cwd(), "openwiki"); const bookDbPath = - mode === "company" ? stratikiBookDbPath : openWikiBookDbPath; + mode === "company" ? getStratikiBookDbPath() : openWikiBookDbPath; if (command.action === "init") { await initBookManifest(bookDir, command); diff --git a/src/config/openwiki-home.ts b/src/config/openwiki-home.ts index a6dce6647..7a9a24adc 100644 --- a/src/config/openwiki-home.ts +++ b/src/config/openwiki-home.ts @@ -66,12 +66,8 @@ export function getStratikiHomeDisplayPath( } export const openWikiHomeDir = resolveOpenWikiHomeDir(); -export const stratikiHomeDir = resolveStratikiHomeDir(); export const openWikiHomeDisplayPath = getOpenWikiHomeDisplayPath(); -export const stratikiHomeDisplayPath = getStratikiHomeDisplayPath(); export const openWikiConnectorsDir = path.join(openWikiHomeDir, "connectors"); -export const stratikiCompanyWikiDir = path.join(stratikiHomeDir, "wiki"); -export const stratikiBookDbPath = path.join(stratikiHomeDir, "book.db"); export const openWikiConversationHistoryDir = path.join( openWikiHomeDir, "conversation_history", @@ -81,10 +77,35 @@ export const openWikiBookDbPath = path.join(openWikiHomeDir, "book.db"); export const openWikiSkillsDir = path.join(openWikiHomeDir, "skills"); export const openWikiConnectorsDisplayPath = `${openWikiHomeDisplayPath}/connectors`; export const openWikiLocalWikiDisplayPath = `${openWikiHomeDisplayPath}/wiki`; -export const stratikiCompanyWikiDisplayPath = `${stratikiHomeDisplayPath}/wiki`; export const openWikiSkillsDisplayPath = `${openWikiHomeDisplayPath}/skills`; export const openWikiEnvDisplayPath = `${openWikiHomeDisplayPath}/.env`; +// Stratiki getters - lazy to avoid homedir() during module init +let _stratikiHomeDir: string | undefined; +let _stratikiCompanyWikiDir: string | undefined; +let _stratikiBookDbPath: string | undefined; + +export function getStratikiHomeDir(): string { + if (_stratikiHomeDir === undefined) { + _stratikiHomeDir = resolveStratikiHomeDir(); + } + return _stratikiHomeDir; +} + +export function getStratikiCompanyWikiDir(): string { + if (_stratikiCompanyWikiDir === undefined) { + _stratikiCompanyWikiDir = path.join(getStratikiHomeDir(), "wiki"); + } + return _stratikiCompanyWikiDir; +} + +export function getStratikiBookDbPath(): string { + if (_stratikiBookDbPath === undefined) { + _stratikiBookDbPath = path.join(getStratikiHomeDir(), "book.db"); + } + return _stratikiBookDbPath; +} + export function getConnectorDir(connectorId: string): string { return path.join(openWikiConnectorsDir, connectorId); } @@ -116,10 +137,12 @@ export async function ensureOpenWikiHome(): Promise { } export async function ensureStratikiHome(): Promise { - await mkdir(stratikiHomeDir, { recursive: true, mode: 0o700 }); - await chmodIfExists(stratikiHomeDir, 0o700); - await restrictDirToCurrentUser(stratikiHomeDir); - await mkdir(stratikiCompanyWikiDir, { recursive: true, mode: 0o700 }); + const homeDir = getStratikiHomeDir(); + const wikiDir = getStratikiCompanyWikiDir(); + await mkdir(homeDir, { recursive: true, mode: 0o700 }); + await chmodIfExists(homeDir, 0o700); + await restrictDirToCurrentUser(homeDir); + await mkdir(wikiDir, { recursive: true, mode: 0o700 }); } export async function ensureConnectorHome(connectorId: string): Promise { From 53364cd1c6bc97ef6ce58b9ecb61264fff5d4e68 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 07:18:23 +0000 Subject: [PATCH 4/4] fix(company): use same wiki dir for book ops and company runs - runBookCommand now uses getStratikiCompanyWikiDir() for company mode - Ensures book init/status/refresh/context all use ~/.stratiki/wiki - Call ensureStratikiHome() before company book operations - Matches the directory used by company runs (stratiki company --init) This fixes the bug where company runs wrote to ~/.stratiki/wiki but book operations used ~/.stratiki/openwiki. Co-authored-by: divo12 --- src/cli/runners.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cli/runners.ts b/src/cli/runners.ts index c86d402d0..f45f1461d 100644 --- a/src/cli/runners.ts +++ b/src/cli/runners.ts @@ -21,8 +21,9 @@ import { CONNECTOR_IDS, isConnectorId } from "../connectors/registry.js"; import { openWikiBookDbPath, openWikiHomeDir, - getStratikiHomeDir, + getStratikiCompanyWikiDir, getStratikiBookDbPath, + ensureStratikiHome, } from "../config/openwiki-home.js"; import { resolveConfiguredProvider } from "../config/constants.js"; import { @@ -400,11 +401,15 @@ export async function runBookCommand( ): Promise { const bookDir = mode === "company" - ? path.join(getStratikiHomeDir(), "openwiki") + ? getStratikiCompanyWikiDir() : path.join(process.cwd(), "openwiki"); const bookDbPath = mode === "company" ? getStratikiBookDbPath() : openWikiBookDbPath; + if (mode === "company") { + await ensureStratikiHome(); + } + if (command.action === "init") { await initBookManifest(bookDir, command); return;