@sanqianx/project-knowledge 5.0.23 → 5.0.25

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 -1346
  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 +70 -67
  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,588 @@
1
+ 'use strict';
2
+ // P-WB-QA-INTEGRATION-5025 — WB-005 草稿与附件隔离的 canonical 复测入口。
3
+ //
4
+ // 本入口是已接受的 R5 探针(parallel-workbench-drafts-r5-probe.js,冻结
5
+ // 只读)在 integration-5025 独占前缀下的整理版:8 个子项的产品 expected
6
+ // 与断言语义逐条原样,变化只在工具链层面——
7
+ // 1. fixture/harness 换成 canonical 的 integration-5025 版:可移植临时根
8
+ // (PK_TEST_TEMP_ROOT → 仓库旁共享根 → TEMP/TMP/TMPDIR → os.tmpdir,
9
+ // 全部拒绝落在仓库内)、完整 env 白名单(含 APPDATA/LOCALAPPDATA/
10
+ // CODEX_HOME)、Chrome>=136 默认目录脚手架,全在每次运行独立的
11
+ // owner root 下;不硬编码任何盘符或用户目录;
12
+ // 2. 模块身份是独立链:期望版本默认取根 package.json 的精确依赖约束
13
+ // (claude-ai-workbench: npm:@sanqianx/claude-ai-workbench@<version>),
14
+ // 与被测模块自报版本比对,绝不"自读自比";候选发布可用
15
+ // PK_WB_EXPECTED_VERSION / PK_WB_MODULE_ROOT 显式指定新版本/新根;
16
+ // 3. 真实 profile 来源整体不带入:宿主用产品自己的 ProfileStore 在隔离
17
+ // dataDir 播种纯虚构 profile(i5025fake / i5025-fake-provider /
18
+ // https://i5025-fake.invalid —— RFC 2606 保留域)与显式假 Key;
19
+ // 4. 退出纪律、整探针 watchdog、每步预算、状态驱动侧栏展开全部沿用
20
+ // R5 已接受的根修实现。
21
+ //
22
+ // 退出码:0 = 全部证明;3 = 诚实 BLOCKED/超时;1 = 脚本错误。
23
+ const fs = require('node:fs');
24
+ const path = require('node:path');
25
+ const crypto = require('node:crypto');
26
+ const launcher = require('./helpers/parallel-workbench-integration-5025-launcher');
27
+ const fixture = require('./helpers/parallel-workbench-integration-5025-fixture');
28
+ const harness = require('./helpers/parallel-workbench-integration-5025-harness');
29
+
30
+ const DRAFT_A = '1111';
31
+ const DRAFT_B = '2222';
32
+ const DRAFT_S2 = '1111-two';
33
+ const FAKE_PROFILE_ID = 'i5025fake';
34
+ const FAKE_PROFILE_NAME = 'I5025-Fake';
35
+ const FAKE_MODEL = 'i5025-fake-model';
36
+ const FAKE_BASE_URL = 'https://i5025-fake.invalid/anthropic';
37
+ const FAKE_KEY = 'i5025-fake-key-never-dial';
38
+ // 候选发布支持显式期望版本;默认独立取根 package 的精确依赖约束。
39
+ const EXPLICIT_EXPECTED_VERSION = String(process.env.PK_WB_EXPECTED_VERSION || '').trim() || null;
40
+
41
+ const KB_ROOT = path.resolve(__dirname, '..', '..');
42
+ const EVIDENCE_DIR = path.join(KB_ROOT, 'qa-autopilot', 'artifacts', 'parallel-20261023', 'workbench-integration-5025');
43
+ const PROBE_TIMEOUT_MS = Number(process.env.PK_WB_INTEGRATION_5025_TIMEOUT_MS || 450000);
44
+
45
+ const results = [];
46
+ const stepTimings = [];
47
+ function record(caseId, status, detail) {
48
+ results.push({ caseId, status, detail });
49
+ console.log(`CASE ${caseId} ${status} ${JSON.stringify(detail).slice(0, 300)}`);
50
+ }
51
+ function assertTrue(condition, message) { if (!condition) throw new Error(message); }
52
+
53
+ function isInside(candidate, parent) {
54
+ const relative = path.relative(parent, candidate);
55
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
56
+ }
57
+
58
+ // 每步骤独立预算:waitUntil 自身的超时管“条件迟迟不满足”,预算管
59
+ // “evaluate/fetch 本身挂死”这类 waitUntil 管不到的等待。
60
+ async function withBudget(label, budgetMs, fn) {
61
+ const started = Date.now();
62
+ let timer = null;
63
+ try {
64
+ const value = await Promise.race([
65
+ fn(),
66
+ new Promise((_, reject) => {
67
+ timer = setTimeout(() => reject(new Error(`STEP TIMEOUT: ${label} exceeded its ${budgetMs}ms budget`)), budgetMs);
68
+ }),
69
+ ]);
70
+ stepTimings.push({ step: label, ms: Date.now() - started, outcome: 'ok' });
71
+ return value;
72
+ } catch (error) {
73
+ stepTimings.push({ step: label, ms: Date.now() - started, outcome: String(error && error.message || error).slice(0, 200) });
74
+ throw error;
75
+ } finally {
76
+ if (timer) clearTimeout(timer);
77
+ }
78
+ }
79
+
80
+ function sha256Text(text) { return crypto.createHash('sha256').update(text, 'utf8').digest('hex'); }
81
+
82
+ async function waitSidebarProjects(browser, count) {
83
+ return harness.waitUntil(async () => {
84
+ const ids = await browser.evaluate("Array.from(document.querySelectorAll('#project-list .proj')).map(node => node.dataset.project)");
85
+ return ids.length === count ? ids : null;
86
+ }, `the sidebar to list ${count} projects`, 30000);
87
+ }
88
+
89
+ // 等产品自己的 input 处理器把草稿写进它自己的 localStorage 键(只读旁观,
90
+ // 不代写):证明持久化发生在产品侧,后续的切换/刷新/重启恢复才是产品行为。
91
+ async function waitStorageDraft(browser, projectId, sessionId, text) {
92
+ const key = JSON.stringify([projectId, sessionId || null]);
93
+ return harness.waitUntil(async () => {
94
+ const storage = await harness.readDraftStorage(browser);
95
+ return storage.parsed && Object.prototype.hasOwnProperty.call(storage.parsed, key) && storage.parsed[key] === text;
96
+ }, `the product to persist its own draft ${key} as ${JSON.stringify(text)}`, 15000);
97
+ }
98
+
99
+ // 挂起/失败归因快照:网络(宿主健康)、DOM(页面就绪度)、服务(进程存活)。
100
+ async function diagnose(host, browser) {
101
+ const out = { at: new Date().toISOString() };
102
+ if (host && host.base) {
103
+ try {
104
+ const health = await harness.getJson(`${host.base}${harness.API_BASE}/health`);
105
+ out.hostHealth = { status: health.status, ok: health.ok, body: health.body };
106
+ } catch (error) { out.hostHealth = { error: String(error && error.message || error).slice(0, 160) }; }
107
+ if (host.child && host.child.child) {
108
+ out.hostPid = host.child.child.pid;
109
+ out.hostPidAlive = harness.listProcesses().some(entry => entry.pid === host.child.child.pid);
110
+ }
111
+ }
112
+ if (browser) {
113
+ try {
114
+ out.page = await browser.evaluate("({ readyState: document.readyState, hasInput: !!document.getElementById('chat-input'), stateDefined: typeof state !== 'undefined' && !!state, sidebarProjects: document.querySelectorAll('#project-list .proj').length, composer: (document.getElementById('chat-input') || {}).value || '' })");
115
+ } catch (error) { out.page = { error: String(error && error.message || error).slice(0, 200) }; }
116
+ }
117
+ return out;
118
+ }
119
+
120
+ (async () => {
121
+ const startedAt = new Date().toISOString();
122
+ fs.mkdirSync(path.join(EVIDENCE_DIR, 'evidence'), { recursive: true });
123
+
124
+ // 整探针 watchdog:非 unref —— 它既保证“永不无限等待”(到点强制退),
125
+ // 也保证被挂住的 await 之外事件循环仍有心跳可触发它。
126
+ const watchdog = setTimeout(() => {
127
+ const target = path.join(EVIDENCE_DIR, 'evidence', 'drafts-5025-watchdog.json');
128
+ try {
129
+ fs.writeFileSync(target, `${JSON.stringify({
130
+ probe: 'parallel-workbench-integration-5025-drafts-probe', firedAt: new Date().toISOString(), startedAt,
131
+ verdict: 'WATCHDOG TIMEOUT', exitCode: 3, steps: stepTimings, cases: results,
132
+ }, null, 2)}\n`);
133
+ } catch { /* best effort */ }
134
+ console.log(`WATCHDOG TIMEOUT after ${PROBE_TIMEOUT_MS}ms — checkpoint at ${target}; forcing exit 3`);
135
+ process.exit(3);
136
+ }, PROBE_TIMEOUT_MS);
137
+
138
+ // 统一入口纪律(P-WB-CANONICAL-R2):先门检、再真实 env 只读发现(Chrome
139
+ // 走 LOCALAPPDATA,模块走 APPDATA / 显式 PK_WB_MODULE_ROOT),然后本进程
140
+ // 套完整隔离白名单;捕获值以显式 override 贯穿(含安装包身份)。真实
141
+ // profile 目录只取路径字符串用于 guard,本入口无任何真实 profile 读取。
142
+ const boot = launcher.boot({ prefix: 'parallel-wb-integration-5025', paid: false, needsRealProfile: false });
143
+ const chromePath = boot.discovery.chrome;
144
+ const moduleRoot = boot.discovery.moduleRoot;
145
+ const realProfileDir = fixture.realWorkbenchProfileDir();
146
+ assertTrue(chromePath, 'no real Chrome was found with the real environment');
147
+ assertTrue(moduleRoot, 'no claude-ai-workbench module copy was found with the real environment');
148
+
149
+ // 独立身份链:期望版本来自根 package 的精确依赖约束(或显式覆盖),
150
+ // 与被测模块自报版本比对。
151
+ const identityCheck = fixture.verifyModuleIdentity({ moduleRoot, expectedVersion: EXPLICIT_EXPECTED_VERSION || undefined });
152
+ assertTrue(identityCheck.ok, `the hosted module is ${JSON.stringify(identityCheck.actual)}, the independent chain requires ${JSON.stringify(identityCheck.expected)}`);
153
+
154
+ // 隔离布局 + 探针自身的完整隔离白名单(宿主另有同一白名单的显式副本)。
155
+ const layout = boot.layout;
156
+ const envApplied = boot.envApplied;
157
+ const ambientClean = ![...fixture.AMBIENT_MODULE_URLS, ...fixture.AMBIENT_MODEL_ENV].some(key => process.env[key] !== undefined);
158
+ const pathKeyValues = {};
159
+ for (const key of fixture.ISOLATION_PATH_KEYS) {
160
+ const value = path.resolve(process.env[key]);
161
+ pathKeyValues[key] = value;
162
+ assertTrue(isInside(value, layout.root), `the isolated ${key} must live inside this run's layout root, saw ${value}`);
163
+ assertTrue(!isInside(value, fixture.REAL_HOME), `the isolated ${key} must not live inside the real home, saw ${value}`);
164
+ assertTrue(!value.includes(realProfileDir) && !realProfileDir.includes(value), `the isolated ${key} must not reference the real profile directory`);
165
+ }
166
+
167
+ let host = null;
168
+ let browser = null;
169
+ let opened = null;
170
+ let identity = null;
171
+ let sceneKept = null;
172
+ let isolationDetail = null;
173
+ let bDetail = null;
174
+ let cDetail = null;
175
+ let dDetail = null;
176
+ let eDetail = null;
177
+ let fDetail = null;
178
+ let gDetail = null;
179
+ let hDetail = null;
180
+ let servedIntegrity = null;
181
+
182
+ try {
183
+ host = await withBudget('start-host', 90000, () => harness.startHost({ layout, moduleRoot, prefix: 'parallel-wb-integration-5025' }));
184
+ opened = await withBudget('open-browser', 90000, () => harness.openBrowser(host.base, host.layout, { chrome: chromePath }));
185
+ browser = opened.browser;
186
+ identity = harness.moduleIdentity(host.moduleRoot);
187
+ const identityAfterStart = fixture.verifyModuleIdentity({ moduleRoot: host.moduleRoot, expectedVersion: EXPLICIT_EXPECTED_VERSION || undefined });
188
+ assertTrue(identityAfterStart.ok, `the hosted module is ${JSON.stringify(identityAfterStart.actual)}, the independent chain requires ${JSON.stringify(identityAfterStart.expected)}`);
189
+
190
+ // --- a. 真实宿主/浏览器身份 + served/pinned 哈希 + 完整隔离前提 --------
191
+ const health = await harness.getJson(`${host.base}${harness.API_BASE}/health`);
192
+ assertTrue(health.ok && health.body && health.body.status === 'ok', 'the isolated host is not healthy');
193
+ const servedFiles = {};
194
+ for (const rel of ['app.js', 'index.html']) {
195
+ const response = await fetch(`${host.base}/agent-terminal/${rel}`, { signal: AbortSignal.timeout(10000) });
196
+ const text = await response.text();
197
+ servedFiles[rel] = {
198
+ httpStatus: response.status,
199
+ servedSha256: sha256Text(text),
200
+ diskSha256: harness.sha256File(path.join(host.moduleRoot, 'apps', 'agent-terminal', rel)),
201
+ };
202
+ }
203
+ const servedMatches = Object.values(servedFiles).every(file => file.httpStatus === 200 && file.servedSha256 && file.servedSha256 === file.diskSha256);
204
+ assertTrue(servedMatches, 'the served agent-terminal UI is not byte-identical to the hosted module files');
205
+ servedIntegrity = { checked: Object.keys(servedFiles), servedEqualsDisk: servedMatches, files: servedFiles };
206
+
207
+ // 虚构 profile 是唯一被服务的 profile;假 Key 从不出现在公开列表里。
208
+ const profilesResp = await harness.getJson(`${host.base}${harness.API_BASE}/profiles`);
209
+ const listed = Array.isArray(profilesResp.body && profilesResp.body.profiles) ? profilesResp.body.profiles : [];
210
+ assertTrue(listed.length === 1, `the isolated store must serve exactly the fictional profile, saw ${JSON.stringify(listed.map(item => item.id))}`);
211
+ const fakeListed = listed[0];
212
+ assertTrue(fakeListed.id === FAKE_PROFILE_ID && fakeListed.name === FAKE_PROFILE_NAME, 'the served profile is not the fictional one');
213
+ assertTrue(fakeListed.provider === 'i5025-fake-provider' && fakeListed.baseUrl === FAKE_BASE_URL, 'the fictional profile was altered on the way out');
214
+ assertTrue(!JSON.stringify(listed).includes(FAKE_KEY), 'the fake key leaked through the public profile listing');
215
+
216
+ // 隔离 dataDir 里的 store 文件只含虚构条目(假 Key 是显式假值,可入证据)。
217
+ const profilesRaw = JSON.parse(fs.readFileSync(path.join(layout.dataDir, 'profiles.v2.json'), 'utf8'));
218
+ const credentialsRaw = JSON.parse(fs.readFileSync(path.join(layout.dataDir, 'credentials.v1.json'), 'utf8'));
219
+ assertTrue(Array.isArray(profilesRaw.profiles) && profilesRaw.profiles.length === 1 && profilesRaw.profiles[0].id === FAKE_PROFILE_ID, 'the isolated profile file holds more than the fictional profile');
220
+ const credentialKeys = Object.keys(credentialsRaw.credentials || {});
221
+ assertTrue(credentialKeys.length === 1 && credentialKeys[0] === `credential:${FAKE_PROFILE_ID}`, 'the isolated credential file holds more than the fake credential');
222
+ assertTrue(credentialsRaw.credentials[credentialKeys[0]] === FAKE_KEY, 'the isolated credential is not the explicit fake key');
223
+
224
+ isolationDetail = {
225
+ envWhitelistApplied: envApplied,
226
+ pathKeysResolvedInsideLayoutRoot: pathKeyValues,
227
+ ambientCredentialVarsPresentAfterSanitize: !ambientClean,
228
+ realProfileSource: 'absent by construction — the integration-5025 fixture has no read-only facade, no PK_WB_REAL_PROFILE_DIR and no digest machinery',
229
+ fakeProfile: {
230
+ seededThrough: "the product's own ProfileStore.save/setCredential on the isolated dataDir",
231
+ profile: profilesRaw.profiles[0],
232
+ credentialKeys,
233
+ baseUrlUnroutable: 'i5025-fake.invalid is a reserved RFC 2606 TLD — it can never resolve to a provider',
234
+ },
235
+ browser: 'real Chrome via CDP (headless), isolated user-data-dir below the same isolated layout',
236
+ chrome: opened.chrome,
237
+ };
238
+
239
+ const projectA = await withBudget('create-project-a', 60000, () => harness.ensureProject(browser, path.join(layout.root, 'i5025-project-a'), { name: 'i5025-project-a' }));
240
+ const projectB = await withBudget('create-project-b', 60000, () => harness.ensureProject(browser, path.join(layout.root, 'i5025-project-b'), { name: 'i5025-project-b' }));
241
+ assertTrue(projectA.created === true && projectB.created === true, 'the two projects were not created through the product form');
242
+ assertTrue(projectA.projectId && projectB.projectId && projectA.projectId !== projectB.projectId, 'the two projects do not have distinct ids');
243
+ record('WB005-5025.a-real-browser-host-and-isolation', 'PASS', {
244
+ module: identity,
245
+ identityChain: identityAfterStart,
246
+ servedUiIntegrity: servedIntegrity,
247
+ host: { mode: 'standalone (fictional profile)', pid: host.child.child.pid, port: host.port, dataDir: layout.dataDir, workspace: layout.workspace, tempRoot: path.dirname(layout.root) },
248
+ isolation: isolationDetail,
249
+ projects: [
250
+ { id: projectA.projectId, name: projectA.name, createdThroughTheProductForm: true },
251
+ { id: projectB.projectId, name: projectB.name, createdThroughTheProductForm: true },
252
+ ],
253
+ health: health.body,
254
+ });
255
+
256
+ // 只选模型让产品具备建会话条件;从不发送消息,见 g/h 项证据。
257
+ await withBudget('select-model', 60000, () => harness.selectModel(browser, FAKE_PROFILE_NAME, FAKE_MODEL));
258
+
259
+ // --- b. 项目级草稿:A=1111、B=2222,产品侧边栏切换各自恢复 ------------
260
+ await withBudget('project-drafts', 90000, async () => {
261
+ await harness.selectProject(browser, projectA.projectId);
262
+ await harness.typeText(browser, DRAFT_A);
263
+ await waitStorageDraft(browser, projectA.projectId, null, DRAFT_A);
264
+ await harness.selectProject(browser, projectB.projectId);
265
+ const firstEntryB = await harness.readState(browser);
266
+ assertTrue(firstEntryB.inputValue === '', `project B must start with an empty composer, saw ${JSON.stringify(firstEntryB.inputValue)}`);
267
+ await harness.typeText(browser, DRAFT_B);
268
+ await waitStorageDraft(browser, projectB.projectId, null, DRAFT_B);
269
+ await harness.selectProject(browser, projectA.projectId);
270
+ const backInA = await harness.readState(browser);
271
+ assertTrue(backInA.inputValue === DRAFT_A, `project A did not restore its draft: ${JSON.stringify(backInA.inputValue)}`);
272
+ await harness.selectProject(browser, projectB.projectId);
273
+ const backInB = await harness.readState(browser);
274
+ assertTrue(backInB.inputValue === DRAFT_B, `project B did not restore its draft: ${JSON.stringify(backInB.inputValue)}`);
275
+ assertTrue(!backInA.inputValue.includes(DRAFT_B) && !backInB.inputValue.includes(DRAFT_A), 'a project draft leaked across projects');
276
+ bDetail = {
277
+ switch: "the product's own sidebar project head (.proj-item), a real click",
278
+ projectA: { id: projectA.projectId, draft: DRAFT_A, composerAfterSwitchBack: backInA.inputValue },
279
+ projectB: { id: projectB.projectId, draft: DRAFT_B, composerOnFirstEntry: firstEntryB.inputValue, composerAfterSwitchBack: backInB.inputValue },
280
+ crossLeak: backInA.inputValue.includes(DRAFT_B) || backInB.inputValue.includes(DRAFT_A),
281
+ persistedByTheProduct: true,
282
+ };
283
+ });
284
+ record('WB005-5025.b-project-drafts-are-isolated', 'PASS', bDetail);
285
+
286
+ // --- c. 真实浏览器刷新后项目级草稿恢复(此时还没有任何会话)-----------
287
+ await withBudget('refresh-project-drafts', 60000, async () => {
288
+ await harness.reloadPage(browser);
289
+ await waitSidebarProjects(browser, 2);
290
+ await harness.selectProject(browser, projectA.projectId);
291
+ const aAfter = await harness.readState(browser);
292
+ assertTrue(aAfter.inputValue === DRAFT_A, `project A draft lost after refresh: ${JSON.stringify(aAfter.inputValue)}`);
293
+ await harness.selectProject(browser, projectB.projectId);
294
+ const bAfter = await harness.readState(browser);
295
+ assertTrue(bAfter.inputValue === DRAFT_B, `project B draft lost after refresh: ${JSON.stringify(bAfter.inputValue)}`);
296
+ cDetail = {
297
+ refresh: 'a real Page.reload; the sidebar re-rendered both projects before any click',
298
+ composerInProjectAAfterRefresh: aAfter.inputValue,
299
+ composerInProjectBAfterRefresh: bAfter.inputValue,
300
+ storageKeysAfterRefresh: (await harness.readDraftStorage(browser)).keys,
301
+ };
302
+ });
303
+ record('WB005-5025.c-drafts-survive-a-real-browser-refresh', 'PASS', cDetail);
304
+
305
+ // --- d. 同一项目两个会话的草稿隔离 -------------------------------------
306
+ let sessionS1 = null;
307
+ let sessionS2 = null;
308
+ await withBudget('session-drafts', 120000, async () => {
309
+ await harness.selectProject(browser, projectA.projectId);
310
+ await harness.clickNewSession(browser);
311
+ const s1State = await harness.waitForState(browser, state => state.product && state.product.activeSessionId, 'the first session of project A', 30000);
312
+ sessionS1 = s1State.product.activeSessionId;
313
+ // 首会话创建时产品把项目级草稿 1111 迁入该会话(app.js newSession)。
314
+ const inS1 = await harness.readState(browser);
315
+ assertTrue(inS1.inputValue === DRAFT_A, `the project draft did not migrate into the first session: ${JSON.stringify(inS1.inputValue)}`);
316
+ const tombstone = await harness.readDraftStorage(browser);
317
+ const tombstoneKey = JSON.stringify([projectA.projectId, null]);
318
+ assertTrue(tombstone.parsed && tombstone.parsed[tombstoneKey] === '', 'the project-level entry must be an empty migration tombstone after the first session');
319
+
320
+ await harness.clickNewSession(browser);
321
+ const s2State = await harness.waitForState(browser, state => state.product && state.product.activeSessionId && state.product.activeSessionId !== sessionS1, 'the second session of project A', 30000);
322
+ sessionS2 = s2State.product.activeSessionId;
323
+ const s2Fresh = await harness.readState(browser);
324
+ assertTrue(s2Fresh.inputValue === '', `session two must start empty, saw ${JSON.stringify(s2Fresh.inputValue)}`);
325
+ await harness.typeText(browser, DRAFT_S2);
326
+ await waitStorageDraft(browser, projectA.projectId, sessionS2, DRAFT_S2);
327
+
328
+ await harness.openSessionRow(browser, projectA.projectId, sessionS1);
329
+ const backInS1 = await harness.readState(browser);
330
+ assertTrue(backInS1.inputValue === DRAFT_A, `session one draft not restored: ${JSON.stringify(backInS1.inputValue)}`);
331
+ assertTrue(!backInS1.inputValue.includes(DRAFT_S2), 'session two draft leaked into session one');
332
+ await harness.openSessionRow(browser, projectA.projectId, sessionS2);
333
+ const backInS2 = await harness.readState(browser);
334
+ assertTrue(backInS2.inputValue === DRAFT_S2, `session two draft not restored: ${JSON.stringify(backInS2.inputValue)}`);
335
+ assertTrue(backInS2.inputValue !== DRAFT_A, 'session two must not hold session one draft (exact equality)');
336
+ dDetail = {
337
+ switch: "the product's own sidebar session row (.session), real clicks",
338
+ projectId: projectA.projectId,
339
+ migrationIntoFirstSession: { sessionId: sessionS1, composerAfterCreate: inS1.inputValue, projectLevelTombstone: true },
340
+ sessionOne: { sessionId: sessionS1, draft: DRAFT_A, composerAfterReopen: backInS1.inputValue },
341
+ sessionTwo: { sessionId: sessionS2, draft: DRAFT_S2, composerOnCreate: s2Fresh.inputValue, composerAfterReopen: backInS2.inputValue },
342
+ sessionOneDraftLeakedIntoSessionTwo: s2Fresh.inputValue !== '',
343
+ sessionTwoDraftLeakedIntoSessionOne: backInS1.inputValue === DRAFT_S2,
344
+ };
345
+ });
346
+ record('WB005-5025.d-session-drafts-are-isolated', 'PASS', dDetail);
347
+
348
+ // --- e. 附件生命周期与跨项目隔离 ---------------------------------------
349
+ await withBudget('attachments', 120000, async () => {
350
+ await harness.openSessionRow(browser, projectA.projectId, sessionS1);
351
+ const tokenA = `I5025-ATTACH-A-${crypto.randomBytes(4).toString('hex')}`;
352
+ const contentA = `${tokenA}\nP-WB-QA-INTEGRATION-5025 attachment of project A session one\n`;
353
+ await harness.attachFile(browser, 'i5025-note.txt', contentA);
354
+ const s1WithAttachment = await harness.readState(browser);
355
+ assertTrue(s1WithAttachment.attachments.length === 1 && s1WithAttachment.attachments[0].includes('i5025-note.txt'), 'the attachment did not reach the tray of session one');
356
+ assertTrue(s1WithAttachment.product.attachmentCount === 1, 'the product did not count the attachment');
357
+ const base64A = Buffer.from(contentA, 'utf8').toString('base64');
358
+ const storageWithA = await harness.readDraftStorage(browser);
359
+ const bytesAInStorage = storageWithA.raw.includes(tokenA) || storageWithA.raw.includes(base64A);
360
+
361
+ await harness.selectProject(browser, projectB.projectId);
362
+ const trayInB = await harness.readState(browser);
363
+ assertTrue(trayInB.attachments.length === 0 && trayInB.product.attachmentCount === 0, "A's attachment leaked into project B");
364
+
365
+ const tokenB = `I5025-ATTACH-B-${crypto.randomBytes(4).toString('hex')}`;
366
+ const contentB = `${tokenB}\nP-WB-QA-INTEGRATION-5025 attachment of project B\n`;
367
+ await harness.attachFile(browser, 'i5025-b.txt', contentB);
368
+ const bWithAttachment = await harness.readState(browser);
369
+ assertTrue(bWithAttachment.attachments.length === 1 && bWithAttachment.attachments[0].includes('i5025-b.txt'), 'the attachment of project B did not reach its tray');
370
+ const base64B = Buffer.from(contentB, 'utf8').toString('base64');
371
+ const storageWithB = await harness.readDraftStorage(browser);
372
+ const bytesBInStorage = storageWithB.raw.includes(tokenB) || storageWithB.raw.includes(base64B);
373
+ assertTrue(!bytesAInStorage && !bytesBInStorage, 'attachment bytes entered the browser storage');
374
+
375
+ await harness.openSessionRow(browser, projectA.projectId, sessionS1);
376
+ const backInS1 = await harness.readState(browser);
377
+ assertTrue(backInS1.attachments.length === 1 && backInS1.attachments[0].includes('i5025-note.txt'), "session one's attachment did not come back");
378
+ assertTrue(!backInS1.attachments.join(' ').includes('i5025-b.txt'), "B's attachment leaked into A's session");
379
+ eDetail = {
380
+ attach: "the product's own <input type=\"file\"> change handler (a real File into the real input)",
381
+ sessionOne: { sessionId: sessionS1, file: 'i5025-note.txt', tray: s1WithAttachment.attachments, productAttachmentCount: s1WithAttachment.product.attachmentCount, trayAfterReturn: backInS1.attachments },
382
+ projectB: { file: 'i5025-b.txt', trayWhenInA: trayInB.attachments, ownTray: bWithAttachment.attachments },
383
+ attachmentBytesInBrowserStorage: bytesAInStorage || bytesBInStorage,
384
+ storageKeys: storageWithB.keys,
385
+ contract: 'app.js writeDraft(): only the text is persisted, attachment bytes stay in page memory',
386
+ };
387
+ });
388
+ record('WB005-5025.e-attachment-lifecycle-and-isolation', 'PASS', eDetail);
389
+
390
+ // --- f. 只重启本用例自己的隔离宿主(同布局/同端口/同浏览器 profile)----
391
+ await withBudget('own-host-restart', 180000, async () => {
392
+ const oldPid = host.child.child.pid;
393
+ const oldPort = host.port;
394
+ await harness.stopHost(host);
395
+ const exitInfo = await host.child.exited;
396
+ const oldGone = !harness.listProcesses().some(entry => entry.pid === oldPid);
397
+ assertTrue(oldGone && Boolean(exitInfo), 'the own host did not really exit');
398
+
399
+ const restarted = await harness.startHost({ layout: host.layout, port: oldPort, moduleRoot: host.moduleRoot });
400
+ host = restarted;
401
+ const identityAfter = harness.moduleIdentity(restarted.moduleRoot);
402
+ assertTrue(identityAfter.version === identity.version && identityAfter.uiFiles['apps/agent-terminal/app.js'] === identity.uiFiles['apps/agent-terminal/app.js'], 'the restarted host serves different UI bytes');
403
+ const healthAfter = await harness.getJson(`${restarted.base}${harness.API_BASE}/health`);
404
+ assertTrue(healthAfter.ok && healthAfter.body && healthAfter.body.status === 'ok', 'the restarted host is not healthy');
405
+
406
+ // 隔离 store 的虚构 profile 跨重启仍在且仍唯一(同数据目录)。
407
+ const profilesAfter = await harness.getJson(`${restarted.base}${harness.API_BASE}/profiles`);
408
+ const listedAfter = Array.isArray(profilesAfter.body && profilesAfter.body.profiles) ? profilesAfter.body.profiles : [];
409
+ assertTrue(listedAfter.length === 1 && listedAfter[0].id === FAKE_PROFILE_ID, 'the fictional profile did not survive the own-host restart as the only profile');
410
+
411
+ await harness.reloadPage(browser);
412
+ await waitSidebarProjects(browser, 2);
413
+ const stateAfterRestart = await harness.waitForState(browser, state => state.product && state.product.projects.length === 2, 'both projects after the restart', 30000);
414
+
415
+ await harness.selectProject(browser, projectB.projectId);
416
+ const bAfter = await harness.readState(browser);
417
+ assertTrue(bAfter.inputValue === DRAFT_B, `project B draft lost after the own-host restart: ${JSON.stringify(bAfter.inputValue)}`);
418
+ assertTrue(bAfter.attachments.length === 0, 'a memory-backed attachment survived the restart or leaked into B');
419
+
420
+ await harness.selectProject(browser, projectA.projectId);
421
+ const aProjectAfter = await harness.readState(browser);
422
+ assertTrue(aProjectAfter.inputValue === '', `project A project-level entry must stay the migration tombstone, saw ${JSON.stringify(aProjectAfter.inputValue)}`);
423
+ await harness.openSessionRow(browser, projectA.projectId, sessionS1);
424
+ const s1After = await harness.readState(browser);
425
+ assertTrue(s1After.inputValue === DRAFT_A, `session one draft lost after the restart: ${JSON.stringify(s1After.inputValue)}`);
426
+ await harness.openSessionRow(browser, projectA.projectId, sessionS2);
427
+ const s2After = await harness.readState(browser);
428
+ assertTrue(s2After.inputValue === DRAFT_S2, `session two draft lost after the restart: ${JSON.stringify(s2After.inputValue)}`);
429
+
430
+ const storageFinal = await harness.readDraftStorage(browser);
431
+ fDetail = {
432
+ restart: "only this case's own isolated host child was stopped and restarted (same layout, same port, same browser profile)",
433
+ oldHostPid: oldPid, oldHostExited: true, oldHostProcessStillListed: !oldGone, oldHostExitCode: exitInfo && exitInfo.code,
434
+ newHostPid: restarted.child.child.pid, portKept: oldPort === restarted.port,
435
+ healthAfterRestart: healthAfter.body,
436
+ fakeProfileAfterRestart: { soleProfile: listedAfter[0].id, model: listedAfter[0].models && listedAfter[0].models.default },
437
+ projectsAfterRestart: stateAfterRestart.product.projects.map(project => ({ id: project.id, name: project.name })),
438
+ composerInProjectBAfterRestart: bAfter.inputValue,
439
+ attachmentTrayInProjectBAfterRestart: bAfter.attachments,
440
+ projectAProjectLevelAfterRestart: aProjectAfter.inputValue,
441
+ projectAProjectLevelNote: 'the empty value is the migration tombstone the product itself wrote; the draft lives in session one',
442
+ composerInSessionOneAfterRestart: s1After.inputValue,
443
+ composerInSessionTwoAfterRestart: s2After.inputValue,
444
+ storageKeysAfterRestart: storageFinal.keys,
445
+ };
446
+ });
447
+ record('WB005-5025.f-own-host-restart-keeps-projects-and-drafts', 'PASS', fDetail);
448
+
449
+ // --- g. 全程零模型请求 -------------------------------------------------
450
+ await withBudget('no-model-request', 30000, async () => {
451
+ const events = await harness.recordedEvents(browser);
452
+ const modelEvents = events.filter(event => event.type === 'claude/result' || event.type === 'claude/init' || event.type === 'claude/user-prompt');
453
+ assertTrue(modelEvents.length === 0, `the draft case triggered model stream events: ${JSON.stringify(modelEvents.map(event => event.type))}`);
454
+ gDetail = { recordedStreamEvents: events.map(event => event.type), modelStreamEvents: [], draftsNeverSentToAnyModel: [DRAFT_A, DRAFT_B, DRAFT_S2], paidTurns: 0 };
455
+ });
456
+ record('WB005-5025.g-no-model-request-was-made', 'PASS', gDetail);
457
+ } catch (error) {
458
+ const diagnosis = await diagnose(host, browser).catch(() => null);
459
+ record('WB005-5025.runtime', 'BLOCKED', { error: String(error && error.message || error), diagnosis, stepsSoFar: stepTimings.slice() });
460
+ sceneKept = layout.root;
461
+ } finally {
462
+ // 先关浏览器(内部会杀 Chrome 树并清 profile),再显式等 CDP websocket
463
+ // 的 close 事件(至多 1.5s)——长命 socket 拖住事件循环是旧三挂的根因
464
+ // 类别;随后才停自有宿主。
465
+ if (browser) {
466
+ try { await browser.close(); } catch { /* disposable */ }
467
+ try {
468
+ await Promise.race([
469
+ new Promise(resolve => { try { browser.socket.once('close', resolve); } catch { resolve(); } }),
470
+ new Promise(resolve => setTimeout(resolve, 1500)),
471
+ ]);
472
+ } catch { /* best effort */ }
473
+ }
474
+ try { if (host && host.child) await harness.stopHost(host); } catch { /* best effort */ }
475
+ }
476
+
477
+ // --- h. 无真实 profile 读取 + 假 Key 从未外呼 ---------------------------
478
+ try {
479
+ // 复核白名单在整场运行后仍成立(宿主已停,浏览器已关)。
480
+ let guardsHold = true;
481
+ const guardValues = {};
482
+ for (const key of fixture.ISOLATION_PATH_KEYS) {
483
+ const value = path.resolve(process.env[key]);
484
+ guardValues[key] = value;
485
+ if (!isInside(value, layout.root) || isInside(value, fixture.REAL_HOME) || value.includes(realProfileDir)) guardsHold = false;
486
+ }
487
+ assertTrue(guardsHold, 'the isolation whitelist no longer holds at the end of the run');
488
+ // 零模型请求引用 g 项在真实页面上录得的事件流(浏览器此时已按退出纪律
489
+ // 关闭,不再有可询问的页面)。
490
+ assertTrue(gDetail && Array.isArray(gDetail.modelStreamEvents) && gDetail.modelStreamEvents.length === 0, 'case g did not prove a clean event stream');
491
+ hDetail = {
492
+ realProfileDirPathKnownToThisProbe: realProfileDir,
493
+ realProfileDirFilesystemAccess: 'none — this probe performs no fs operation on it (path string only, for the guards above); the integration-5025 fixture has no digest machinery at all',
494
+ isolationGuards: { everyIsolationEnvValueInsideLayoutRoot: true, noIsolationEnvValueInsideTheRealHome: true, noIsolationEnvValueReferencesTheRealProfileDir: true, finalCheck: guardValues },
495
+ fakeCredential: { value: FAKE_KEY, everSentToAnyProvider: false, baseUrlHost: 'i5025-fake.invalid (RFC 2606 reserved — unresolvable by construction)', providerRequests: 0 },
496
+ servedProfilesOverTheWholeRun: 'exactly one fictional profile (case a + case f restart)',
497
+ draftsSentToAnyModel: [],
498
+ };
499
+ record('WB005-5025.h-no-real-profile-read-and-fake-key-never-dialled', 'PASS', hDetail);
500
+ } catch (error) {
501
+ record('WB005-5025.h-no-real-profile-read-and-fake-key-never-dialled', 'FAIL', { error: String(error && error.message || error) });
502
+ }
503
+
504
+ // --- 自有残留清点:宿主 pid 消失 + 命令行引用隔离 root 的自有进程清零 ----
505
+ let ownResidue = null;
506
+ try {
507
+ ownResidue = await withBudget('own-residue-census', 30000, () => harness.waitUntil(async () => {
508
+ const remaining = harness.listProcesses().filter(entry => entry.commandLine.includes(layout.root));
509
+ return remaining.length === 0 ? [] : null;
510
+ }, 'every own process referencing the isolated root to be gone', 20000).catch(() => harness.listProcesses().filter(entry => entry.commandLine.includes(layout.root))));
511
+ } catch (error) { ownResidue = [{ note: String(error && error.message || error) }]; }
512
+ const finalHostPid = host && host.child && host.child.child ? host.child.child.pid : null;
513
+ const hostStillListed = finalHostPid ? harness.listProcesses().some(entry => entry.pid === finalHostPid) : false;
514
+ const ownResidueClean = Array.isArray(ownResidue) && ownResidue.length === 0 && !hostStillListed;
515
+
516
+ clearTimeout(watchdog);
517
+
518
+ const essential = [
519
+ 'WB005-5025.a-real-browser-host-and-isolation',
520
+ 'WB005-5025.b-project-drafts-are-isolated',
521
+ 'WB005-5025.c-drafts-survive-a-real-browser-refresh',
522
+ 'WB005-5025.d-session-drafts-are-isolated',
523
+ 'WB005-5025.e-attachment-lifecycle-and-isolation',
524
+ 'WB005-5025.f-own-host-restart-keeps-projects-and-drafts',
525
+ 'WB005-5025.g-no-model-request-was-made',
526
+ 'WB005-5025.h-no-real-profile-read-and-fake-key-never-dialled',
527
+ ];
528
+ const proven = essential.every(id => (results.find(entry => entry.caseId === id) || {}).status === 'PASS') && ownResidueClean;
529
+
530
+ const summary = {
531
+ lane: 'workbench-integration-5025',
532
+ file: 'parallel-workbench-integration-5025-drafts-probe.js',
533
+ case: 'WB-005',
534
+ title: '草稿与附件隔离(真实浏览器 + 真实 UI 操作,canonical 集成复测)',
535
+ startedAt,
536
+ finishedAt: new Date().toISOString(),
537
+ module: identity,
538
+ identityChain: identityCheck,
539
+ servedUiIntegrity: servedIntegrity,
540
+ host: host ? { mode: 'standalone (fictional profile)', pid: host.child.child.pid, port: host.port, dataDir: host.layout.dataDir, workspace: host.layout.workspace } : null,
541
+ isolation: {
542
+ envWhitelistApplied: envApplied,
543
+ pathKeysResolvedInsideLayoutRoot: pathKeyValues,
544
+ realProfileSourceAbsent: true,
545
+ fakeProfile: { id: FAKE_PROFILE_ID, name: FAKE_PROFILE_NAME, provider: 'i5025-fake-provider', baseUrl: FAKE_BASE_URL, model: FAKE_MODEL, credential: FAKE_KEY, credentialSentAnywhere: false },
546
+ chromeResolvedBeforeEnvSwitch: chromePath,
547
+ moduleRootResolvedBeforeEnvSwitch: moduleRoot,
548
+ },
549
+ timeouts: { wholeProbeWatchdogMs: PROBE_TIMEOUT_MS, perStepBudgets: 'withBudget labels in steps below', exitDiscipline: 'graceful CDP close wait (<=1.5s) then forced process.exit after the verdict' },
550
+ drafts: { projectA: DRAFT_A, projectB: DRAFT_B, sessionTwoOfProjectA: DRAFT_S2, sentToAnyModel: false },
551
+ uiActions: [
552
+ "the product's own add-project form to create both projects",
553
+ "the product's own sidebar project head (.proj-item) to switch projects",
554
+ "the product's own sidebar session row (.session) to open sessions",
555
+ "the product's own 新会话 button to create sessions",
556
+ "the product's own <input type=\"file\"> change handler to attach files",
557
+ 'a real Page.reload of the real page (twice: refresh case and after the own-host restart)',
558
+ "a real stop+start of only this case's own isolated host child",
559
+ ],
560
+ storeApiUsedAsUiSubstitute: false,
561
+ recoveryExpectationsFollowTheProductLifecycle: 'text drafts are storage-backed and must survive switch/refresh/restart; the first session migrates the project-level draft and leaves a tombstone; attachments are page-memory only and must vanish on refresh/restart without ever entering storage (expecteds identical to the accepted R4/R5 runs)',
562
+ steps: stepTimings,
563
+ cases: results,
564
+ ownResidue: { processesReferencingTheIsolatedRoot: ownResidue, finalHostPid, finalHostStillListed: hostStillListed, clean: ownResidueClean },
565
+ verdict: proven
566
+ ? 'PASS: canonical 集成复测——可移植临时根与独立 owner root、完整环境白名单(含 APPDATA/LOCALAPPDATA/CODEX_HOME)、Chrome>=136 脚手架、独立身份链(根 package 精确依赖约束 vs 被测模块自报版本 + served 字节逐字节一致);真实 profile 来源不带入,宿主以产品自身 ProfileStore 播种纯虚构 profile 与假 Key;草稿按项目/会话隔离并在切换/刷新/自有宿主重启后分别恢复;附件按会话生命周期不跨项目泄漏且字节永不入存储;全程零模型请求;自有宿主/浏览器无残留;进程真实退出(exit code 由运行方观测)'
567
+ : 'BLOCKED',
568
+ paidTurnsSent: 0,
569
+ quotaEndpointQueried: false,
570
+ sceneKeptOnFailure: sceneKept,
571
+ };
572
+
573
+ harness.writeEvidence(path.join(EVIDENCE_DIR, 'evidence', 'drafts-5025-probe.json'), summary);
574
+ if (!proven) {
575
+ if (sceneKept) console.log(`SCENE KEPT ${sceneKept}`);
576
+ } else {
577
+ fixture.removeTree(layout.root);
578
+ }
579
+ console.log(`parallel-workbench-integration-5025-drafts-probe verdict=${summary.verdict}`);
580
+ const exitCode = proven ? 0 : 3;
581
+ // 强制退出(hangRuling 认可的根修):verdict 打印后进程必须真的退。
582
+ setTimeout(() => process.exit(exitCode), 300);
583
+ process.exitCode = exitCode;
584
+ })().catch(error => {
585
+ console.error(error);
586
+ setTimeout(() => process.exit(1), 300);
587
+ process.exitCode = 1;
588
+ });