@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,148 @@
1
+ 'use strict';
2
+ // NAV-003 — 空项目。
3
+ //
4
+ // A brand new data directory must show real empty states, a failed registry
5
+ // read must not be presented as an empty library, and nothing may create a
6
+ // placeholder project on the way.
7
+ //
8
+ // The registry read is failed by injecting an HTTP 503 response from a proxy
9
+ // in front of the product: the read fails at the HTTP level (the browser
10
+ // really receives a failure answer), which is deliberately not a
11
+ // network-level disconnection. The regression drives the real UI in a real
12
+ // browser against an isolated instance (own port, own data dir, own knowledge
13
+ // dir, own Bridge journal, own browser profile). Screenshots are written to
14
+ // PK_TEST_EVIDENCE_DIR when the batch sets it, so this tracked file stays
15
+ // free of machine-specific paths.
16
+
17
+ const fs = require('node:fs');
18
+ const os = require('node:os');
19
+ const path = require('node:path');
20
+ const assert = require('node:assert/strict');
21
+
22
+ // One temp root per run, resolved from the caller's environment: the batch
23
+ // contract's TEMP/TMP/TMPDIR (and a CI runner's) flow into os.tmpdir(), so
24
+ // this tracked file carries no machine-specific path and never rewrites the
25
+ // process environment.
26
+ const TEMP_ROOT = os.tmpdir();
27
+
28
+ const { spawnServer } = require('./helpers/spawn-server');
29
+ const { findChrome } = require('./helpers/find-chrome');
30
+ const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
31
+ const { attachNetworkCapture } = require('./helpers/cdp-network-capture');
32
+ const { createProductProxy } = require('./helpers/module-fixture');
33
+ const freePort = require('./helpers/free-port');
34
+
35
+ const ROOT = path.resolve(__dirname, '../..');
36
+
37
+ async function main() {
38
+ const fixture = fs.mkdtempSync(path.join(TEMP_ROOT, 'pk-product-empty-state-'));
39
+ const evidenceDir = process.env.PK_TEST_EVIDENCE_DIR ? path.resolve(process.env.PK_TEST_EVIDENCE_DIR) : path.join(fixture, 'evidence');
40
+ fs.mkdirSync(evidenceDir, { recursive: true });
41
+ const port = await freePort();
42
+ const base = `http://127.0.0.1:${port}`;
43
+ const server = spawnServer({
44
+ root: ROOT,
45
+ port,
46
+ dataDir: path.join(fixture, 'data'),
47
+ tag: 'product-empty-state',
48
+ analysisOwner: 'engine',
49
+ });
50
+ const proxy = createProductProxy({ target: base, tag: 'product-empty-state' });
51
+ let browser;
52
+ try {
53
+ await waitFor(async () => {
54
+ try { return (await fetch(base + '/api/health')).ok; } catch { return false; }
55
+ }, 'the isolated shell', 20000);
56
+ const proxyUrl = await proxy.start();
57
+
58
+ // The very first registry read of this instance is answered with an
59
+ // injected HTTP 503 by the proxy in front of the product.
60
+ proxy.fail(
61
+ entry => entry.method === 'GET' && entry.path.startsWith('/api/projects/projection'),
62
+ 503,
63
+ '注入的登记读取故障',
64
+ { once: true },
65
+ );
66
+
67
+ browser = await launchCdpBrowser({
68
+ chrome: findChrome(),
69
+ profileDir: path.join(fixture, 'browser'),
70
+ url: proxyUrl + '/',
71
+ width: 1920,
72
+ height: 1080,
73
+ });
74
+ await browser.setViewport(1920, 1080);
75
+ const capture = attachNetworkCapture(browser);
76
+ await capture.enable();
77
+ const run = expression => browser.evaluate(expression);
78
+
79
+ // NAV-003 expected 2: the failed read is reported, never dressed up as an
80
+ // empty library.
81
+ await waitFor(() => run("document.getElementById('homeContent').textContent.includes('项目登记读取失败')"), 'the failed registry read is reported', 20000);
82
+ const failedRead = proxy.requests.find(entry => entry.path.startsWith('/api/projects/projection'));
83
+ assert.ok(failedRead, 'the registry read really left the browser for the product proxy');
84
+ assert.equal(failedRead.status, 503, 'the proxy really answered the registry read with an HTTP 503 failure');
85
+ assert.equal(await run("document.getElementById('homeContent').textContent.includes('从第一个项目开始')"), false, 'a failed read is not shown as an empty library');
86
+ assert.equal(await run("document.querySelectorAll('#homeContent [data-action=\"refresh\"]').length"), 1, 'the failed read offers a real retry');
87
+ await waitFor(() => run("document.getElementById('toast').textContent.includes('暂未刷新')"), 'the failed read is reported to the user', 10000);
88
+ await browser.screenshot(path.join(evidenceDir, 'empty-state-read-failure.png'));
89
+
90
+ // The retry reads the registry for real; now the empty state is a fact.
91
+ await run("document.querySelector('#homeContent [data-action=\"refresh\"]').click()");
92
+ await waitFor(() => run("document.getElementById('homeContent').textContent.includes('从第一个项目开始')"), 'the real empty home', 20000);
93
+ assert.equal(await run("document.getElementById('homeContent').textContent.includes('项目登记读取失败')"), false, 'the recovered read clears the failure');
94
+ await browser.screenshot(path.join(evidenceDir, 'empty-state-home.png'));
95
+
96
+ // NAV-003 expected 1: work, search, domains and projects show empty
97
+ // states.
98
+ await run("document.querySelector('[data-view=\"work\"]').click()");
99
+ await waitFor(() => run("document.getElementById('view-work').classList.contains('active')"), 'the work view');
100
+ assert.equal(await run("document.getElementById('workEmpty').hidden"), false, 'the work view shows its empty state');
101
+ assert.equal(await run("document.getElementById('workEmpty').textContent.includes('导入项目后')"), true, 'the work empty state explains what is missing');
102
+ assert.equal(await run("document.getElementById('workLayout').hidden"), true, 'the work layout stays hidden without a project');
103
+ await browser.screenshot(path.join(evidenceDir, 'empty-state-work.png'));
104
+
105
+ await run("document.querySelector('[data-view=\"search\"]').click()");
106
+ await waitFor(() => run("document.getElementById('view-search').classList.contains('active')"), 'the search view');
107
+ assert.equal(await run("document.getElementById('searchEmpty').hidden"), false, 'the search view shows its empty state');
108
+ assert.equal(await run("document.getElementById('searchEmpty').textContent.includes('尚未导入项目')"), true, 'the search empty state explains what is missing');
109
+ await browser.screenshot(path.join(evidenceDir, 'empty-state-search.png'));
110
+
111
+ await run("document.querySelector('[data-view=\"projects\"]').click()");
112
+ await waitFor(() => run("document.getElementById('view-projects').classList.contains('active')"), 'the projects view');
113
+ assert.equal(await run("document.getElementById('projectTree').textContent.includes('暂无项目')"), true, 'the project tree shows the empty group');
114
+ assert.equal(await run("document.getElementById('projectTree').textContent.includes('未分组')"), true, 'the empty project tree still shows the ungrouped domain');
115
+ assert.equal(await run("document.getElementById('projectDetail').textContent.includes('尚未导入项目')"), true, 'the project detail shows the empty state');
116
+ await browser.screenshot(path.join(evidenceDir, 'empty-state-projects.png'));
117
+
118
+ const brain = await (await fetch(base + '/api/brain')).json();
119
+ assert.equal((brain.brain.domains || []).length, 0, 'a fresh instance owns no domain');
120
+
121
+ // NAV-003 expected 3: nothing created a placeholder project.
122
+ assert.equal(await run("document.querySelectorAll('#projectTree [data-select-project]').length"), 0, 'the project tree offers no project');
123
+ assert.equal(await run("document.getElementById('projectSelect').disabled"), true, 'the project selector stays disabled');
124
+ assert.equal(await run("document.getElementById('projectSelect').value"), '', 'no project is selected');
125
+ assert.equal(await run("localStorage.getItem('kb.project')"), null, 'no project id is remembered');
126
+ const aggregated = await (await fetch(base + '/api/projects/aggregated')).json();
127
+ assert.equal(aggregated.projects.length, 0, 'the product registers no project');
128
+ const registryPath = path.join(server.dataDir, 'projects.json');
129
+ if (fs.existsSync(registryPath)) {
130
+ const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
131
+ assert.equal(Object.keys(registry.projects || {}).length, 0, 'the project registry stays empty');
132
+ }
133
+ const projectDirs = path.join(server.dataDir, 'projects');
134
+ assert.equal(fs.existsSync(projectDirs) ? fs.readdirSync(projectDirs).length : 0, 0, 'no project metadata directory is created');
135
+ const writes = capture.find(entry => entry.url.includes('/api/') && !['GET', 'HEAD', 'OPTIONS'].includes(entry.method));
136
+ assert.deepEqual(writes.map(entry => `${entry.method} ${entry.url}`), [], 'the empty state never writes a project');
137
+ assert.equal(browser.exceptions.length, 0, `no uncaught product JavaScript exceptions: ${JSON.stringify(browser.exceptions)}`);
138
+
139
+ console.log('product-empty-state-test PASS (real empty states, a failed read is not an empty library, no placeholder project)');
140
+ } finally {
141
+ await browser?.close();
142
+ await proxy.close();
143
+ await server.cleanup();
144
+ fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
145
+ }
146
+ }
147
+
148
+ main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
@@ -1,104 +1,219 @@
1
- 'use strict';
2
- const assert = require('node:assert/strict');
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
- const http = require('node:http');
7
- const { spawnSync } = require('node:child_process');
8
- const { spawnServer } = require('./helpers/spawn-server');
9
- const { findChrome } = require('./helpers/find-chrome');
10
- const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
11
-
12
- const ROOT = path.resolve(__dirname, '../..');
13
- const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-product-import-ui-'));
14
- const dataDir = path.join(fixture, 'data');
15
- const repo = path.join(fixture, '中文 空格项目');
16
- const knowledgeRoot = path.join(fixture, '知识目录');
17
- const knowledge = path.join(knowledgeRoot, '项目知识');
18
- const git = args => {
19
- const result = spawnSync('git', args, { cwd: repo, windowsHide: true, encoding: 'utf8' });
20
- assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
21
- };
22
- async function main() {
23
- fs.mkdirSync(repo); fs.mkdirSync(knowledgeRoot);
24
- git(['init', '-q']); git(['config', 'user.email', 'ui-regression@local.invalid']); git(['config', 'user.name', 'UI Regression']);
25
- fs.writeFileSync(path.join(repo, 'README.md'), '# UI regression baseline\n'); git(['add', '.']); git(['commit', '-qm', 'baseline']);
26
- const baseline = git(['rev-parse', 'HEAD']);
27
- // Offline modules are intentional: retain registration and report partial
28
- // wiring, rather than fabricate successful links.
29
- const offline = http.createServer((req, res) => { res.writeHead(503, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { code: 'MODULE_OFFLINE', message: '验收中的离线模块' } })); });
30
- await new Promise(resolve => offline.listen(0, '127.0.0.1', resolve));
31
- const offlineUrl = `http://127.0.0.1:${offline.address().port}`;
32
- const port = 7960 + process.pid % 200;
33
- const base = `http://127.0.0.1:${port}`;
34
- const server = spawnServer({ root: ROOT, port, dataDir, tag: 'product-import-ui', analysisOwner: 'engine', extraEnv: {
35
- KB_MODULES_REGISTER: '1', KB_TERMINAL_URL: offlineUrl, KB_VECTORHUB_URL: offlineUrl,
36
- KB_EVENTBRIDGE_URL: offlineUrl, KB_KNOWLEDGE_ENGINE_URL: offlineUrl,
37
- } });
38
- let browser;
39
- const api = async route => { const response = await fetch(base + route, { signal: AbortSignal.timeout(10000) }); assert.ok(response.ok, route); return response.json(); };
40
- try {
41
- await waitFor(async () => { try { return (await api('/api/health')).ok; } catch { return false; } }, 'isolated shell', 20000);
42
- browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(fixture, 'browser'), url: base, width: 1920, height: 1080 });
43
- await browser.setViewport(1920, 1080);
44
- const run = expression => browser.evaluate(expression);
45
- const click = selector => run(`document.querySelector(${JSON.stringify(selector)}).click()`);
46
- const fill = (selector, value) => run(`(() => { const field=document.querySelector(${JSON.stringify(selector)}); field.value=${JSON.stringify(value)}; field.dispatchEvent(new Event('input',{bubbles:true})); })()`);
47
- await waitFor(() => run("document.getElementById('homeContent').textContent.includes('第一个项目')"), 'real empty state');
48
- for (const view of ['work', 'search', 'memory', 'projects', 'activity', 'home']) {
49
- await click(`[data-view="${view}"]`);
50
- assert.equal(await run(`document.getElementById('view-${view}').classList.contains('active')`), true);
51
- assert.equal(await run('document.documentElement.scrollWidth > innerWidth'), false, `desktop overflow in ${view}`);
52
- }
53
- await click('#themeToggle'); assert.equal(await run('document.documentElement.dataset.theme'), 'dark');
54
- await click('[data-view="projects"]'); await click('#createDomain');
55
- await fill('#domainForm input[name="name"]', '回归领域'); await click('#domainForm button[type="submit"]');
56
- await waitFor(() => run("!document.getElementById('domainDialog').open && document.getElementById('projectTree').textContent.includes('回归领域')"), 'domain persisted from UI');
57
- await click('.view.active [data-action="import"]');
58
- assert.equal(await run("document.getElementById('confirmImport').disabled"), true);
59
- assert.equal(await run("document.getElementById('pickCode').hidden || document.getElementById('pickKnowledge').hidden"), false);
60
- await fill('#importCode', repo); await fill('#importName', '回归项目'); await fill('#importKnowledge', knowledge);
61
- const domainId = await run("[...document.getElementById('importDomain').options].find(o=>o.textContent==='回归领域').value");
62
- await run(`document.getElementById('importDomain').value=${JSON.stringify(domainId)}`);
63
- await waitFor(() => run("!document.getElementById('setupImportRoot').hidden"), 'root missing action');
64
- assert.equal(await run("document.getElementById('confirmImport').disabled"), true);
65
- await click('#setupImportRoot'); await fill('#knowledgeRoot', knowledgeRoot); await click('#saveBrainSettings');
66
- await waitFor(() => run("document.getElementById('importDialog').open && !document.getElementById('confirmImport').disabled"), 'setup returns to ready import');
67
- const values = await run("Object.fromEntries(['importCode','importKnowledge','importName','importDomain'].map(id=>[id,document.getElementById(id).value]))");
68
- assert.deepEqual(values, { importCode: repo, importKnowledge: knowledge, importName: '回归项目', importDomain: domainId });
69
- const racedHook = path.join(repo, '.git', 'hooks', 'post-commit');
70
- fs.writeFileSync(racedHook, '#!/bin/sh\necho third-party fixture\n');
71
- await click('#confirmImport');
72
- await waitFor(() => run("document.getElementById('preflightStatus').textContent.includes('HOOK_CONFLICT') && document.getElementById('preflightStatus').textContent.includes('操作 ID:op-')"), 'durable import failure feedback');
73
- await new Promise(resolve => setTimeout(resolve, 750));
74
- assert.ok(await run("document.getElementById('preflightStatus').textContent.includes('HOOK_CONFLICT')"), 'failed import must not turn back into a passed preflight');
75
- assert.equal((await api('/api/projects/aggregated')).projects.length, 0);
76
- assert.equal(fs.readFileSync(racedHook, 'utf8'), '#!/bin/sh\necho third-party fixture\n');
77
- const failureLogs = (await api('/api/logs?event=project.import.failed')).entries;
78
- assert.equal(failureLogs.length, 1); assert.equal(failureLogs[0].error.code, 'HOOK_CONFLICT');
79
- assert.ok(await run(`document.getElementById('preflightStatus').textContent.includes(${JSON.stringify(failureLogs[0].operationId)})`), 'UI operation ID resolves to the persisted failure');
80
- fs.unlinkSync(racedHook);
81
- await click('#confirmImport');
82
- await waitFor(() => run("!document.getElementById('importDialog').open && document.getElementById('projectDetail').textContent.includes('回归项目')"), 'import feedback', 30000);
83
- const selected = await run("document.getElementById('projectSelect').value");
84
- const project = (await api('/api/projects/aggregated')).projects.find(p => p.projectId === selected);
85
- assert.ok(project && Object.values(project.modules).every(module => !module.registered));
86
- assert.equal(path.resolve(project.workspacePath), path.resolve(repo)); assert.equal(path.resolve(project.knowledgePath), path.resolve(knowledge));
87
- assert.equal(project.domainId, domainId);
88
- assert.ok(fs.existsSync(path.join(repo, '.git/hooks/post-commit')));
89
- assert.ok(fs.existsSync(knowledge)); assert.equal(git(['rev-parse', 'HEAD']), baseline, 'import must not commit or analyze history');
90
- assert.match(await run("document.getElementById('toast').textContent"), /部分接线待恢复/);
91
- await click('.view.active [data-action="import"]'); await fill('#importCode', repo); await fill('#importKnowledge', knowledge);
92
- await waitFor(() => run("document.getElementById('preflightStatus').textContent.includes('此项目已导入')"), 'duplicate rejected');
93
- assert.equal(await run("document.getElementById('confirmImport').disabled"), true);
94
- assert.equal((await api('/api/projects/aggregated')).projects.length, 1);
95
- assert.equal(browser.exceptions.length, 0, 'no uncaught product JavaScript exceptions');
96
- console.log('product-import-ui-test PASS (1920×1080; real UI → registry/Hook/directories; offline module wiring remains partial)');
97
- } finally {
98
- await browser?.close(); await server.cleanup();
99
- offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve));
100
- assert.equal(path.dirname(fixture), path.resolve(os.tmpdir())); assert.ok(path.basename(fixture).startsWith('pk-product-import-ui-'));
101
- fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
102
- }
103
- }
104
- main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const http = require('node:http');
7
+ const { spawnSync } = require('node:child_process');
8
+ const { spawnServer } = require('./helpers/spawn-server');
9
+ const { findChrome } = require('./helpers/find-chrome');
10
+ const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
11
+ const { attachNetworkCapture } = require('./helpers/cdp-network-capture');
12
+
13
+ const ROOT = path.resolve(__dirname, '../..');
14
+ const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-product-import-ui-'));
15
+ const dataDir = path.join(fixture, 'data');
16
+ const repo = path.join(fixture, '中文 空格项目');
17
+ const knowledgeRoot = path.join(fixture, '知识目录');
18
+ const knowledge = path.join(knowledgeRoot, '项目知识');
19
+ const git = args => {
20
+ const result = spawnSync('git', args, { cwd: repo, windowsHide: true, encoding: 'utf8' });
21
+ assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
22
+ };
23
+ const gitIn = (dir, args) => {
24
+ const result = spawnSync('git', args, { cwd: dir, windowsHide: true, encoding: 'utf8' });
25
+ assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
26
+ };
27
+ async function main() {
28
+ fs.mkdirSync(repo); fs.mkdirSync(knowledgeRoot);
29
+ git(['init', '-q']); git(['config', 'user.email', 'ui-regression@local.invalid']); git(['config', 'user.name', 'UI Regression']);
30
+ fs.writeFileSync(path.join(repo, 'README.md'), '# UI regression baseline\n'); git(['add', '.']); git(['commit', '-qm', 'baseline']);
31
+ const baseline = git(['rev-parse', 'HEAD']);
32
+ // Offline modules are intentional: retain registration and report partial
33
+ // wiring, rather than fabricate successful links.
34
+ const offline = http.createServer((req, res) => { res.writeHead(503, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { code: 'MODULE_OFFLINE', message: '验收中的离线模块' } })); });
35
+ await new Promise(resolve => offline.listen(0, '127.0.0.1', resolve));
36
+ const offlineUrl = `http://127.0.0.1:${offline.address().port}`;
37
+ const port = 7960 + process.pid % 200;
38
+ const base = `http://127.0.0.1:${port}`;
39
+ const server = spawnServer({ root: ROOT, port, dataDir, tag: 'product-import-ui', analysisOwner: 'engine', extraEnv: {
40
+ KB_MODULES_REGISTER: '1', KB_TERMINAL_URL: offlineUrl, KB_VECTORHUB_URL: offlineUrl,
41
+ KB_EVENTBRIDGE_URL: offlineUrl, KB_KNOWLEDGE_ENGINE_URL: offlineUrl,
42
+ } });
43
+ let browser;
44
+ const api = async route => { const response = await fetch(base + route, { signal: AbortSignal.timeout(10000) }); assert.ok(response.ok, route); return response.json(); };
45
+ try {
46
+ await waitFor(async () => { try { return (await api('/api/health')).ok; } catch { return false; } }, 'isolated shell', 20000);
47
+ browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(fixture, 'browser'), url: base, width: 1920, height: 1080 });
48
+ await browser.setViewport(1920, 1080);
49
+ const capture = attachNetworkCapture(browser);
50
+ await capture.enable();
51
+ const run = expression => browser.evaluate(expression);
52
+ const click = selector => run(`document.querySelector(${JSON.stringify(selector)}).click()`);
53
+ const fill = (selector, value) => run(`(() => { const field=document.querySelector(${JSON.stringify(selector)}); field.value=${JSON.stringify(value)}; field.dispatchEvent(new Event('input',{bubbles:true})); })()`);
54
+ await waitFor(() => run("document.getElementById('homeContent').textContent.includes('第一个项目')"), 'real empty state');
55
+ for (const view of ['work', 'search', 'memory', 'projects', 'activity', 'home']) {
56
+ await click(`[data-view="${view}"]`);
57
+ assert.equal(await run(`document.getElementById('view-${view}').classList.contains('active')`), true);
58
+ assert.equal(await run('document.documentElement.scrollWidth > innerWidth'), false, `desktop overflow in ${view}`);
59
+ }
60
+ await click('#themeToggle'); assert.equal(await run('document.documentElement.dataset.theme'), 'dark');
61
+ await click('[data-view="projects"]'); await click('#createDomain');
62
+ // PRJ-001: an empty name is refused in the browser itself, so no request
63
+ // ever reaches the product and no domain is persisted.
64
+ await fill('#domainForm input[name="name"]', '');
65
+ await click('#domainForm button[type="submit"]');
66
+ await new Promise(resolve => setTimeout(resolve, 400));
67
+ assert.equal(await run("document.getElementById('domainDialog').open"), true, 'an empty domain name must not close the dialog');
68
+ assert.equal(await run("document.getElementById('domainForm').checkValidity()"), false, 'the domain form reports an empty name as invalid');
69
+ assert.equal(capture.find(entry => entry.method === 'POST' && entry.url.endsWith('/api/domains')).length, 0, 'an empty domain name must not reach the product');
70
+ assert.equal(((await api('/api/settings')).settings.brain.domains || []).length, 0, 'an empty domain name must not persist a domain');
71
+ await fill('#domainForm input[name="name"]', '回归领域'); await click('#domainForm button[type="submit"]');
72
+ await waitFor(() => run("!document.getElementById('domainDialog').open && document.getElementById('projectTree').textContent.includes('回归领域')"), 'domain persisted from UI');
73
+ const domainsOnDisk = JSON.parse(fs.readFileSync(path.join(dataDir, 'settings.json'), 'utf8')).brain.domains || [];
74
+ assert.ok(domainsOnDisk.some(domain => domain.name === '回归领域'), 'the domain is persisted on disk');
75
+ assert.equal(capture.find(entry => entry.method === 'POST' && entry.url.endsWith('/api/domains')).length, 1, 'only the named domain reached the product');
76
+ assert.equal((await api('/api/projects/aggregated')).projects.length, 0, 'creating a domain must not create a Git project');
77
+ assert.ok(!fs.existsSync(path.join(dataDir, 'projects.json')) || Object.keys(JSON.parse(fs.readFileSync(path.join(dataDir, 'projects.json'), 'utf8')).projects || {}).length === 0, 'the project registry stays empty after a domain is created');
78
+ await click('.view.active [data-action="import"]');
79
+ assert.equal(await run("document.getElementById('confirmImport').disabled"), true, 'the import stays disabled before a preflight is ready');
80
+ assert.equal(await run("document.getElementById('pickCode').hidden || document.getElementById('pickKnowledge').hidden"), false);
81
+ await fill('#importCode', repo); await fill('#importName', '回归项目'); await fill('#importKnowledge', knowledge);
82
+ const domainId = await run("[...document.getElementById('importDomain').options].find(o=>o.textContent==='回归领域').value");
83
+ await run(`document.getElementById('importDomain').value=${JSON.stringify(domainId)}`);
84
+ await waitFor(() => run("!document.getElementById('setupImportRoot').hidden"), 'root missing action');
85
+ assert.equal(await run("document.getElementById('confirmImport').disabled"), true, 'the import stays disabled while the knowledge root is missing');
86
+ await click('#setupImportRoot'); await fill('#knowledgeRoot', knowledgeRoot); await click('#saveBrainSettings');
87
+ await waitFor(() => run("document.getElementById('importDialog').open && !document.getElementById('confirmImport').disabled"), 'setup returns to ready import');
88
+ const values = await run("Object.fromEntries(['importCode','importKnowledge','importName','importDomain'].map(id=>[id,document.getElementById(id).value]))");
89
+ assert.deepEqual(values, { importCode: repo, importKnowledge: knowledge, importName: '回归项目', importDomain: domainId }, 'the original import inputs survive the knowledge-root round trip');
90
+ // PRJ-002/PRJ-003: the import id travels with the preflight and must
91
+ // survive the knowledge-root round trip unchanged.
92
+ const preflightRequest = capture.lastApi('POST', '/api/projects/preflight-import');
93
+ assert.ok(preflightRequest && preflightRequest.body && typeof preflightRequest.body.projectId === 'string' && preflightRequest.body.projectId, 'the preflight request carries the import id');
94
+ const preflightResponse = await capture.body(preflightRequest);
95
+ assert.ok(preflightResponse && preflightResponse.ready === true, 'the preflight that enables the import reports ready');
96
+ const racedHook = path.join(repo, '.git', 'hooks', 'post-commit');
97
+ fs.writeFileSync(racedHook, '#!/bin/sh\necho third-party fixture\n');
98
+ await click('#confirmImport');
99
+ await waitFor(() => run("document.getElementById('preflightStatus').textContent.includes('HOOK_CONFLICT') && document.getElementById('preflightStatus').textContent.includes('操作 ID:op-')"), 'durable import failure feedback');
100
+ await new Promise(resolve => setTimeout(resolve, 750));
101
+ assert.ok(await run("document.getElementById('preflightStatus').textContent.includes('HOOK_CONFLICT')"), 'failed import must not turn back into a passed preflight');
102
+ assert.equal((await api('/api/projects/aggregated')).projects.length, 0, 'the refused import registers no project');
103
+ assert.equal(fs.readFileSync(racedHook, 'utf8'), '#!/bin/sh\necho third-party fixture\n', 'the third-party Hook bytes are preserved');
104
+ const failureLogs = (await api('/api/logs?event=project.import.failed')).entries;
105
+ assert.equal(failureLogs.length, 1, 'the failed import is persisted as one failure record'); assert.equal(failureLogs[0].error.code, 'HOOK_CONFLICT');
106
+ assert.ok(await run(`document.getElementById('preflightStatus').textContent.includes(${JSON.stringify(failureLogs[0].operationId)})`), 'UI operation ID resolves to the persisted failure');
107
+ fs.unlinkSync(racedHook);
108
+ await click('#confirmImport');
109
+ await waitFor(() => run("!document.getElementById('importDialog').open && document.getElementById('projectDetail').textContent.includes('回归项目')"), 'import feedback', 30000);
110
+ const selected = await run("document.getElementById('projectSelect').value");
111
+ const project = (await api('/api/projects/aggregated')).projects.find(p => p.projectId === selected);
112
+ assert.ok(project && Object.values(project.modules).every(module => !module.registered));
113
+ assert.equal((await api('/api/projects/aggregated')).projects.length, 1, 'the knowledge-root round trip must not create a second project');
114
+ assert.equal(path.resolve(project.workspacePath), path.resolve(repo)); assert.equal(path.resolve(project.knowledgePath), path.resolve(knowledge));
115
+ assert.equal(project.domainId, domainId);
116
+ // PRJ-002: the four modules are reported one by one, each with its own
117
+ // status, both in the import result and in the registered project view.
118
+ const importRequest = capture.lastApi('POST', '/api/projects/import');
119
+ assert.ok(importRequest && importRequest.body && importRequest.body.projectId, 'the import request carries the import id');
120
+ assert.equal(importRequest.body.projectId, preflightRequest.body.projectId, 'the import id survives the knowledge-root round trip');
121
+ assert.equal(selected, importRequest.body.projectId, 'the registered project keeps the import id');
122
+ const importResponse = await capture.body(importRequest);
123
+ assert.ok(importResponse, 'the import response is readable');
124
+ assert.deepEqual(Object.keys(importResponse.modules).sort(), ['engine', 'eventBridge', 'terminal', 'vectorHub'], 'the import reports each of the four modules separately');
125
+ for (const moduleKey of ['engine', 'eventBridge', 'terminal', 'vectorHub']) {
126
+ assert.equal(typeof importResponse.modules[moduleKey].status, 'string', `${moduleKey} reports its own status`);
127
+ }
128
+ assert.deepEqual(Object.keys(project.modules).sort(), ['engine', 'eventBridge', 'terminal', 'vectorHub'], 'the project view reports each of the four modules separately');
129
+ for (const [moduleKey, module] of Object.entries(project.modules)) {
130
+ assert.equal(module.registered, false, `${moduleKey} is not registered against the offline module`);
131
+ assert.equal(typeof module.status, 'string', `${moduleKey} reports its own status`);
132
+ }
133
+ assert.ok(fs.existsSync(path.join(repo, '.git/hooks/post-commit')));
134
+ assert.ok(fs.existsSync(knowledge)); assert.equal(git(['rev-parse', 'HEAD']), baseline, 'import must not commit or analyze history');
135
+ // PRJ-002: the source history is left alone, and analysis starts from the
136
+ // imported HEAD instead of walking older commits.
137
+ assert.deepEqual(git(['log', '--format=%H']).split('\n').filter(Boolean), [baseline], 'import must not create or rewrite commits');
138
+ const importedProject = (await api(`/api/projects/${selected}`)).project;
139
+ assert.equal(importedProject.state.trackingStartCommit, baseline, 'analysis starts from the imported HEAD');
140
+ assert.ok(!fs.existsSync(path.join(dataDir, 'runtime', 'processed-commits', selected)), 'import must not analyze the existing history');
141
+ assert.match(await run("document.getElementById('toast').textContent"), /部分接线待恢复/);
142
+ await click('.view.active [data-action="import"]'); await fill('#importCode', repo); await fill('#importKnowledge', knowledge);
143
+ await waitFor(() => run("document.getElementById('preflightStatus').textContent.includes('此项目已导入')"), 'duplicate rejected');
144
+ assert.equal(await run("document.getElementById('confirmImport').disabled"), true);
145
+ assert.equal((await api('/api/projects/aggregated')).projects.length, 1);
146
+ // PRJ-012: the other half of the case — the import itself succeeds, and
147
+ // the registry read that follows it fails. The failure is injected at the
148
+ // transport level in the browser (the answer is really lost), so the
149
+ // product never sees a broken request and the page has to cope with a
150
+ // read it never got.
151
+ const repo2 = path.join(fixture, '第二个项目');
152
+ const knowledge2 = path.join(knowledgeRoot, '第二个项目知识');
153
+ fs.mkdirSync(repo2);
154
+ gitIn(repo2, ['init', '-q']); gitIn(repo2, ['config', 'user.email', 'ui-regression@local.invalid']); gitIn(repo2, ['config', 'user.name', 'UI Regression']);
155
+ fs.writeFileSync(path.join(repo2, 'README.md'), '# second project baseline\n'); gitIn(repo2, ['add', '.']); gitIn(repo2, ['commit', '-qm', 'baseline']);
156
+ const fetchFaults = { armed: false, failed: [] };
157
+ const onFetchMessage = raw => {
158
+ let message; try { message = JSON.parse(String(raw)); } catch { return; }
159
+ if (message.method !== 'Fetch.requestPaused') return;
160
+ const { requestId, request } = message.params;
161
+ if (fetchFaults.armed) {
162
+ fetchFaults.failed.push({ url: request.url, at: Date.now() });
163
+ browser.send('Fetch.failRequest', { requestId, errorReason: 'ConnectionFailed' }).catch(() => {});
164
+ return;
165
+ }
166
+ browser.send('Fetch.continueRequest', { requestId }).catch(() => {});
167
+ };
168
+ browser.socket.on('message', onFetchMessage);
169
+ await browser.send('Fetch.enable', { patterns: [{ urlPattern: '*projects/projection*' }] });
170
+ await run(`(() => { window.__toasts = []; const el = document.getElementById('toast'); new MutationObserver(() => { const text = (el.textContent || '').trim(); if (text && window.__toasts[window.__toasts.length - 1] !== text) window.__toasts.push(text); }).observe(el, { childList: true, characterData: true, subtree: true }); })()`);
171
+ await click('.view.active [data-action="import"]');
172
+ await fill('#importCode', repo2); await fill('#importName', '第二个项目'); await fill('#importKnowledge', knowledge2);
173
+ await waitFor(() => run("!document.getElementById('confirmImport').disabled"), 'the second preflight is ready', 20000);
174
+ assert.equal((await api('/api/projects/aggregated')).projects.length, 1, 'the second import has not registered anything yet');
175
+ const beforeSecondImport = capture.requests.length;
176
+ fetchFaults.armed = true;
177
+ await click('#confirmImport');
178
+ await waitFor(() => fetchFaults.failed.length >= 1, 'the registry read that follows the import fails', 20000);
179
+ await waitFor(() => run("!document.getElementById('importDialog').open"), 'the successful import closes the dialog', 20000);
180
+ await waitFor(async () => (await run('window.__toasts || []')).some(text => text.includes('部分状态暂未刷新')), 'the failed registry read is reported', 20000);
181
+ const requests = capture.requests;
182
+ const importIndex = requests.findIndex((entry, index) => index >= beforeSecondImport && entry.method === 'POST' && entry.url.endsWith('/api/projects/import'));
183
+ assert.ok(importIndex >= 0, 'the second import request is recorded');
184
+ assert.equal(requests[importIndex].status, 201, 'the import itself succeeded');
185
+ const readsAfterImport = requests.slice(importIndex + 1).filter(entry => entry.method === 'GET' && entry.url.includes('/api/projects/projection'));
186
+ assert.ok(readsAfterImport.length >= 1, 'the import is followed by a registry read');
187
+ assert.ok(readsAfterImport.every(entry => entry.status === null), 'every registry read after the import really failed at the transport level');
188
+ assert.ok(fetchFaults.failed.length >= 1, 'the browser really lost the registry read');
189
+ // The import created exactly one project; the failed read created nothing.
190
+ const registered = (await api('/api/projects/aggregated')).projects;
191
+ assert.equal(registered.length, 2, 'the successful import registers exactly one more project');
192
+ assert.ok(registered.some(project => project.name === '第二个项目'), 'the second project is registered');
193
+ // The page does not present the failed read as fresh state.
194
+ assert.equal(await run("document.querySelectorAll('#projectTree [data-select-project]').length"), 1, 'the failed read leaves the project tree on the previous state');
195
+ assert.equal(await run("document.getElementById('projectTree').textContent.includes('第二个项目')"), false, 'the failed read is not shown as a refreshed list');
196
+ // A later read succeeds: the same import is not registered a second time.
197
+ fetchFaults.armed = false;
198
+ const laterRead = () => capture.find(entry => entry.method === 'GET' && entry.url.includes('/api/projects/projection') && entry.status === 200 && requests.indexOf(entry) > importIndex).length > 0;
199
+ const readDeadline = Date.now() + 20000;
200
+ while (!laterRead() && Date.now() < readDeadline) {
201
+ await run("document.dispatchEvent(new Event('visibilitychange'))");
202
+ await new Promise(resolve => setTimeout(resolve, 300));
203
+ }
204
+ assert.ok(laterRead(), 'the later registry read succeeds');
205
+ await waitFor(() => run("document.getElementById('projectTree').textContent.includes('第二个项目')"), 'the refreshed project tree', 20000);
206
+ assert.equal(await run("document.querySelectorAll('#projectTree [data-select-project]').length"), 2, 'the retry reads the same two projects and creates no third');
207
+ assert.equal((await api('/api/projects/aggregated')).projects.length, 2, 'the product registry still holds exactly two projects');
208
+ assert.ok(fs.existsSync(knowledge2), 'the second project keeps its knowledge directory');
209
+ await browser.send('Fetch.disable');
210
+ assert.equal(browser.exceptions.length, 0, 'no uncaught product JavaScript exceptions');
211
+ console.log('product-import-ui-test PASS (1920×1080; real UI → registry/Hook/directories; offline module wiring remains partial)');
212
+ } finally {
213
+ await browser?.close(); await server.cleanup();
214
+ offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve));
215
+ assert.equal(path.dirname(fixture), path.resolve(os.tmpdir())); assert.ok(path.basename(fixture).startsWith('pk-product-import-ui-'));
216
+ fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
217
+ }
218
+ }
219
+ main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });