@sanqianx/project-knowledge 5.0.24 → 5.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/.agents/plugins/marketplace.json +20 -20
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +1361 -1353
  4. package/LICENSE +201 -201
  5. package/NOTICE +5 -5
  6. package/README.md +349 -349
  7. package/_modules/knowledge-engine/package.json +1 -1
  8. package/_modules/knowledge-engine/src/coordinator.js +48 -3
  9. package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -140
  10. package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +238 -238
  11. package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -26
  12. package/_modules/knowledge-engine/src/runtime/commit-prompt.js +193 -193
  13. package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +394 -394
  14. package/_modules/knowledge-engine/src/runtime/contracts.js +282 -282
  15. package/_modules/knowledge-engine/src/runtime/conversation-store.js +389 -389
  16. package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -228
  17. package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -62
  18. package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +505 -505
  19. package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +400 -400
  20. package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +171 -171
  21. package/_modules/knowledge-engine/src/runtime/scanner.js +298 -298
  22. package/_modules/knowledge-engine/ui/app.js +23 -2
  23. package/_site/README.md +30 -30
  24. package/_site/_test/agent-capture-mcp-ui-test.js +68 -68
  25. package/_site/_test/agent-capture-ui-test.js +101 -101
  26. package/_site/_test/ai-profile-resolver-test.js +137 -137
  27. package/_site/_test/ai-profile-test.js +75 -75
  28. package/_site/_test/ai-vendor-presets-test.js +91 -91
  29. package/_site/_test/automatic-context-test.js +70 -70
  30. package/_site/_test/automation-queue-test.js +14 -14
  31. package/_site/_test/automation-ui-test.js +40 -40
  32. package/_site/_test/background-task-registry-test.js +43 -43
  33. package/_site/_test/baseline-schema-test.js +91 -91
  34. package/_site/_test/bin-cli-test.js +230 -230
  35. package/_site/_test/bridge-adapter-test.js +95 -95
  36. package/_site/_test/bridge-consumer-service-test.js +310 -310
  37. package/_site/_test/candidate-bin-content-test.js +35 -35
  38. package/_site/_test/candidate-product-assets-test.js +13 -13
  39. package/_site/_test/candidate-release-contract-test.js +14 -14
  40. package/_site/_test/capture-hook-dedup-test.js +158 -158
  41. package/_site/_test/cdp-browser-failure-test.js +103 -103
  42. package/_site/_test/chat-claudecodeui-match-test.js +92 -92
  43. package/_site/_test/claude-executable-discovery-test.js +90 -90
  44. package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
  45. package/_site/_test/claude-workbench-test.js +174 -174
  46. package/_site/_test/cli-instance-management-test.js +62 -62
  47. package/_site/_test/client-capture-restart-e2e-test.js +189 -189
  48. package/_site/_test/codex-conversation-projection-test.js +60 -60
  49. package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
  50. package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
  51. package/_site/_test/codex-notify-fanout-test.js +141 -134
  52. package/_site/_test/codex-notify-upgrade-test.js +253 -253
  53. package/_site/_test/commit-boundary-freeze-test.js +147 -147
  54. package/_site/_test/commit-conversation-binding-test.js +99 -99
  55. package/_site/_test/commit-evidence-test.js +110 -110
  56. package/_site/_test/configuration-generation-test.js +22 -22
  57. package/_site/_test/context-pack-test.js +200 -200
  58. package/_site/_test/conversation-api-test.js +119 -119
  59. package/_site/_test/conversation-origin-test.js +15 -15
  60. package/_site/_test/conversation-store-test.js +138 -138
  61. package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
  62. package/_site/_test/desktop-browser-compat-test.js +28 -28
  63. package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
  64. package/_site/_test/embedding-config-test.js +32 -32
  65. package/_site/_test/explicit-commit-processor-test.js +37 -37
  66. package/_site/_test/fixtures/fake-browser-exit.js +10 -10
  67. package/_site/_test/fixtures/fake-browser-hang.js +10 -10
  68. package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
  69. package/_site/_test/fixtures/make-git-repos.js +92 -92
  70. package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
  71. package/_site/_test/folder-picker-output-test.js +19 -19
  72. package/_site/_test/fresh-hook-reimport-test.js +53 -53
  73. package/_site/_test/fresh-product-data-test.js +56 -56
  74. package/_site/_test/full-integration-e2e-test.js +167 -167
  75. package/_site/_test/git-path-compatibility-test.js +80 -80
  76. package/_site/_test/git-validation-test.js +81 -81
  77. package/_site/_test/glm-ui-refresh-review-test.js +313 -404
  78. package/_site/_test/harness-isolation-test.js +138 -0
  79. package/_site/_test/helpers/cdp-browser.js +383 -0
  80. package/_site/_test/helpers/cdp-network-capture.js +66 -0
  81. package/_site/_test/helpers/find-chrome.js +34 -0
  82. package/_site/_test/helpers/free-port.js +8 -0
  83. package/_site/_test/helpers/logging-ui-fixture.js +131 -0
  84. package/_site/_test/helpers/module-fixture.js +282 -0
  85. package/_site/_test/helpers/parallel-engine-advanced-engine-child.js +45 -0
  86. package/_site/_test/helpers/parallel-engine-advanced-fault-writer.js +171 -0
  87. package/_site/_test/helpers/parallel-engine-advanced-harness.js +245 -0
  88. package/_site/_test/helpers/parallel-engine-advanced-standin-analyzer.js +80 -0
  89. package/_site/_test/helpers/parallel-engine-advanced-ui-host.js +177 -0
  90. package/_site/_test/helpers/parallel-engine-final-harness.js +314 -0
  91. package/_site/_test/helpers/parallel-engine-final-standin-analyzer.js +159 -0
  92. package/_site/_test/helpers/parallel-knowledge-fixture.js +255 -0
  93. package/_site/_test/helpers/parallel-logging-acceptance-fixture.js +111 -0
  94. package/_site/_test/helpers/parallel-logging-acceptance-proxy.js +106 -0
  95. package/_site/_test/helpers/parallel-logging-fix-r2-proxy.js +205 -0
  96. package/_site/_test/helpers/parallel-logging-integration-5025-env.js +432 -0
  97. package/_site/_test/helpers/parallel-memory-analyzer.js +81 -0
  98. package/_site/_test/helpers/parallel-memory-event-bridge.js +128 -0
  99. package/_site/_test/helpers/parallel-memory-shell.js +470 -0
  100. package/_site/_test/helpers/parallel-nav-isolation-env.js +389 -0
  101. package/_site/_test/helpers/parallel-nav-module-ui.js +186 -0
  102. package/_site/_test/helpers/parallel-nav-proxy.js +148 -0
  103. package/_site/_test/helpers/parallel-nav-shell.js +361 -0
  104. package/_site/_test/helpers/parallel-qa-engine-endpoint-guard.js +206 -0
  105. package/_site/_test/helpers/parallel-qa-engine-endpoint-isolate.js +257 -0
  106. package/_site/_test/helpers/parallel-retrieval-followup-corpus.js +293 -0
  107. package/_site/_test/helpers/parallel-retrieval-followup-next-session.js +70 -0
  108. package/_site/_test/helpers/parallel-retrieval-followup-r2-shell.js +737 -0
  109. package/_site/_test/helpers/parallel-retrieval-followup-r2-wb-host.js +82 -0
  110. package/_site/_test/helpers/parallel-retrieval-followup-shell.js +279 -0
  111. package/_site/_test/helpers/parallel-workbench-fixture.js +371 -0
  112. package/_site/_test/helpers/parallel-workbench-host.js +80 -0
  113. package/_site/_test/helpers/parallel-workbench-integration-5025-fixture.js +408 -0
  114. package/_site/_test/helpers/parallel-workbench-integration-5025-harness.js +593 -0
  115. package/_site/_test/helpers/parallel-workbench-integration-5025-host.js +70 -0
  116. package/_site/_test/helpers/parallel-workbench-integration-5025-launcher.js +240 -0
  117. package/_site/_test/helpers/parallel-workbench-integration-5025-runtime-harness.js +630 -0
  118. package/_site/_test/helpers/parallel-workbench-source-map.js +196 -0
  119. package/_site/_test/helpers/spawn-server.js +106 -0
  120. package/_site/_test/helpers/version-visibility-fixture.js +251 -0
  121. package/_site/_test/hook-runtime-endpoint-test.js +88 -88
  122. package/_site/_test/hook-status-repair-api-test.js +152 -152
  123. package/_site/_test/hook-trigger-test.js +85 -85
  124. package/_site/_test/hook-worktree-test.js +42 -42
  125. package/_site/_test/import-preflight-api-test.js +234 -234
  126. package/_site/_test/index-builder-test.js +41 -41
  127. package/_site/_test/index-error-display-contract-test.js +10 -10
  128. package/_site/_test/index-writer-concurrency-test.js +135 -135
  129. package/_site/_test/integration-adapters-test.js +262 -262
  130. package/_site/_test/integration-auto-repair-test.js +113 -113
  131. package/_site/_test/integration-capture-setup-test.js +118 -118
  132. package/_site/_test/integration-intent-test.js +81 -81
  133. package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
  134. package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
  135. package/_site/_test/integration-runtime-binding-test.js +473 -473
  136. package/_site/_test/integration-status-scan-test.js +29 -29
  137. package/_site/_test/integration-surface-coverage-test.js +164 -164
  138. package/_site/_test/kb-framework-test.js +57 -57
  139. package/_site/_test/kbpath-follow-test.js +63 -63
  140. package/_site/_test/knowledge-db-test.js +144 -144
  141. package/_site/_test/knowledge-engine-client-test.js +120 -109
  142. package/_site/_test/knowledge-language-control-test.js +170 -170
  143. package/_site/_test/knowledge-maintenance-test.js +40 -40
  144. package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
  145. package/_site/_test/knowledge-query-test.js +54 -54
  146. package/_site/_test/knowledge-retrieval-service-test.js +77 -77
  147. package/_site/_test/knowledge-scopes-test.js +37 -37
  148. package/_site/_test/knowledge-storage-location-test.js +28 -28
  149. package/_site/_test/knowledge-storage-startup-test.js +52 -52
  150. package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
  151. package/_site/_test/launcher-readiness-test.js +80 -80
  152. package/_site/_test/legacy-claude-session-alias-test.js +106 -106
  153. package/_site/_test/legacy-routes-removed-test.js +47 -47
  154. package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
  155. package/_site/_test/live-gateway-test.js +48 -48
  156. package/_site/_test/local-marketplace-test.js +16 -16
  157. package/_site/_test/log-redaction-test.js +58 -58
  158. package/_site/_test/logging-api-test.js +89 -89
  159. package/_site/_test/logging-sse-no-gap-test.js +88 -88
  160. package/_site/_test/logging-ui-test.js +107 -107
  161. package/_site/_test/markdown-delta-overlay-test.js +85 -85
  162. package/_site/_test/markdown-maintenance-api-test.js +75 -75
  163. package/_site/_test/markdown-maintenance-test.js +11 -11
  164. package/_site/_test/mcp-server-test.js +149 -149
  165. package/_site/_test/model-context-windows-test.js +89 -89
  166. package/_site/_test/module-agent-start-proxy-test.js +65 -65
  167. package/_site/_test/module-artifact-integrity-test.js +27 -27
  168. package/_site/_test/module-boundary-test.js +30 -30
  169. package/_site/_test/module-bridge-eventbridge-test.js +202 -202
  170. package/_site/_test/module-dynamic-port-test.js +118 -118
  171. package/_site/_test/module-index-proxy-test.js +33 -33
  172. package/_site/_test/module-model-configuration-test.js +44 -44
  173. package/_site/_test/module-process-lifecycle-test.js +35 -35
  174. package/_site/_test/module-stream-proxy-test.js +34 -34
  175. package/_site/_test/module-terminal-restart-test.js +63 -63
  176. package/_site/_test/module-wiring-unbind-race-test.js +59 -59
  177. package/_site/_test/native-context-integrations-test.js +86 -86
  178. package/_site/_test/non-release-ci-test.js +34 -34
  179. package/_site/_test/offline-boundary-isolation-test.js +32 -32
  180. package/_site/_test/orphaned-claim-recovery-test.js +80 -80
  181. package/_site/_test/package-boundary-test.js +43 -43
  182. package/_site/_test/package-startup-test.js +56 -56
  183. package/_site/_test/packaged-ui-smoke-test.js +88 -88
  184. package/_site/_test/parallel-ci-r9-posix-socket-budget-test.js +156 -0
  185. package/_site/_test/parallel-engine-advanced-eng005-test.js +136 -0
  186. package/_site/_test/parallel-engine-advanced-eng006-test.js +171 -0
  187. package/_site/_test/parallel-engine-advanced-eng007-reconcile-test.js +231 -0
  188. package/_site/_test/parallel-engine-advanced-eng009-test.js +208 -0
  189. package/_site/_test/parallel-engine-advanced-eng010-test.js +213 -0
  190. package/_site/_test/parallel-engine-advanced-eng011-ui-probe.js +459 -0
  191. package/_site/_test/parallel-engine-final-eng002-test.js +255 -0
  192. package/_site/_test/parallel-engine-final-eng007-test.js +269 -0
  193. package/_site/_test/parallel-engine-final-eng011-test.js +197 -0
  194. package/_site/_test/parallel-engine-final-eng012-test.js +354 -0
  195. package/_site/_test/parallel-knowledge-client-error-code-test.js +184 -0
  196. package/_site/_test/parallel-knowledge-engine-test.js +353 -0
  197. package/_site/_test/parallel-knowledge-retrieval-test.js +292 -0
  198. package/_site/_test/parallel-knowledge-vector-test.js +168 -0
  199. package/_site/_test/parallel-linux-ci-r7-log007-day-test.js +110 -0
  200. package/_site/_test/parallel-logging-acceptance-log003-test.js +224 -0
  201. package/_site/_test/parallel-logging-acceptance-log004-test.js +231 -0
  202. package/_site/_test/parallel-logging-acceptance-log005-test.js +208 -0
  203. package/_site/_test/parallel-logging-acceptance-log006-test.js +214 -0
  204. package/_site/_test/parallel-logging-acceptance-log007-test.js +223 -0
  205. package/_site/_test/parallel-logging-acceptance-log008-test.js +180 -0
  206. package/_site/_test/parallel-logging-fix-r2-crash-probe.js +271 -0
  207. package/_site/_test/parallel-logging-fix-r2-gap-test.js +294 -0
  208. package/_site/_test/parallel-logging-fix-r2-normalhook-test.js +330 -0
  209. package/_site/_test/parallel-logging-integration-5025-portability-test.js +554 -0
  210. package/_site/_test/parallel-memory-candidate-test.js +301 -0
  211. package/_site/_test/parallel-memory-manual-test.js +197 -0
  212. package/_site/_test/parallel-memory-scope-test.js +228 -0
  213. package/_site/_test/parallel-memory-source-test.js +227 -0
  214. package/_site/_test/parallel-memory-sync-test.js +238 -0
  215. package/_site/_test/parallel-memory-view-r8-reload-gate-test.js +123 -0
  216. package/_site/_test/parallel-nav-failure-test.js +119 -0
  217. package/_site/_test/parallel-nav-isolation-test.js +308 -0
  218. package/_site/_test/parallel-nav-isolation-vector-path-test.js +90 -0
  219. package/_site/_test/parallel-nav-offline-test.js +272 -0
  220. package/_site/_test/parallel-nav-operability-test.js +257 -0
  221. package/_site/_test/parallel-nav-switch-test.js +247 -0
  222. package/_site/_test/parallel-nav-theme-test.js +130 -0
  223. package/_site/_test/parallel-nav-viewport-test.js +310 -0
  224. package/_site/_test/parallel-qa-engine-endpoint-guard-test.js +273 -0
  225. package/_site/_test/parallel-retrieval-followup-citation-test.js +219 -0
  226. package/_site/_test/parallel-retrieval-followup-corpus-test.js +287 -0
  227. package/_site/_test/parallel-workbench-independent-host.js +262 -0
  228. package/_site/_test/parallel-workbench-integration-5025-abort-probe.js +340 -0
  229. package/_site/_test/parallel-workbench-integration-5025-drafts-probe.js +588 -0
  230. package/_site/_test/parallel-workbench-integration-5025-env-check.js +317 -0
  231. package/_site/_test/parallel-workbench-integration-5025-model-probe.js +342 -0
  232. package/_site/_test/parallel-workbench-integration-5025-ordering-probe.js +326 -0
  233. package/_site/_test/parallel-workbench-model-probe.js +336 -0
  234. package/_site/_test/parallel-workbench-offline-test.js +169 -0
  235. package/_site/_test/parallel-workbench-provenance-trace.js +134 -0
  236. package/_site/_test/parallel-workbench-source-match.js +206 -0
  237. package/_site/_test/parallel-workbench-standalone-turn.js +349 -0
  238. package/_site/_test/path-consistency-test.js +151 -151
  239. package/_site/_test/pending-sweep-test.js +9 -9
  240. package/_site/_test/post-commit-automation-test.js +87 -87
  241. package/_site/_test/pr-consumer-contract-test.js +55 -55
  242. package/_site/_test/product-brand-contract-test.js +25 -25
  243. package/_site/_test/product-diagnostics-ui-test.js +93 -93
  244. package/_site/_test/product-empty-state-test.js +148 -0
  245. package/_site/_test/product-import-ui-test.js +219 -104
  246. package/_site/_test/product-navigation-test.js +152 -0
  247. package/_site/_test/product-progress-status-test.js +84 -84
  248. package/_site/_test/project-delete-recovery-test.js +64 -64
  249. package/_site/_test/project-domain-create-test.js +91 -0
  250. package/_site/_test/project-domain-scope-retrieval-test.js +214 -0
  251. package/_site/_test/project-git-init-confirm-test.js +115 -0
  252. package/_site/_test/project-goal-editor-test.js +144 -144
  253. package/_site/_test/project-import-duplicate-alias-test.js +134 -0
  254. package/_site/_test/project-import-unicode-restart-test.js +140 -0
  255. package/_site/_test/project-lifecycle-transaction-test.js +106 -106
  256. package/_site/_test/project-module-partial-recovery-test.js +222 -0
  257. package/_site/_test/project-remove-running-guard-test.js +79 -79
  258. package/_site/_test/project-store-test.js +90 -90
  259. package/_site/_test/project-unbind-late-read-ui-test.js +250 -0
  260. package/_site/_test/project-unbind-writer-guard-test.js +231 -0
  261. package/_site/_test/prompt-settings-test.js +115 -115
  262. package/_site/_test/protected-architecture-gate-test.js +122 -122
  263. package/_site/_test/refactor-characterization-test.js +36 -36
  264. package/_site/_test/registry-events-test.js +38 -38
  265. package/_site/_test/release-version-sync-test.js +115 -115
  266. package/_site/_test/requirement-binding-test.js +113 -113
  267. package/_site/_test/requirement-recorder-test.js +173 -173
  268. package/_site/_test/run-all-tests.js +173 -161
  269. package/_site/_test/runtime-endpoint-test.js +64 -64
  270. package/_site/_test/scanner-test.js +83 -83
  271. package/_site/_test/server-security-test.js +96 -96
  272. package/_site/_test/sessions-stream-test.js +93 -93
  273. package/_site/_test/shared-contracts-test.js +58 -58
  274. package/_site/_test/shared-scope-root-binding-test.js +74 -67
  275. package/_site/_test/simple-import-test.js +98 -98
  276. package/_site/_test/startup-analysis-disabled-test.js +16 -16
  277. package/_site/_test/storage-foundation-test.js +78 -78
  278. package/_site/_test/structured-logger-test.js +101 -101
  279. package/_site/_test/t07b-cln-005-coverage-test.js +212 -0
  280. package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
  281. package/_site/_test/team-knowledge-store-test.js +132 -132
  282. package/_site/_test/test-server-cleanup-test.js +35 -35
  283. package/_site/_test/test-server-module-isolation-test.js +37 -37
  284. package/_site/_test/tracking-start-test.js +144 -144
  285. package/_site/_test/vector-storage-root-test.js +75 -75
  286. package/_site/_test/version-visibility-browser-probe.js +249 -0
  287. package/_site/_test/version-visibility-test.js +92 -0
  288. package/_site/_test/workbench-context-receipts-test.js +22 -22
  289. package/_site/_test/workbench-permission-test.js +117 -117
  290. package/_site/_test/zcode-capture-integration-test.js +112 -112
  291. package/_site/lib/ai-profile-resolver.js +78 -78
  292. package/_site/lib/ai-vendor-presets.js +83 -83
  293. package/_site/lib/ai-workspace.js +104 -104
  294. package/_site/lib/atomic-file.js +204 -204
  295. package/_site/lib/automation-config.js +140 -140
  296. package/_site/lib/bridge-adapter.js +260 -260
  297. package/_site/lib/bridge-consumer-service.js +402 -402
  298. package/_site/lib/capture-hook-config.js +108 -108
  299. package/_site/lib/claude-cli-runner.js +1599 -1599
  300. package/_site/lib/cli-instances.js +43 -43
  301. package/_site/lib/codex-context-status.js +17 -17
  302. package/_site/lib/codex-notify-config.js +193 -193
  303. package/_site/lib/commit-conversation-binder.js +247 -247
  304. package/_site/lib/commit-processing-ledger.js +27 -27
  305. package/_site/lib/commit-prompt.js +202 -202
  306. package/_site/lib/commit-reconciler.js +444 -444
  307. package/_site/lib/context-pack-builder.js +299 -299
  308. package/_site/lib/contracts.js +291 -291
  309. package/_site/lib/conversation-exclusions.js +204 -204
  310. package/_site/lib/conversation-query-service.js +161 -161
  311. package/_site/lib/conversation-store.js +387 -387
  312. package/_site/lib/data-dir.js +13 -13
  313. package/_site/lib/embedding-service.js +85 -85
  314. package/_site/lib/engine-connection.js +93 -93
  315. package/_site/lib/engine-knowledge-client.js +76 -72
  316. package/_site/lib/evidence-bundle.js +228 -228
  317. package/_site/lib/folder-picker-output.js +24 -24
  318. package/_site/lib/git-path.js +14 -14
  319. package/_site/lib/git-runner.js +62 -62
  320. package/_site/lib/github-team-store.js +1175 -1175
  321. package/_site/lib/hook-manager.js +203 -203
  322. package/_site/lib/index-builder.js +157 -157
  323. package/_site/lib/index-service.js +173 -173
  324. package/_site/lib/integration-auto-repair.js +77 -77
  325. package/_site/lib/integration-installer.js +1240 -1240
  326. package/_site/lib/integration-intent.js +88 -88
  327. package/_site/lib/integration-status-scan.js +99 -99
  328. package/_site/lib/integration-status-worker.js +14 -14
  329. package/_site/lib/kb-framework.js +191 -191
  330. package/_site/lib/kb-validator.js +124 -124
  331. package/_site/lib/knowledge-db.js +363 -363
  332. package/_site/lib/knowledge-promotion.js +435 -435
  333. package/_site/lib/knowledge-retrieval-service.js +401 -401
  334. package/_site/lib/knowledge-schema.js +121 -121
  335. package/_site/lib/knowledge-tool-runtime.js +366 -366
  336. package/_site/lib/live-gateway.js +78 -78
  337. package/_site/lib/llm-client.js +173 -173
  338. package/_site/lib/markdown-knowledge-indexer.js +230 -230
  339. package/_site/lib/model-context-windows.js +82 -82
  340. package/_site/lib/module-bridge.js +874 -874
  341. package/_site/lib/module-port-resolver.js +57 -57
  342. package/_site/lib/post-commit-automation.js +141 -141
  343. package/_site/lib/product-data.js +35 -35
  344. package/_site/lib/project-lifecycle-service.js +554 -554
  345. package/_site/lib/project-registry-store.js +129 -129
  346. package/_site/lib/project-store.js +268 -268
  347. package/_site/lib/prompt-registry.js +148 -148
  348. package/_site/lib/public-version-info.js +37 -0
  349. package/_site/lib/registry-events.js +16 -16
  350. package/_site/lib/requirement-adapters.js +10 -10
  351. package/_site/lib/requirement-binder.js +112 -112
  352. package/_site/lib/requirement-recorder.js +278 -278
  353. package/_site/lib/runtime-control.js +89 -89
  354. package/_site/lib/runtime-endpoint.js +160 -160
  355. package/_site/lib/scanner.js +298 -298
  356. package/_site/lib/server-app.js +1672 -1664
  357. package/_site/lib/settings-store.js +153 -153
  358. package/_site/lib/storage-layout.js +166 -166
  359. package/_site/lib/structured-logger.js +574 -574
  360. package/_site/lib/zcode-context-integration.js +60 -60
  361. package/_site/scripts/codex-notify-fanout.cjs +95 -95
  362. package/_site/scripts/folder-picker.ps1 +156 -156
  363. package/_site/scripts/hook-trigger.js +158 -158
  364. package/_site/scripts/install-module-candidates.js +69 -69
  365. package/_site/scripts/pack-module-candidates.js +89 -89
  366. package/_site/scripts/sync-product-content.js +28 -28
  367. package/_site/scripts/sync-release-version.js +164 -164
  368. package/_site/scripts/vendor-modules.js +49 -49
  369. package/_site/scripts/verify-module-runtime.js +50 -50
  370. package/_site/scripts/verify-product-runtime.js +36 -36
  371. package/_site/scripts/verify-retrieval-gold.js +83 -83
  372. package/_site/server.js +15 -15
  373. package/_site/start.bat +26 -26
  374. package/_site/stop.bat +11 -11
  375. package/bin/project-knowledge-integrations.js +254 -254
  376. package/bin/project-knowledge-kb.js +67 -67
  377. package/bin/project-knowledge-mcp.js +201 -201
  378. package/bin/project-knowledge.js +269 -170
  379. package/claude-prompts.json +21 -21
  380. package/docs/README.zh-CN.md +207 -205
  381. package/docs/assets/architecture.svg +181 -181
  382. package/docs/assets/logo.svg +49 -49
  383. package/docs/fresh-v5-start.md +44 -44
  384. package/docs/leader-glm-workbench-playbook.md +122 -122
  385. package/docs/project-registry-schema.md +33 -33
  386. package/docs/release-5.0.16-acceptance.md +176 -176
  387. package/docs/release-5.0.17-acceptance.md +117 -117
  388. package/docs/release-5.0.19-acceptance.md +39 -39
  389. package/docs/team-knowledge-mode-a-plan.md +73 -73
  390. package/docs/testing-strategy.md +33 -33
  391. package/module-runtime-manifest.json +68 -65
  392. package/package.json +113 -110
  393. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  394. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  395. package/plugins/project-knowledge/.mcp.json +1 -1
  396. package/templates/change.md +30 -30
  397. package/templates/project-readme.md +52 -52
  398. package/ui/index.html +3 -2
  399. package/ui/version-info.css +44 -0
  400. package/ui/version-info.js +24 -0
  401. package/vendor-manifest.json +55 -55
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+ // PRJ-011: changing the domain of a project updates the retrieval scope.
3
+ //
4
+ // The steps of the case are "change the domain of the project, then run a
5
+ // project / domain retrieval". The expectations are:
6
+ // 1. the scope follows the registration update;
7
+ // 2. the absolute knowledge path does not move.
8
+ //
9
+ // The instance is an isolated product server with its own data directory and
10
+ // its own knowledge root. The knowledge engine is the real vendored module;
11
+ // the three other modules are local fixtures that answer 503, so only the
12
+ // engine participates.
13
+ //
14
+ // Boundary: the vector index is not part of this fixture, so the retrieval
15
+ // answers from the current knowledge files only. The scope expansion and the
16
+ // registration update are the behaviour under test; the ranking channel is
17
+ // explicitly the lexical one, which the answer reports itself.
18
+
19
+ const assert = require('node:assert/strict');
20
+ const crypto = require('node:crypto');
21
+ const fs = require('node:fs');
22
+ const path = require('node:path');
23
+ const { spawnSync } = require('node:child_process');
24
+ const { spawnServer } = require('./helpers/spawn-server');
25
+ const { createModuleFixture, createUpstreamProxy } = require('./helpers/module-fixture');
26
+ const freePort = require('./helpers/free-port');
27
+ const { applyDriverIsolation } = require('./helpers/parallel-qa-engine-endpoint-isolate');
28
+ const { guardedCreateEngineServer } = require('./helpers/parallel-qa-engine-endpoint-guard');
29
+
30
+ const ROOT = path.resolve(__dirname, '../..');
31
+
32
+ function git(cwd, args) {
33
+ const result = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
34
+ assert.equal(result.status, 0, `git ${args.join(' ')} failed: ${result.stderr}`);
35
+ return String(result.stdout || '').trim();
36
+ }
37
+
38
+ function makeRepo(root, name) {
39
+ const repo = path.join(root, name);
40
+ fs.mkdirSync(repo, { recursive: true });
41
+ git(repo, ['init', '-q', '-b', 'main']);
42
+ git(repo, ['config', 'user.email', 'domain@local.invalid']);
43
+ git(repo, ['config', 'user.name', 'Domain Regression']);
44
+ fs.writeFileSync(path.join(repo, 'README.md'), `# ${name}\n`);
45
+ git(repo, ['add', '.']); git(repo, ['commit', '-qm', 'baseline']);
46
+ return repo;
47
+ }
48
+
49
+ async function waitForHealth(base, timeoutMs = 20000) {
50
+ const deadline = Date.now() + timeoutMs;
51
+ while (Date.now() < deadline) {
52
+ try {
53
+ const response = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(2000) });
54
+ if (response.ok) return;
55
+ } catch {}
56
+ await new Promise(resolve => setTimeout(resolve, 100));
57
+ }
58
+ throw new Error('Timed out waiting for the isolated server on ' + base);
59
+ }
60
+
61
+ async function main() {
62
+ // Driver isolation FIRST: the environment is rebuilt from a system whitelist
63
+ // with every product surface pointed at a per-run layout, before any product
64
+ // module is required.
65
+ const applied = applyDriverIsolation({ prefix: 'pk-qa-prj-domain' });
66
+ const fixture = fs.mkdtempSync(path.join(applied.layout.temp, 'pk-prj-domain-'));
67
+ const dataDir = path.join(fixture, 'data');
68
+ const knowledgeRoot = path.join(fixture, 'knowledge');
69
+ const repoA = makeRepo(fixture, 'repo-domain-a');
70
+ const repoB = makeRepo(fixture, 'repo-domain-b');
71
+ const knowledgeA = path.join(knowledgeRoot, '领域项目');
72
+ const knowledgeB = path.join(knowledgeRoot, '另一领域项目');
73
+ fs.mkdirSync(knowledgeA, { recursive: true }); fs.mkdirSync(knowledgeB, { recursive: true });
74
+
75
+ const engineToken = crypto.randomBytes(24).toString('hex');
76
+ // OWN fixtures first (offline by design — the lexical channel is the
77
+ // behaviour under test), then the engine constructed against them: the
78
+ // coordinator ignores env and would otherwise fall back to LIVE 8787/8790.
79
+ const terminal = createModuleFixture({ kind: 'terminal' });
80
+ const vectorHub = createModuleFixture({ kind: 'vectorHub' });
81
+ const eventBridge = createModuleFixture({ kind: 'eventBridge' });
82
+ for (const fixtureService of [terminal, vectorHub, eventBridge]) {
83
+ await fixtureService.start();
84
+ fixtureService.setOnline(false);
85
+ }
86
+ const engine = guardedCreateEngineServer({ dataDir: path.join(fixture, 'engine'), runtimeToken: engineToken, debug: true, consume: false, vectorUrl: vectorHub.url, bridgeUrl: eventBridge.url });
87
+ await engine.ready;
88
+ await new Promise(resolve => engine.server.listen(0, '127.0.0.1', resolve));
89
+ const engineUrl = `http://127.0.0.1:${engine.server.address().port}`;
90
+ const engineProxy = createUpstreamProxy({ target: engineUrl, token: engineToken, tag: 'engine' });
91
+ await engineProxy.start();
92
+
93
+ const port = await freePort();
94
+ const base = `http://127.0.0.1:${port}`;
95
+ const server = spawnServer({
96
+ root: ROOT,
97
+ port,
98
+ dataDir,
99
+ tag: 'project-domain',
100
+ analysisOwner: 'engine',
101
+ extraEnv: {
102
+ KB_MODULES_REGISTER: '1',
103
+ KB_MODULES_AUTOSTART: '0',
104
+ KB_KNOWLEDGE_ENGINE_URL: engineProxy.url,
105
+ KB_TERMINAL_URL: terminal.url,
106
+ KB_VECTORHUB_URL: vectorHub.url,
107
+ KB_EVENTBRIDGE_URL: eventBridge.url,
108
+ },
109
+ });
110
+ const api = async (method, route, body) => {
111
+ const response = await fetch(base + route, {
112
+ method,
113
+ headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
114
+ body: body === undefined ? undefined : JSON.stringify(body),
115
+ signal: AbortSignal.timeout(20000),
116
+ });
117
+ return { status: response.status, body: await response.json() };
118
+ };
119
+ const engineApi = async (method, route, body) => {
120
+ const response = await fetch(engineUrl + route, {
121
+ method,
122
+ headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
123
+ body: body === undefined ? undefined : JSON.stringify(body),
124
+ signal: AbortSignal.timeout(20000),
125
+ });
126
+ return { status: response.status, body: await response.json() };
127
+ };
128
+
129
+ try {
130
+ await waitForHealth(base);
131
+ assert.equal((await api('PATCH', '/api/settings', { knowledge: { rootPath: knowledgeRoot } })).status, 200);
132
+
133
+ // Two real domains. The product registers them as shared scopes with the
134
+ // engine, so a domain retrieval has something to expand to.
135
+ const first = await api('POST', '/api/domains', { name: '领域一' });
136
+ assert.equal(first.status, 201, JSON.stringify(first.body));
137
+ const domainA = first.body.domain.id;
138
+ const second = await api('POST', '/api/domains', { name: '领域二' });
139
+ assert.equal(second.status, 201, JSON.stringify(second.body));
140
+ const domainB = second.body.domain.id;
141
+ assert.ok(domainA && domainB && domainA !== domainB);
142
+
143
+ const scopesBefore = (await engineApi('GET', '/api/snapshot?summary=1&limit=1')).body.sharedScopes;
144
+ assert.ok(scopesBefore.some(scope => scope.kind === 'domain' && scope.domainId === domainA), 'the engine knows the first domain scope');
145
+ assert.ok(scopesBefore.some(scope => scope.kind === 'domain' && scope.domainId === domainB), 'the engine knows the second domain scope');
146
+ const scopePathsBefore = Object.fromEntries(scopesBefore.map(scope => [scope.id, scope.knowledgePath]));
147
+
148
+ const importedA = await api('POST', '/api/projects/import', { localPath: repoA, knowledgePath: knowledgeA, displayName: '领域项目', domainId: domainA });
149
+ assert.equal(importedA.status, 201, JSON.stringify(importedA.body));
150
+ const projectA = importedA.body.projectId;
151
+ assert.equal(importedA.body.project.config.domainId, domainA, 'the first project starts in the first domain');
152
+ const importedB = await api('POST', '/api/projects/import', { localPath: repoB, knowledgePath: knowledgeB, displayName: '另一领域项目', domainId: domainB });
153
+ assert.equal(importedB.status, 201, JSON.stringify(importedB.body));
154
+ const projectB = importedB.body.projectId;
155
+ assert.equal(importedB.body.project.config.domainId, domainB, 'the second project starts in the second domain');
156
+
157
+ // The knowledge file is written after the import: a new project's
158
+ // knowledge directory has to be empty when the project is registered.
159
+ const knowledgeFile = path.join(knowledgeA, 'note.md');
160
+ fs.writeFileSync(knowledgeFile, '# 领域笔记\n\n这是一条属于项目的知识记录。\n');
161
+ const knowledgeBytes = fs.readFileSync(knowledgeFile);
162
+
163
+ // A real domain retrieval through the product, answered by the real engine.
164
+ const before = await api('POST', '/api/knowledge/search', { projectId: projectA, query: '领域笔记', scope: 'domain' });
165
+ assert.equal(before.status, 200, JSON.stringify(before.body));
166
+ // The answer reports the scopes it scanned, one entry per scope.
167
+ const scopeIds = result => (result.health?.scopes || []).map(scope => scope.projectId);
168
+ assert.ok(scopeIds(before.body).includes(domainA), 'the retrieval runs in the domain of the project');
169
+ assert.equal(scopeIds(before.body).includes(domainB), false, 'the retrieval does not reach the other domain');
170
+ assert.equal(scopeIds(before.body).includes(projectB), false, 'the retrieval does not reach the project of the other domain');
171
+ assert.ok(before.body.results.length >= 1, 'the retrieval answers from the current knowledge files');
172
+ assert.ok(before.body.results.some(row => row.scope_project_id === projectA && String(row.snippet || '').includes('领域笔记')), 'the answer points at the knowledge file of the project');
173
+ assert.deepEqual(before.body.retrievalModes, ['lexical'], 'the answer reports the lexical channel it used');
174
+
175
+ // Change the domain of the first project.
176
+ const changed = await api('PATCH', `/api/projects/${encodeURIComponent(projectA)}`, { domainId: domainB });
177
+ assert.equal(changed.status, 200, JSON.stringify(changed.body));
178
+ assert.equal(changed.body.config.domainId, domainB, 'the product records the new domain');
179
+
180
+ // The registration in the real engine follows the change.
181
+ const registration = (await engineApi('GET', '/api/projects')).body.projects.find(project => project.projectId === projectA);
182
+ assert.ok(registration, 'the engine keeps the project registration');
183
+ assert.equal(registration.domainId, domainB, 'the engine registration carries the new domain');
184
+
185
+ // 1. The scope follows the registration update.
186
+ const after = await api('POST', '/api/knowledge/search', { projectId: projectA, query: '领域笔记', scope: 'domain' });
187
+ assert.equal(after.status, 200, JSON.stringify(after.body));
188
+ assert.ok(scopeIds(after.body).includes(domainB), 'the retrieval now runs in the new domain');
189
+ assert.equal(scopeIds(after.body).includes(domainA), false, 'the retrieval no longer reaches the old domain');
190
+ assert.ok(scopeIds(after.body).includes(projectB), 'the retrieval now reaches the project of the new domain');
191
+ assert.ok(after.body.results.some(row => row.scope_project_id === projectA && String(row.snippet || '').includes('领域笔记')), 'the project knowledge is still reachable');
192
+
193
+ // 2. The absolute knowledge path does not move.
194
+ const projected = (await api('GET', '/api/projects/projection')).body.projects.find(project => project.projectId === projectA);
195
+ assert.equal(path.resolve(projected.knowledgePath), path.resolve(knowledgeA), 'the knowledge path stays the same absolute path');
196
+ assert.deepEqual(fs.readFileSync(knowledgeFile), knowledgeBytes, 'the knowledge file bytes do not change');
197
+ assert.ok(fs.existsSync(path.join(knowledgeA, 'note.md')), 'the knowledge file stays where it is');
198
+ const scopesAfter = (await engineApi('GET', '/api/snapshot?summary=1&limit=1')).body.sharedScopes;
199
+ for (const scope of scopesAfter) {
200
+ assert.equal(scope.knowledgePath, scopePathsBefore[scope.id], `the shared scope path of ${scope.id} does not move`);
201
+ }
202
+ assert.ok(fs.existsSync(path.join(knowledgeRoot, '_shared')), 'the shared knowledge root stays where it is');
203
+
204
+ console.log('project-domain-scope-retrieval-test PASS (the domain retrieval scope follows the registration update; the absolute knowledge path does not move)');
205
+ } finally {
206
+ await server.cleanup();
207
+ await engine.close().catch(() => {});
208
+ await engineProxy.close();
209
+ for (const fixtureService of [terminal, vectorHub, eventBridge]) await fixtureService.close();
210
+ fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
211
+ }
212
+ }
213
+
214
+ main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+ // PRJ-008: Git initialization needs an explicit user confirmation.
3
+ //
4
+ // The steps of the case are "select a directory that is not a Git repository,
5
+ // then request the import with and without the initialization confirmation".
6
+ // Two expectations are covered:
7
+ // 1. without an explicit confirmation no `git init` may happen;
8
+ // 2. the initialization only happens inside the fixture the user selected.
9
+ //
10
+ // The instance is an isolated product server with its own data directory and
11
+ // its own knowledge root. Nothing outside the fixture directory is touched.
12
+
13
+ const assert = require('node:assert/strict');
14
+ const fs = require('node:fs');
15
+ const os = require('node:os');
16
+ const path = require('node:path');
17
+ const { spawnSync } = require('node:child_process');
18
+ const { spawnServer } = require('./helpers/spawn-server');
19
+ const freePort = require('./helpers/free-port');
20
+
21
+ const ROOT = path.resolve(__dirname, '../..');
22
+
23
+ function git(cwd, args) {
24
+ const result = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
25
+ return { status: result.status, stdout: String(result.stdout || '').trim(), stderr: String(result.stderr || '').trim() };
26
+ }
27
+
28
+ async function waitForHealth(base, timeoutMs = 20000) {
29
+ const deadline = Date.now() + timeoutMs;
30
+ while (Date.now() < deadline) {
31
+ try {
32
+ const response = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(2000) });
33
+ if (response.ok) return;
34
+ } catch {}
35
+ await new Promise(resolve => setTimeout(resolve, 100));
36
+ }
37
+ throw new Error('Timed out waiting for the isolated server on ' + base);
38
+ }
39
+
40
+ function findGitDirectories(root, depth = 0) {
41
+ const found = [];
42
+ if (depth > 3) return found;
43
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
44
+ if (!entry.isDirectory()) continue;
45
+ const absolute = path.join(root, entry.name);
46
+ if (entry.name === '.git') { found.push(absolute); continue; }
47
+ found.push(...findGitDirectories(absolute, depth + 1));
48
+ }
49
+ return found;
50
+ }
51
+
52
+ async function main() {
53
+ const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-prj-gitinit-'));
54
+ const dataDir = path.join(fixture, 'data');
55
+ const knowledgeRoot = path.join(fixture, 'knowledge');
56
+ const selected = path.join(fixture, 'selected-project');
57
+ const neighbour = path.join(fixture, 'neighbour-project');
58
+ fs.mkdirSync(selected); fs.mkdirSync(neighbour);
59
+ fs.writeFileSync(path.join(selected, 'README.md'), '# selected\n');
60
+ fs.writeFileSync(path.join(neighbour, 'README.md'), '# neighbour\n');
61
+
62
+ const port = await freePort();
63
+ const base = `http://127.0.0.1:${port}`;
64
+ const server = spawnServer({ root: ROOT, port, dataDir, tag: 'project-git-init' });
65
+ const api = async (method, route, body) => {
66
+ const response = await fetch(base + route, {
67
+ method,
68
+ headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
69
+ body: body === undefined ? undefined : JSON.stringify(body),
70
+ signal: AbortSignal.timeout(15000),
71
+ });
72
+ return { status: response.status, body: await response.json() };
73
+ };
74
+ try {
75
+ await waitForHealth(base);
76
+ assert.equal((await api('PATCH', '/api/settings', { knowledge: { rootPath: knowledgeRoot } })).status, 200);
77
+
78
+ // The preflight reports that a Git initialization is planned. The
79
+ // directory itself stays untouched: the preflight never mutates state.
80
+ const preflight = await api('POST', '/api/projects/preflight-import', { localPath: selected, knowledgePath: path.join(knowledgeRoot, 'selected'), displayName: '选定目录' });
81
+ assert.equal(preflight.status, 200, JSON.stringify(preflight.body));
82
+ assert.equal(preflight.body.plannedGitInit, true, 'the preflight must report the planned Git initialization');
83
+ assert.equal(preflight.body.checks.git.status, 'non-git', 'the preflight must report the directory as a non-Git directory');
84
+ assert.equal(fs.existsSync(path.join(selected, '.git')), false, 'the preflight must not initialize the repository');
85
+
86
+ // Without the explicit confirmation the import is refused and no Git
87
+ // repository is created anywhere.
88
+ const refused = await api('POST', '/api/projects/import', { localPath: selected, knowledgePath: path.join(knowledgeRoot, 'selected'), displayName: '选定目录' });
89
+ assert.equal(refused.status, 400, JSON.stringify(refused.body));
90
+ assert.equal(refused.body.error.code, 'INVALID_ARGUMENT');
91
+ assert.match(refused.body.error.message, /初始化/, 'the refusal must name the missing Git initialization confirmation');
92
+ assert.equal(fs.existsSync(path.join(selected, '.git')), false, 'a refused import must not run git init');
93
+ assert.deepEqual(findGitDirectories(fixture), [], 'a refused import must not create a Git repository anywhere in the fixture');
94
+ assert.equal((await api('GET', '/api/projects/aggregated')).body.projects.length, 0, 'a refused import must not register a project');
95
+
96
+ // With the explicit confirmation the initialization happens inside the
97
+ // selected fixture only.
98
+ const imported = await api('POST', '/api/projects/import', { localPath: selected, knowledgePath: path.join(knowledgeRoot, 'selected'), displayName: '选定目录', confirmGitInit: true });
99
+ assert.equal(imported.status, 201, JSON.stringify(imported.body));
100
+ assert.equal(fs.existsSync(path.join(selected, '.git')), true, 'the confirmed import must initialize the selected directory');
101
+ assert.equal(fs.existsSync(path.join(neighbour, '.git')), false, 'the neighbouring directory must stay untouched');
102
+ assert.deepEqual(findGitDirectories(fixture), [path.join(selected, '.git')], 'exactly the selected fixture may hold a Git repository');
103
+ const inside = git(selected, ['rev-parse', '--is-inside-work-tree']);
104
+ assert.equal(inside.status, 0, inside.stderr);
105
+ assert.equal(inside.stdout, 'true');
106
+ assert.equal(path.resolve(imported.body.project.config.repoPath), path.resolve(selected), 'the registered workspace path is the selected fixture');
107
+ assert.equal((await api('GET', '/api/projects/aggregated')).body.projects.length, 1, 'the confirmed import registers exactly one project');
108
+ console.log('project-git-init-confirm-test PASS (no git init without confirmation; initialization confined to the selected fixture)');
109
+ } finally {
110
+ await server.cleanup();
111
+ fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
112
+ }
113
+ }
114
+
115
+ main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
@@ -1,145 +1,145 @@
1
- // _site/_test/project-goal-editor-test.js
2
- //
3
- // T13: Project Goal editor.
4
- // GET /api/projects/:id/goal -> read GOAL.md from project knowledge path
5
- // PUT /api/projects/:id/goal -> atomic write GOAL.md
6
- // Verifies:
7
- // - missing GOAL.md returns exists=false;
8
- // - PUT creates the file atomically;
9
- // - subsequent GET returns the persisted content;
10
- // - empty content allowed (no auto-template);
11
- // - the file is scoped to the project's own knowledgePath (no
12
- // cross-project writes);
13
- // - 413 when content exceeds 256KB.
14
-
15
- const assert = require('assert');
16
- const fs = require('fs');
17
- const os = require('os');
18
- const path = require('path');
19
- const { spawnSync } = require('child_process');
20
- const { spawnServer } = require('./helpers/spawn-server');
21
-
22
- const ROOT = path.resolve(__dirname, '..', '..');
23
- const sitePort = 8000 + (process.pid % 200);
24
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-' + process.pid + '-'));
25
- const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-repo-' + process.pid + '-'));
26
-
27
- function git(args) {
28
- const result = spawnSync('git', ['-C', repo, ...args], {
29
- encoding: 'utf8', windowsHide: true,
30
- env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
31
- });
32
- if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed: ${result.status}`);
33
- return String(result.stdout || '').trim();
34
- }
35
-
36
- async function serverFetch(url, init, timeoutMs = 10000) {
37
- const controller = new AbortController();
38
- const timer = setTimeout(() => controller.abort(), timeoutMs);
39
- try {
40
- const response = await fetch(url, { ...init, signal: controller.signal });
41
- const text = await response.text();
42
- return { status: response.status, body: text ? JSON.parse(text) : {} };
43
- } finally {
44
- clearTimeout(timer);
45
- }
46
- }
47
-
48
- (async () => {
49
- git(['init', '-q', '-b', 'main']);
50
- git(['config', 'user.email', 'goal@example.local']);
51
- git(['config', 'user.name', 'Goal']);
52
- fs.writeFileSync(path.join(repo, 'README.md'), '# goal\n');
53
- git(['add', 'README.md']);
54
- git(['commit', '-q', '-m', 'baseline']);
55
- const knowledgeRoot = path.join(dataDir, 'knowledge');
56
- fs.mkdirSync(knowledgeRoot, { recursive: true });
57
- const server = spawnServer({
58
- root: ROOT, port: sitePort, dataDir, tag: 'goal',
59
- extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1', KB_EMBEDDING_FAKE: '1', KB_SKIP_MIGRATION: '1' },
60
- });
61
- try {
62
- for (let attempt = 0; attempt < 100; attempt += 1) {
63
- try { const r = await serverFetch(`http://127.0.0.1:${sitePort}/api/health`); if (r.body.ok) break; } catch {}
64
- await new Promise(resolve => setTimeout(resolve, 100));
65
- }
66
- await serverFetch(`http://127.0.0.1:${sitePort}/api/settings`, {
67
- method: 'PATCH', headers: { 'content-type': 'application/json' },
68
- body: JSON.stringify({ knowledge: { rootPath: knowledgeRoot } }),
69
- });
70
- await serverFetch(`http://127.0.0.1:${sitePort}/api/ai-profiles`, {
71
- method: 'PUT', headers: { 'content-type': 'application/json' },
72
- body: JSON.stringify({
73
- schema: 'ai-profiles/v1', defaultProfileId: 'fake',
74
- profiles: [{ id: 'fake', name: 'Fake', enabled: true, vendor: 'anthropic', model: 'fake', apiKeyUpdate: { mode: 'replace', value: 'k' } }],
75
- }),
76
- });
77
- const importR = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/import`, {
78
- method: 'POST', headers: { 'content-type': 'application/json' },
79
- body: JSON.stringify({ localPath: repo, aiProfileId: 'fake' }),
80
- });
81
- assert.strictEqual(importR.status, 201, JSON.stringify(importR.body));
82
- const projectId = importR.body.projectId;
83
- const knowledgePath = importR.body.config.knowledgePath;
84
-
85
- // Case 1: GET on missing GOAL.md returns exists=false
86
- let r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
87
- assert.strictEqual(r.status, 200);
88
- assert.strictEqual(r.body.exists, false);
89
- assert.strictEqual(r.body.content, '');
90
-
91
- // Case 2: PUT creates the file atomically with the supplied content
92
- r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
93
- method: 'PUT', headers: { 'content-type': 'application/json' },
94
- body: JSON.stringify({ content: '# Project Goal\n\nBuild a knowledge base for v4.1.22 -> main recovery.\n' }),
95
- });
96
- assert.strictEqual(r.status, 200, JSON.stringify(r.body));
97
- const goalPath = path.join(knowledgePath, 'GOAL.md');
98
- assert(fs.existsSync(goalPath), 'GOAL.md must be created');
99
- assert(fs.readFileSync(goalPath, 'utf8').includes('v4.1.22'), 'goal content must include the user-supplied text');
100
-
101
- // Case 3: GET now returns the persisted content
102
- r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
103
- assert.strictEqual(r.status, 200);
104
- assert.strictEqual(r.body.exists, true);
105
- assert(r.body.content.includes('v4.1.22'));
106
-
107
- // Case 4: empty content is allowed (no auto-template)
108
- r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
109
- method: 'PUT', headers: { 'content-type': 'application/json' },
110
- body: JSON.stringify({ content: '' }),
111
- });
112
- assert.strictEqual(r.status, 200);
113
- assert.strictEqual(fs.readFileSync(goalPath, 'utf8'), '', 'empty content must clear the file');
114
-
115
- // Case 5: writes are scoped to the project's own knowledgePath
116
- r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
117
- method: 'PUT', headers: { 'content-type': 'application/json' },
118
- body: JSON.stringify({ content: 'real content\n' }),
119
- });
120
- assert.strictEqual(r.status, 200);
121
- const otherFiles = fs.readdirSync(knowledgePath).filter(name => name !== 'GOAL.md');
122
- assert.deepStrictEqual(otherFiles, [], 'only GOAL.md must be created in the project knowledge path');
123
-
124
- // Case 6: 413 when content exceeds 256KB
125
- const huge = 'x'.repeat(257 * 1024);
126
- r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
127
- method: 'PUT', headers: { 'content-type': 'application/json' },
128
- body: JSON.stringify({ content: huge }),
129
- });
130
- assert.strictEqual(r.status, 413);
131
-
132
- // Case 7: GET on a non-existent project returns 404
133
- r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/does-not-exist/goal`, undefined);
134
- assert.strictEqual(r.status, 404);
135
- } finally {
136
- server.child.kill();
137
- await new Promise(resolve => server.child.once('exit', resolve));
138
- fs.rmSync(dataDir, { recursive: true, force: true });
139
- fs.rmSync(repo, { recursive: true, force: true });
140
- }
141
- console.log('project-goal-editor-test PASS');
142
- })().catch(error => {
143
- console.error(error.stack || error.message);
144
- process.exit(1);
1
+ // _site/_test/project-goal-editor-test.js
2
+ //
3
+ // T13: Project Goal editor.
4
+ // GET /api/projects/:id/goal -> read GOAL.md from project knowledge path
5
+ // PUT /api/projects/:id/goal -> atomic write GOAL.md
6
+ // Verifies:
7
+ // - missing GOAL.md returns exists=false;
8
+ // - PUT creates the file atomically;
9
+ // - subsequent GET returns the persisted content;
10
+ // - empty content allowed (no auto-template);
11
+ // - the file is scoped to the project's own knowledgePath (no
12
+ // cross-project writes);
13
+ // - 413 when content exceeds 256KB.
14
+
15
+ const assert = require('assert');
16
+ const fs = require('fs');
17
+ const os = require('os');
18
+ const path = require('path');
19
+ const { spawnSync } = require('child_process');
20
+ const { spawnServer } = require('./helpers/spawn-server');
21
+
22
+ const ROOT = path.resolve(__dirname, '..', '..');
23
+ const sitePort = 8000 + (process.pid % 200);
24
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-' + process.pid + '-'));
25
+ const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-repo-' + process.pid + '-'));
26
+
27
+ function git(args) {
28
+ const result = spawnSync('git', ['-C', repo, ...args], {
29
+ encoding: 'utf8', windowsHide: true,
30
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
31
+ });
32
+ if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed: ${result.status}`);
33
+ return String(result.stdout || '').trim();
34
+ }
35
+
36
+ async function serverFetch(url, init, timeoutMs = 10000) {
37
+ const controller = new AbortController();
38
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
39
+ try {
40
+ const response = await fetch(url, { ...init, signal: controller.signal });
41
+ const text = await response.text();
42
+ return { status: response.status, body: text ? JSON.parse(text) : {} };
43
+ } finally {
44
+ clearTimeout(timer);
45
+ }
46
+ }
47
+
48
+ (async () => {
49
+ git(['init', '-q', '-b', 'main']);
50
+ git(['config', 'user.email', 'goal@example.local']);
51
+ git(['config', 'user.name', 'Goal']);
52
+ fs.writeFileSync(path.join(repo, 'README.md'), '# goal\n');
53
+ git(['add', 'README.md']);
54
+ git(['commit', '-q', '-m', 'baseline']);
55
+ const knowledgeRoot = path.join(dataDir, 'knowledge');
56
+ fs.mkdirSync(knowledgeRoot, { recursive: true });
57
+ const server = spawnServer({
58
+ root: ROOT, port: sitePort, dataDir, tag: 'goal',
59
+ extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1', KB_EMBEDDING_FAKE: '1', KB_SKIP_MIGRATION: '1' },
60
+ });
61
+ try {
62
+ for (let attempt = 0; attempt < 100; attempt += 1) {
63
+ try { const r = await serverFetch(`http://127.0.0.1:${sitePort}/api/health`); if (r.body.ok) break; } catch {}
64
+ await new Promise(resolve => setTimeout(resolve, 100));
65
+ }
66
+ await serverFetch(`http://127.0.0.1:${sitePort}/api/settings`, {
67
+ method: 'PATCH', headers: { 'content-type': 'application/json' },
68
+ body: JSON.stringify({ knowledge: { rootPath: knowledgeRoot } }),
69
+ });
70
+ await serverFetch(`http://127.0.0.1:${sitePort}/api/ai-profiles`, {
71
+ method: 'PUT', headers: { 'content-type': 'application/json' },
72
+ body: JSON.stringify({
73
+ schema: 'ai-profiles/v1', defaultProfileId: 'fake',
74
+ profiles: [{ id: 'fake', name: 'Fake', enabled: true, vendor: 'anthropic', model: 'fake', apiKeyUpdate: { mode: 'replace', value: 'k' } }],
75
+ }),
76
+ });
77
+ const importR = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/import`, {
78
+ method: 'POST', headers: { 'content-type': 'application/json' },
79
+ body: JSON.stringify({ localPath: repo, aiProfileId: 'fake' }),
80
+ });
81
+ assert.strictEqual(importR.status, 201, JSON.stringify(importR.body));
82
+ const projectId = importR.body.projectId;
83
+ const knowledgePath = importR.body.config.knowledgePath;
84
+
85
+ // Case 1: GET on missing GOAL.md returns exists=false
86
+ let r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
87
+ assert.strictEqual(r.status, 200);
88
+ assert.strictEqual(r.body.exists, false);
89
+ assert.strictEqual(r.body.content, '');
90
+
91
+ // Case 2: PUT creates the file atomically with the supplied content
92
+ r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
93
+ method: 'PUT', headers: { 'content-type': 'application/json' },
94
+ body: JSON.stringify({ content: '# Project Goal\n\nBuild a knowledge base for v4.1.22 -> main recovery.\n' }),
95
+ });
96
+ assert.strictEqual(r.status, 200, JSON.stringify(r.body));
97
+ const goalPath = path.join(knowledgePath, 'GOAL.md');
98
+ assert(fs.existsSync(goalPath), 'GOAL.md must be created');
99
+ assert(fs.readFileSync(goalPath, 'utf8').includes('v4.1.22'), 'goal content must include the user-supplied text');
100
+
101
+ // Case 3: GET now returns the persisted content
102
+ r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
103
+ assert.strictEqual(r.status, 200);
104
+ assert.strictEqual(r.body.exists, true);
105
+ assert(r.body.content.includes('v4.1.22'));
106
+
107
+ // Case 4: empty content is allowed (no auto-template)
108
+ r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
109
+ method: 'PUT', headers: { 'content-type': 'application/json' },
110
+ body: JSON.stringify({ content: '' }),
111
+ });
112
+ assert.strictEqual(r.status, 200);
113
+ assert.strictEqual(fs.readFileSync(goalPath, 'utf8'), '', 'empty content must clear the file');
114
+
115
+ // Case 5: writes are scoped to the project's own knowledgePath
116
+ r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
117
+ method: 'PUT', headers: { 'content-type': 'application/json' },
118
+ body: JSON.stringify({ content: 'real content\n' }),
119
+ });
120
+ assert.strictEqual(r.status, 200);
121
+ const otherFiles = fs.readdirSync(knowledgePath).filter(name => name !== 'GOAL.md');
122
+ assert.deepStrictEqual(otherFiles, [], 'only GOAL.md must be created in the project knowledge path');
123
+
124
+ // Case 6: 413 when content exceeds 256KB
125
+ const huge = 'x'.repeat(257 * 1024);
126
+ r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
127
+ method: 'PUT', headers: { 'content-type': 'application/json' },
128
+ body: JSON.stringify({ content: huge }),
129
+ });
130
+ assert.strictEqual(r.status, 413);
131
+
132
+ // Case 7: GET on a non-existent project returns 404
133
+ r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/does-not-exist/goal`, undefined);
134
+ assert.strictEqual(r.status, 404);
135
+ } finally {
136
+ server.child.kill();
137
+ await new Promise(resolve => server.child.once('exit', resolve));
138
+ fs.rmSync(dataDir, { recursive: true, force: true });
139
+ fs.rmSync(repo, { recursive: true, force: true });
140
+ }
141
+ console.log('project-goal-editor-test PASS');
142
+ })().catch(error => {
143
+ console.error(error.stack || error.message);
144
+ process.exit(1);
145
145
  });