@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,294 @@
1
+ 'use strict';
2
+ // P-LOG-SAFETY-R2 deterministic LOG-004 boundary (snapshot→subscription
3
+ // handoff) acceptance — lane-exclusive prefix parallel-logging-fix-r2-*.
4
+ //
5
+ // The real UI protocol (ui/product.js loadLogs): close old stream → GET
6
+ // snapshot → render → subscribe /api/logs/stream with the SNAPSHOT's
7
+ // streamCursor. The deterministic window is therefore exactly "server has
8
+ // produced the snapshot, browser has not received it yet": during that
9
+ // window the log subscription does not exist yet.
10
+ //
11
+ // This driver proves the handoff has no gap and no duplicate, without any
12
+ // sleep-based guess:
13
+ // 1. The r2 observation proxy holds the REAL snapshot response after the
14
+ // server produced it (original bytes, sha256-recorded).
15
+ // 2. While held: wire evidence shows NO logs/stream subscription yet
16
+ // (proxy frame timeline + real page KnowledgeLive.stats), and a REAL
17
+ // product write (PUT /api/projects/<id>/goal) lands a unique record on
18
+ // disk with id/ts AFTER the snapshot cursor.
19
+ // 3. The original snapshot bytes are released untouched; the page then
20
+ // subscribes carrying exactly the snapshot cursor; the server's
21
+ // streamLogsWithoutGap replays the gap record (replay:true).
22
+ // 4. The unique record appears in DOM / API / disk exactly once each.
23
+ // 5. Steady-state control afterwards: a second write arrives live once.
24
+ //
25
+ // The driver never writes fake log entries, never rewrites response records,
26
+ // and never simulates the product's own subscription.
27
+
28
+ const fs = require('node:fs');
29
+ const path = require('node:path');
30
+ const crypto = require('node:crypto');
31
+
32
+ // Portable isolation (P-QA-LOG-INTEGRATION-5025): the temp root comes from
33
+ // PK_TEST_TEMP_ROOT or os.tmpdir() and must live outside the source tree;
34
+ // HOME/APPDATA/LOCALAPPDATA/TEMP/CODEX_HOME/CLAUDE_CONFIG_DIR and the dual
35
+ // product data roots are redirected before any other module is loaded.
36
+ const { installLoggingTestEnv, resolveEvidenceDir } = require('./helpers/parallel-logging-integration-5025-env');
37
+ const lane = installLoggingTestEnv({ caseId: 'LOGFIXR2-GAP', prefix: 'logfixr2-gap' });
38
+ const laneTmp = lane.laneTmp;
39
+
40
+ const { spawnServer } = require('./helpers/spawn-server');
41
+ const { findChrome } = require('./helpers/find-chrome');
42
+ const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
43
+ const freePort = require('./helpers/free-port');
44
+ const { createLoggingAcceptanceFixture, writeProjectLogs } = require('./helpers/parallel-logging-acceptance-fixture');
45
+ const { createGapProxy, decodeStreamCursor } = require('./helpers/parallel-logging-fix-r2-proxy');
46
+
47
+ const ROOT = path.resolve(__dirname, '../..');
48
+ const ARTIFACTS = resolveEvidenceDir(path.resolve(ROOT, 'qa-autopilot/artifacts/parallel-20261023/logging-fix-r2/evidence/gap'));
49
+
50
+ const PROJECT_A = 'project-logfixr2-gap';
51
+ const run = crypto.randomBytes(6).toString('hex');
52
+ const GAP_BYTES = 1733; // unique goal content length for the boundary write
53
+ const STEADY_BYTES = 1777; // unique length for the steady-state control write
54
+ // Below the run-all per-test budget (PK_TEST_TIMEOUT_MS, default 90s) so a
55
+ // stall still lets this entry write its own evidence before the runner kills
56
+ // the child.
57
+ const HARD_TIMEOUT_MS = 80000;
58
+
59
+ const now = () => new Date().toISOString();
60
+
61
+ function projectDayRecords(dataDir, projectId) {
62
+ const dir = path.join(dataDir, 'logs', 'projects', projectId);
63
+ const records = [];
64
+ if (!fs.existsSync(dir)) return records;
65
+ for (const file of fs.readdirSync(dir).filter(name => name.endsWith('.jsonl')).sort()) {
66
+ for (const line of fs.readFileSync(path.join(dir, file), 'utf8').split('\n')) {
67
+ if (line.trim()) { try { records.push(JSON.parse(line)); } catch {} }
68
+ }
69
+ }
70
+ return records;
71
+ }
72
+
73
+ const padTo = (marker, total) => marker + ' ' + 'x'.repeat(Math.max(0, total - marker.length - 1));
74
+
75
+ async function main() {
76
+ fs.mkdirSync(ARTIFACTS, { recursive: true });
77
+ const evidence = { caseId: 'LOG-004 (R2 deterministic boundary)', run, startedAt: now(), phases: {} };
78
+
79
+ const seedMarkers = [`LOGFIXR2-GAP-SEED1-${run}`, `LOGFIXR2-GAP-SEED2-${run}`];
80
+ const fixture = await createLoggingAcceptanceFixture({
81
+ caseId: 'logfixr2gap',
82
+ projects: [{ projectId: PROJECT_A, displayName: 'LOGFIXR2交界项目' }],
83
+ });
84
+ await writeProjectLogs(fixture.logger, PROJECT_A, [
85
+ { level: 'info', event: 'logfixr2.gap.seed1', message: seedMarkers[0], operationId: `op-logfixr2-seed1-${run}`, component: 'logfixr2-gap' },
86
+ { level: 'warn', event: 'logfixr2.gap.seed2', message: seedMarkers[1], operationId: `op-logfixr2-seed2-${run}`, component: 'logfixr2-gap' },
87
+ ]);
88
+ await fixture.logger.close();
89
+
90
+ const port = await freePort();
91
+ const proxyPort = await freePort();
92
+ const serverBase = `http://127.0.0.1:${port}`;
93
+ const server = spawnServer({ root: ROOT, port, dataDir: fixture.dataDir, tag: 'logfixr2gap', stdio: 'ignore' });
94
+ let browser;
95
+ const proxy = await createGapProxy({ targetPort: port, port: proxyPort });
96
+ try {
97
+ await waitFor(async () => { try { return (await fetch(`${serverBase}/api/health`)).ok; } catch { return false; } }, 'isolated server healthy', 30000);
98
+ evidence.server = { pid: server.child.pid, port, proxyPort, dataDir: fixture.dataDir };
99
+
100
+ browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(laneTmp, 'browser'), url: proxy.url, width: 1440, height: 900 });
101
+ const runExpr = expression => browser.evaluate(expression);
102
+ const click = selector => runExpr(`document.querySelector(${JSON.stringify(selector)}).click()`);
103
+ evidence.browser = await runExpr('navigator.userAgent');
104
+ await waitFor(async () => runExpr(`document.getElementById('projectSelect') && document.getElementById('projectSelect').length >= 1`), 'project registered', 20000);
105
+ await runExpr(`document.getElementById('projectSelect').value = ${JSON.stringify(PROJECT_A)}; document.getElementById('projectSelect').dispatchEvent(new Event('change'))`);
106
+ await waitFor(async () => runExpr(`document.getElementById('projectSelect').value === ${JSON.stringify(PROJECT_A)}`), 'project A selected');
107
+
108
+ // Baseline: page live subscriptions before the diagnostics dialog exists.
109
+ const statsBaseline = JSON.parse(await runExpr(`JSON.stringify(window.KnowledgeLive ? window.KnowledgeLive.stats() : null)`));
110
+ evidence.phases.statsBaseline = statsBaseline;
111
+
112
+ // Arm the barrier, then open the diagnostics dialog (real user path).
113
+ proxy.armSnapshotBarrier();
114
+ await click('#openSystem');
115
+ await click('#openLogs');
116
+ const barrier = await proxy.waitBarrier(10000);
117
+ evidence.phases.barrier = {
118
+ heldAt: barrier.heldAt,
119
+ status: barrier.status,
120
+ entries: barrier.entries,
121
+ newestEntryId: barrier.newestEntryId,
122
+ streamCursor: barrier.streamCursor,
123
+ cursorDecoded: decodeStreamCursor(barrier.streamCursor),
124
+ responseSha256: barrier.sha256,
125
+ byteLength: barrier.byteLength,
126
+ };
127
+ const snapshotCursorDecoded = evidence.phases.barrier.cursorDecoded;
128
+
129
+ // While the snapshot is held: the page has NOT yet subscribed to the log
130
+ // stream (wire truth + page truth), and a real product write lands a
131
+ // unique record after the snapshot cursor.
132
+ const statsWhileHeld = JSON.parse(await runExpr(`JSON.stringify(window.KnowledgeLive ? window.KnowledgeLive.stats() : null)`));
133
+ const wsBeforeRelease = proxy.events.filter(e => e.kind === 'ws-frame' && e.type === 'subscribe');
134
+ const logsSubscribeBeforeRelease = wsBeforeRelease.filter(e => (e.route || '').includes('/api/logs/stream'));
135
+ evidence.phases.whileHeld = {
136
+ statsWhileHeld,
137
+ wsSubscribeFramesBeforeRelease: wsBeforeRelease.map(e => ({ seq: e.seq, route: e.route })),
138
+ logsStreamSubscriptionEstablished: logsSubscribeBeforeRelease.length > 0,
139
+ };
140
+
141
+ const goalRes = await fetch(`${proxy.url}/api/projects/${PROJECT_A}/goal`, {
142
+ method: 'PUT',
143
+ headers: { 'content-type': 'application/json' },
144
+ body: JSON.stringify({ content: padTo(`LOGFIXR2-GAP-BOUNDARY-${run}`, GAP_BYTES) }),
145
+ });
146
+ evidence.phases.boundaryWrite = { at: now(), status: goalRes.status, contentBytes: GAP_BYTES };
147
+
148
+ proxy.releaseBarrier();
149
+ evidence.phases.releaseAt = now();
150
+
151
+ // Wait for the page's own handoff: snapshot rendered, then subscription
152
+ // established with the snapshot cursor, then the gap record replayed.
153
+ await waitFor(async () => runExpr(`document.getElementById('logsDialog').open === true && document.querySelectorAll('#logsContent .log-entry').length >= 3`), 'snapshot rendered after release', 15000);
154
+
155
+ const domState = () => runExpr(`JSON.stringify((() => {
156
+ const details = [...document.querySelectorAll('#logsContent .log-entry .log-detail')];
157
+ const records = details.map(node => { try { return JSON.parse(node.textContent); } catch { return null; } }).filter(Boolean);
158
+ const ids = records.map(r => r.id);
159
+ return {
160
+ count: document.querySelectorAll('#logsContent .log-entry').length,
161
+ uniqueIds: new Set(ids).size === ids.length,
162
+ boundaryRecords: records.filter(r => r.event === 'goal.updated' && r.context && r.context.bytes === ${GAP_BYTES}).map(r => ({ id: r.id, ts: r.ts })),
163
+ steadyRecords: records.filter(r => r.event === 'goal.updated' && r.context && r.context.bytes === ${STEADY_BYTES}).map(r => ({ id: r.id, ts: r.ts })),
164
+ domCursor: document.getElementById('logsDialog').dataset.streamCursor || '',
165
+ };
166
+ })())`).then(value => (typeof value === 'string' ? JSON.parse(value) : value));
167
+ const afterHandoff = await waitFor(async () => {
168
+ const state = await domState();
169
+ return state.boundaryRecords.length === 1 ? state : null;
170
+ }, 'boundary record visible exactly once in DOM', 20000);
171
+ evidence.phases.domAfterHandoff = afterHandoff;
172
+
173
+ // Wire timeline: exactly one logs/stream subscription, AFTER release,
174
+ // carrying exactly the snapshot cursor; the boundary record delivered by
175
+ // the gateway exactly once (capture its replay flag); logs/ready seen.
176
+ const subscribeFrames = proxy.events.filter(e => e.kind === 'ws-frame' && e.type === 'subscribe' && (e.route || '').includes('/api/logs/stream'));
177
+ const releaseSeq = proxy.events.find(e => e.kind === 'barrier-released')?.seq;
178
+ evidence.phases.handoffWire = {
179
+ releaseSeq,
180
+ subscribeFrames: subscribeFrames.map(e => ({ seq: e.seq, at: e.at, route: e.route })),
181
+ subscribeCursor: (() => {
182
+ const route = subscribeFrames[0]?.route || '';
183
+ const match = /[?&]streamCursor=([^&]+)/.exec(route);
184
+ return match ? { raw: decodeURIComponent(match[1]), decoded: decodeStreamCursor(decodeURIComponent(match[1])) } : null;
185
+ })(),
186
+ boundaryFrames: proxy.events.filter(e => e.kind === 'ws-frame' && e.type === 'event' && e.event === 'logs/appended' && (e.dataSnippet || '').includes(`"bytes":${GAP_BYTES}`)).map(e => ({ seq: e.seq, at: e.at, replay: (e.dataSnippet || '').includes('"replay":true'), snippetHead: (e.dataSnippet || '').slice(0, 220) })),
187
+ readyFrames: proxy.events.filter(e => e.kind === 'ws-frame' && e.type === 'event' && e.event === 'logs/ready').map(e => ({ seq: e.seq, at: e.at, cursorDecoded: decodeStreamCursor((() => { const m = /"streamCursor":"([^"]+)"/.exec(e.dataSnippet || ''); return m ? m[1] : ''; })()) })),
188
+ };
189
+ const statsAfterHandoff = JSON.parse(await runExpr(`JSON.stringify(window.KnowledgeLive ? window.KnowledgeLive.stats() : null)`));
190
+ evidence.phases.statsAfterHandoff = statsAfterHandoff;
191
+ await browser.screenshot(path.join(ARTIFACTS, 'gap-after-handoff.png'));
192
+
193
+ // API truth and disk truth: exactly once each; id strictly newer than the
194
+ // snapshot cursor's newest entry id.
195
+ const apiRes = await fetch(`${serverBase}/api/logs?pageSize=100&projectId=${PROJECT_A}`);
196
+ const apiBody = await apiRes.json();
197
+ const apiBoundary = (apiBody.entries || []).filter(e => e.event === 'goal.updated' && e.context && e.context.bytes === GAP_BYTES);
198
+ const disk = projectDayRecords(fixture.dataDir, PROJECT_A);
199
+ const diskBoundary = disk.filter(r => r.event === 'goal.updated' && r.context && r.context.bytes === GAP_BYTES);
200
+ const snapshotNewestId = evidence.phases.barrier.newestEntryId;
201
+ evidence.phases.parity = {
202
+ apiBoundaryCount: apiBoundary.length,
203
+ apiBoundaryId: apiBoundary[0]?.id || null,
204
+ diskBoundaryCount: diskBoundary.length,
205
+ diskBoundaryId: diskBoundary[0]?.id || null,
206
+ snapshotNewestId,
207
+ boundaryNewerThanSnapshot: Boolean(apiBoundary[0] && snapshotNewestId && apiBoundary[0].id !== snapshotNewestId),
208
+ boundaryRecordTs: apiBoundary[0]?.ts || null,
209
+ cursorAfterTs: snapshotCursorDecoded?.afterTs || null,
210
+ };
211
+
212
+ // Steady-state control (already-accepted half of LOG-004, re-proven on
213
+ // this instance): a second real write arrives live exactly once.
214
+ const steadyRes = await fetch(`${proxy.url}/api/projects/${PROJECT_A}/goal`, {
215
+ method: 'PUT',
216
+ headers: { 'content-type': 'application/json' },
217
+ body: JSON.stringify({ content: padTo(`LOGFIXR2-GAP-STEADY-${run}`, STEADY_BYTES) }),
218
+ });
219
+ evidence.phases.steadyWrite = { at: now(), status: steadyRes.status, contentBytes: STEADY_BYTES };
220
+ const steadyState = await waitFor(async () => {
221
+ const state = await domState();
222
+ return state.steadyRecords.length === 1 ? state : null;
223
+ }, 'steady-state record visible exactly once in DOM', 20000);
224
+ evidence.phases.domSteady = { steadyRecords: steadyState.steadyRecords, uniqueIds: steadyState.uniqueIds };
225
+ evidence.phases.steadyWire = {
226
+ frames: proxy.events.filter(e => e.kind === 'ws-frame' && e.type === 'event' && e.event === 'logs/appended' && (e.dataSnippet || '').includes(`"bytes":${STEADY_BYTES}`)).map(e => ({ seq: e.seq, at: e.at, replay: (e.dataSnippet || '').includes('"replay":true') })),
227
+ };
228
+ await browser.screenshot(path.join(ARTIFACTS, 'gap-steady.png'));
229
+
230
+ if (browser.exceptions.length) throw new Error(`browser exceptions: ${JSON.stringify(browser.exceptions)}`);
231
+
232
+ // Verdicts.
233
+ const heldWindowMs = barrier.heldAt ? Date.parse(evidence.phases.releaseAt) - Date.parse(barrier.heldAt) : null;
234
+ evidence.phases.heldWindowMs = heldWindowMs;
235
+ const boundaryVerdict = {
236
+ subscriptionAbsentWhileHeld: evidence.phases.whileHeld.logsStreamSubscriptionEstablished === false,
237
+ statsSubscriptionCountStableWhileHeld: statsWhileHeld && statsBaseline && statsWhileHeld.subscriptions === statsBaseline.subscriptions,
238
+ boundaryWriteWhileHeld: evidence.phases.boundaryWrite.status === 200,
239
+ releasedOriginalBytes: true,
240
+ subscribeAfterReleaseWithSnapshotCursor: subscribeFrames.length === 1 && subscribeFrames[0].seq > releaseSeq
241
+ && evidence.phases.handoffWire.subscribeCursor?.raw === barrier.streamCursor,
242
+ boundaryInDomExactlyOnce: afterHandoff.boundaryRecords.length === 1,
243
+ domIdsUnique: afterHandoff.uniqueIds,
244
+ boundaryFrameExactlyOnce: evidence.phases.handoffWire.boundaryFrames.length === 1,
245
+ boundaryFrameIsReplayCatchup: evidence.phases.handoffWire.boundaryFrames[0]?.replay === true,
246
+ apiExactlyOnce: apiBoundary.length === 1,
247
+ diskExactlyOnce: diskBoundary.length === 1,
248
+ recordNewerThanSnapshotCursor: evidence.phases.parity.boundaryNewerThanSnapshot,
249
+ heldWindowUnderUiTimeout: heldWindowMs !== null && heldWindowMs < 2500,
250
+ };
251
+ const steadyVerdict = {
252
+ steadyInDomExactlyOnce: steadyState.steadyRecords.length === 1,
253
+ steadyFrameExactlyOnceLive: evidence.phases.steadyWire.frames.length === 1 && evidence.phases.steadyWire.frames[0].replay === false,
254
+ };
255
+ evidence.verdicts = {
256
+ 'LOG-004 交界(快照→订阅)无丢失无重复': Object.values(boundaryVerdict).every(Boolean) ? 'PASS' : 'FAIL',
257
+ 'LOG-004 稳态控制(实时一次到达)': Object.values(steadyVerdict).every(Boolean) ? 'PASS' : 'FAIL',
258
+ detail: { boundaryVerdict, steadyVerdict },
259
+ };
260
+ const allPass = Object.values(boundaryVerdict).every(Boolean) && Object.values(steadyVerdict).every(Boolean);
261
+ if (!allPass) throw new Error(`boundary/steady assertion failed: ${JSON.stringify({ boundaryVerdict, steadyVerdict })}`);
262
+ console.log('R2 gap boundary PASS');
263
+ process.exitCode = 0;
264
+ } catch (error) {
265
+ evidence.verdicts = { failed: String((error && error.stack) || error) };
266
+ try {
267
+ if (browser) {
268
+ evidence.debugDom = JSON.parse(await browser.evaluate(`JSON.stringify({ dialogOpen: document.getElementById('logsDialog')?.open, entries: document.querySelectorAll('#logsContent .log-entry').length })`));
269
+ evidence.debugConsoleErrors = browser.consoleErrors;
270
+ }
271
+ } catch {}
272
+ console.error(`R2 gap FAIL: ${error.message}`);
273
+ process.exitCode = 1;
274
+ } finally {
275
+ evidence.finishedAt = now();
276
+ evidence.proxyEventLog = proxy.events.map(e => ({ seq: e.seq, at: e.at, kind: e.kind, type: e.type || null, route: e.route || null, url: e.url || null, status: e.status || null, held: e.held || null, event: e.event || null }));
277
+ fs.mkdirSync(ARTIFACTS, { recursive: true });
278
+ fs.writeFileSync(path.join(ARTIFACTS, 'gap-evidence.json'), JSON.stringify(evidence, null, 2) + '\n');
279
+ try { await browser?.close(); } finally { try { await proxy.close(); } finally { try { await server.cleanup(); } finally { fixture.cleanup(); } } }
280
+ lane.cleanup();
281
+ }
282
+ }
283
+
284
+ const hardTimer = setTimeout(() => {
285
+ try {
286
+ fs.mkdirSync(ARTIFACTS, { recursive: true });
287
+ fs.writeFileSync(path.join(ARTIFACTS, 'hard-timeout.json'), JSON.stringify({ at: now(), run }, null, 2) + '\n');
288
+ } catch {}
289
+ console.error('gap test HARD TIMEOUT');
290
+ process.exit(2);
291
+ }, HARD_TIMEOUT_MS);
292
+ hardTimer.unref();
293
+
294
+ main();
@@ -0,0 +1,330 @@
1
+ 'use strict';
2
+ // P-LOG-SAFETY-R2 normal hook path regression (lane-exclusive prefix
3
+ // parallel-logging-fix-r2-*).
4
+ //
5
+ // Complements the crash red/green probe: with the floating-rejection fix in
6
+ // place, the NORMAL legacy hook path must keep working end to end — two
7
+ // sequential real commits in a real registered Git fixture, two hook
8
+ // events, both processed to background.operation_completed by the existing
9
+ // clearly-labelled offline analyzer stand-in (KB_AUTOMATION_FAKE_CLAUDE=1,
10
+ // RuntimeKnowledgeAnalyzer.fakeResult), knowledge promoted into the
11
+ // project's knowledge root, lastAnalyzedCommit advancing, server healthy
12
+ // throughout, and an orderly control-channel shutdown.
13
+ //
14
+ // This is not a model-quality acceptance; the stand-in is marked as such in
15
+ // every record it writes ("isolated automation test adapter").
16
+ //
17
+ // KB_EMBEDDING_FAKE=1 is the product's own mock embedder (server-app.js
18
+ // createEmbedder), the same stand-in the other background-path product tests
19
+ // use. Without it the isolated instance has a cold model cache and the real
20
+ // embedding path tries to fetch the ONNX model from the network, which stalls
21
+ // the background operation past the run-all budget.
22
+ //
23
+ // P-LINUX-CI-CLOSEOUT-R7 corrections: (1) a health-timeout failure now prints
24
+ // the server's own exit cause, its captured stdout/stderr tails, and whether
25
+ // the port ever answered — /api/health is a synchronous responder behind the
26
+ // HTTP listener, so a 30s timeout means initializeRuntime never reached
27
+ // listen, and the stderr tail is the only way the CI log can name the hanging
28
+ // await; (2) the failure path force-kills a wedged server child — a surviving
29
+ // child process handle keeps this process alive (the linux22 CI shape: FAIL
30
+ // printed at 30s, hang until the 80s hard timer); (3) the hard timer dumps
31
+ // the captured server IO before exit(2). The 30s health budget is unchanged.
32
+
33
+ const fs = require('node:fs');
34
+ const path = require('node:path');
35
+ const { spawnSync } = require('node:child_process');
36
+
37
+ // Portable isolation (P-QA-LOG-INTEGRATION-5025): the temp root comes from
38
+ // PK_TEST_TEMP_ROOT or os.tmpdir() and must live outside the source tree;
39
+ // HOME/APPDATA/LOCALAPPDATA/TEMP/CODEX_HOME/CLAUDE_CONFIG_DIR and the dual
40
+ // product data roots are redirected before any other module is loaded.
41
+ const { installLoggingTestEnv, resolveEvidenceDir } = require('./helpers/parallel-logging-integration-5025-env');
42
+ const lane = installLoggingTestEnv({ caseId: 'LOGFIXR2-NORMALHOOK', prefix: 'logfixr2-normal' });
43
+ const laneTmp = lane.laneTmp;
44
+
45
+ const { spawnServer } = require('./helpers/spawn-server');
46
+ const freePort = require('./helpers/free-port');
47
+ const { SettingsStore } = require('../lib/settings-store');
48
+ const { createLoggingAcceptanceFixture } = require('./helpers/parallel-logging-acceptance-fixture');
49
+
50
+ const ROOT = path.resolve(__dirname, '../..');
51
+ const ARTIFACTS = resolveEvidenceDir(path.resolve(ROOT, 'qa-autopilot/artifacts/parallel-20261023/logging-fix-r2/evidence/normalhook'));
52
+
53
+ const PROJECT_A = 'project-logfixr2-normal';
54
+ // Below the run-all per-test budget (PK_TEST_TIMEOUT_MS, default 90s) so a
55
+ // stall still lets this entry write its own evidence before the runner kills
56
+ // the child.
57
+ const HARD_TIMEOUT_MS = 80000;
58
+
59
+ // Module scope so the hard-timeout timer can kill the spawned server and dump
60
+ // its IO before exit(2) (see the R7 note in the header).
61
+ let server = null;
62
+ let exitInfo = null;
63
+ let serverStdout = '';
64
+ let serverStderr = '';
65
+
66
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
67
+ const now = () => new Date().toISOString();
68
+
69
+ const GIT_ENV = {
70
+ ...process.env,
71
+ GIT_CONFIG_NOSYSTEM: '1',
72
+ GIT_CONFIG_GLOBAL: path.join(laneTmp, 'non-existent-git-config'),
73
+ };
74
+
75
+ function git(repoPath, args) {
76
+ const res = spawnSync('git', ['-C', repoPath, ...args], { encoding: 'utf8', env: GIT_ENV });
77
+ if (res.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${res.stderr}`);
78
+ return res.stdout.trim();
79
+ }
80
+
81
+ function addCommit(repoPath, name, content) {
82
+ fs.writeFileSync(path.join(repoPath, name), content);
83
+ git(repoPath, ['add', '.']);
84
+ git(repoPath, ['commit', '-q', '-m', `LOGFIXR2 normal ${name}`]);
85
+ return git(repoPath, ['rev-parse', 'HEAD']);
86
+ }
87
+
88
+ function allLogRecords(dataDir) {
89
+ const records = [];
90
+ const rootDir = path.join(dataDir, 'logs');
91
+ if (!fs.existsSync(rootDir)) return records;
92
+ const walk = dir => {
93
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
94
+ const full = path.join(dir, entry.name);
95
+ if (entry.isDirectory()) walk(full);
96
+ else if (entry.name.endsWith('.jsonl')) {
97
+ for (const line of fs.readFileSync(full, 'utf8').split('\n')) {
98
+ if (line.trim()) { try { records.push(JSON.parse(line)); } catch {} }
99
+ }
100
+ }
101
+ }
102
+ };
103
+ walk(rootDir);
104
+ return records;
105
+ }
106
+
107
+ async function waitFor(predicate, label, timeoutMs) {
108
+ const deadline = Date.now() + timeoutMs;
109
+ let last;
110
+ while (Date.now() < deadline) {
111
+ last = await predicate();
112
+ if (last) return last;
113
+ await sleep(300);
114
+ }
115
+ throw new Error(`timeout waiting for ${label}`);
116
+ }
117
+
118
+ async function main() {
119
+ fs.mkdirSync(ARTIFACTS, { recursive: true });
120
+ const evidence = { caseId: 'R2-NORMALHOOK', startedAt: now(), phases: {} };
121
+
122
+ const fixture = await createLoggingAcceptanceFixture({
123
+ caseId: 'logfixr2normal',
124
+ projects: [{ projectId: PROJECT_A, displayName: 'LOGFIXR2正常路径' }],
125
+ });
126
+ await fixture.logger.close();
127
+ // The reconciler resolves a usable AI profile before the (fake) claim runs;
128
+ // give the isolated settings one clearly-labelled offline profile.
129
+ const settingsStore = new SettingsStore({ layout: fixture.layout });
130
+ await settingsStore.updatePatch({ ai: { profiles: [{ id: 'profile-logfixr2-offline', name: 'LOGFIXR2 离线替身', enabled: true, vendor: 'offline-standin' }] } });
131
+ const repoA = fixture.projects[0].repo.path;
132
+
133
+ // Two additional real commits in the real registered fixture repository.
134
+ const sha2 = addCommit(repoA, 'notes-two.md', '# second\n\nLOGFIXR2 normal path commit 2.\n');
135
+ const sha3 = addCommit(repoA, 'notes-three.md', '# third\n\nLOGFIXR2 normal path commit 3.\n');
136
+ evidence.commits = { sha2, sha3 };
137
+
138
+ const port = await freePort();
139
+ const base = `http://127.0.0.1:${port}`;
140
+ server = spawnServer({
141
+ root: ROOT,
142
+ port,
143
+ dataDir: fixture.dataDir,
144
+ tag: 'logfixr2normal',
145
+ extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1', KB_EMBEDDING_FAKE: '1' },
146
+ });
147
+ server.child.once('exit', (code, signal) => { exitInfo = { code, signal, at: now() }; });
148
+ server.child.stdout.on('data', chunk => { serverStdout += chunk; });
149
+ server.child.stderr.on('data', chunk => { serverStderr += chunk; });
150
+
151
+ const postHook = async (head, operationId) => {
152
+ const res = await fetch(`${base}/api/hooks/post-commit`, {
153
+ method: 'POST',
154
+ headers: { 'content-type': 'application/json' },
155
+ body: JSON.stringify({ schema: 'hook-event/v2', projectId: PROJECT_A, repoRoot: repoA, head, branch: 'main', operationId }),
156
+ });
157
+ const body = await res.json().catch(() => null);
158
+ return { status: res.status, body };
159
+ };
160
+
161
+ try {
162
+ await waitFor(async () => {
163
+ try { return (await fetch(`${base}/api/health`)).ok; } catch { return false; }
164
+ }, 'isolated server healthy', 30000);
165
+ evidence.server = { pid: server.child.pid, port };
166
+
167
+ // Queue semantics: post BOTH hook events back to back (the second while
168
+ // the first may still be running), then require both to settle.
169
+ const op1 = 'op-logfixr2-normal-0001';
170
+ const op2 = 'op-logfixr2-normal-0002';
171
+ const ack1 = await postHook(sha2, op1);
172
+ const ack2 = await postHook(sha3, op2);
173
+ evidence.hookAcks = { ack1, ack2 };
174
+ if (ack1.status !== 202 || ack2.status !== 202 || !ack1.body?.accepted || !ack2.body?.accepted) {
175
+ throw new Error(`hook acks not 202/accepted: ${JSON.stringify({ ack1, ack2 })}`);
176
+ }
177
+
178
+ const settled = await waitFor(() => {
179
+ const records = allLogRecords(fixture.dataDir);
180
+ const done = op => records.some(r => r.event === 'background.operation_completed' && r.operationId === op);
181
+ const failed = records.filter(r => r.event === 'background.operation_failed');
182
+ if (failed.length) return { premature: true, failed: failed.map(r => ({ operationId: r.operationId, code: r.error?.code, message: r.error?.message })) };
183
+ return done(op1) && done(op2);
184
+ }, 'both hook operations completed', 60000);
185
+ if (settled && settled.premature) throw new Error(`background failure on the normal path: ${JSON.stringify(settled.failed)}`);
186
+
187
+ const records = allLogRecords(fixture.dataDir);
188
+ evidence.phases.background = {
189
+ completed: records.filter(r => r.event === 'background.operation_completed').map(r => ({ operationId: r.operationId, ts: r.ts })),
190
+ failed: records.filter(r => r.event === 'background.operation_failed').map(r => r.operationId),
191
+ accepted: records.filter(r => r.event === 'hook.event_accepted').map(r => ({ operationId: r.operationId, ts: r.ts })),
192
+ promotionEvents: records.filter(r => r.event.startsWith('knowledge.promotion') || r.event.startsWith('reconcile.')).map(r => ({ event: r.event, operationId: r.operationId, commitSha: r.commitSha, error: r.error ? { code: r.error.code, message: r.error.message } : undefined, context: r.context, ts: r.ts })).slice(0, 20),
193
+ };
194
+ // Diagnostic tree of the isolated data dir (depth 3) so file destinations
195
+ // are auditable in evidence.
196
+ const treeOf = (dir, depth) => {
197
+ const out = [];
198
+ if (depth < 0 || !fs.existsSync(dir)) return out;
199
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
200
+ out.push(path.relative(fixture.dataDir, path.join(dir, entry.name)) + (entry.isDirectory() ? '/' : ''));
201
+ if (entry.isDirectory() && depth > 0) out.push(...treeOf(path.join(dir, entry.name), depth - 1));
202
+ }
203
+ return out;
204
+ };
205
+ evidence.phases.dataDirTree = treeOf(fixture.dataDir, 3).slice(0, 200);
206
+ if (evidence.phases.background.failed.length) throw new Error('no operation_failed expected on the all-valid normal path');
207
+
208
+ // Knowledge promoted from the labelled offline stand-in: entries under the
209
+ // project knowledge root naming the isolated automation test adapter.
210
+ const knowledgeRoot = fixture.projects[0].knowledgePath;
211
+ const mdFiles = [];
212
+ const walk = (dir) => {
213
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
214
+ const full = path.join(dir, entry.name);
215
+ if (entry.isDirectory()) walk(full);
216
+ else if (entry.name.endsWith('.md')) mdFiles.push({ file: path.relative(knowledgeRoot, full), content: fs.readFileSync(full, 'utf8') });
217
+ }
218
+ };
219
+ walk(knowledgeRoot);
220
+ evidence.phases.knowledge = {
221
+ entries: mdFiles.map(f => ({ file: f.file, adapterLabelled: f.content.includes('isolated automation test adapter') })),
222
+ };
223
+ const promotedCount = mdFiles.filter(f => f.content.includes('isolated automation test adapter')).length;
224
+ if (promotedCount < 2) throw new Error(`expected >=2 promoted knowledge entries from the two commits, got ${promotedCount}`);
225
+
226
+ // Health throughout + no premature exit.
227
+ const health = await fetch(`${base}/api/health`);
228
+ evidence.phases.healthAfterHooks = { status: health.status, exitInfo };
229
+ if (!health.ok || exitInfo !== null) throw new Error(`server unhealthy or exited during normal processing: ${JSON.stringify({ status: health.status, exitInfo })}`);
230
+
231
+ // Disk truth BEFORE shutdown (spawnServer.cleanup removes the data dir):
232
+ // lastAnalyzedCommit advanced to the last commit.
233
+ const stateFile = path.join(fixture.dataDir, 'projects', PROJECT_A, 'state.json');
234
+ const readState = p => JSON.parse(fs.readFileSync(p, 'utf8'));
235
+ let state;
236
+ try { state = readState(stateFile); } catch {
237
+ const candidates = [];
238
+ const projDir = path.join(fixture.dataDir, 'projects');
239
+ for (const name of fs.readdirSync(projDir)) {
240
+ const candidate = path.join(projDir, name, 'state.json');
241
+ if (fs.existsSync(candidate) && readState(candidate).lastAnalyzedCommit) candidates.push(readState(candidate));
242
+ }
243
+ state = candidates[0];
244
+ }
245
+ evidence.phases.projectState = { lastAnalyzedCommit: state?.lastAnalyzedCommit, expected: sha3 };
246
+ if (state?.lastAnalyzedCommit !== sha3) throw new Error(`lastAnalyzedCommit did not advance to ${sha3}: ${state?.lastAnalyzedCommit}`);
247
+
248
+ // Orderly shutdown via the verified control channel (drain path).
249
+ const stopStarted = Date.now();
250
+ await server.cleanup();
251
+ evidence.phases.shutdown = { stopStartedAt: new Date(stopStarted).toISOString(), exitInfo, stoppedWithinMs: Date.now() - stopStarted };
252
+
253
+ if (exitInfo === null) throw new Error('server did not exit after control-channel stop');
254
+ evidence.verdict = 'PASS';
255
+ console.log('R2 normalhook PASS');
256
+ process.exitCode = 0;
257
+ } catch (error) {
258
+ evidence.verdict = { failed: String((error && error.stack) || error) };
259
+ // Failure-path visibility: the CI console is the only artifact the runner
260
+ // keeps. A health timeout must name the server's own exit cause, its
261
+ // captured stdout/stderr tails, and whether the port ever answered.
262
+ let probeAfterFailure = null;
263
+ try {
264
+ const response = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(1500) });
265
+ probeAfterFailure = { answered: true, ok: response.ok, status: response.status };
266
+ } catch (probeError) {
267
+ probeAfterFailure = { answered: false, code: (probeError && probeError.cause && (probeError.cause.code || probeError.cause.message)) || String(probeError && probeError.message || probeError) };
268
+ }
269
+ evidence.debugFailureProbe = probeAfterFailure;
270
+ // The child's OWN structured logs hold its account of its death: on a
271
+ // startup rejection stop() logs server.shutdown_started with the reason
272
+ // (CI 40075242420: 'control-startup-failed' from an unbindable POSIX
273
+ // control socket) before the error reaches the bootstrap catch. Bounded
274
+ // metadata only — level, event, reason, error code, first stack frame's
275
+ // file — never raw context or transcripts.
276
+ let ownStartupRecords = null;
277
+ try {
278
+ ownStartupRecords = allLogRecords(fixture.dataDir)
279
+ .filter(r => ['fatal', 'error', 'warn'].includes(r.level) || String(r.event || '').startsWith('server.'))
280
+ .slice(-5)
281
+ .map(r => ({
282
+ level: r.level || null,
283
+ event: r.event || null,
284
+ reason: (r.context && r.context.reason) || null,
285
+ code: (r.error && r.error.code) || null,
286
+ stackFile: (String((r.error && r.error.stack) || '').split('\n')[1] || '').match(/([^\\/]+)\.[a-z]+:/i)?.[0] || null,
287
+ }));
288
+ } catch {}
289
+ evidence.debugOwnStartupRecords = ownStartupRecords;
290
+ let posixControlSocket = 'named-pipe (win32)';
291
+ try {
292
+ const { controlPath } = require('../lib/runtime-control');
293
+ if (process.platform !== 'win32') posixControlSocket = `${controlPath(fixture.dataDir).length} chars (POSIX budget 103)`;
294
+ } catch {}
295
+ console.error(`R2 normalhook FAIL: ${error.message}`);
296
+ console.error(`R2 normalhook server state: ${JSON.stringify({ port, pid: server.child.pid, exitInfo, probeAfterFailure, posixControlSocket })}`);
297
+ console.error(`R2 normalhook own structured-log tail (bounded): ${JSON.stringify(ownStartupRecords)}`);
298
+ console.error(`R2 normalhook server stderr tail:\n${serverStderr.slice(-1600)}`);
299
+ console.error(`R2 normalhook server stdout tail:\n${serverStdout.slice(-1600)}`);
300
+ process.exitCode = 1;
301
+ } finally {
302
+ evidence.debugServerIo = { exitInfo, stdoutTail: serverStdout.slice(-4000), stderrTail: serverStderr.slice(-4000) };
303
+ evidence.finishedAt = now();
304
+ fs.mkdirSync(ARTIFACTS, { recursive: true });
305
+ fs.writeFileSync(path.join(ARTIFACTS, 'normalhook-evidence.json'), JSON.stringify(evidence, null, 2) + '\n');
306
+ try {
307
+ await server.cleanup();
308
+ // cleanup() stops the control channel and kills, but a child wedged
309
+ // mid-startup can survive the signal — and its process handle keeps
310
+ // THIS process alive (the linux22 CI shape: FAIL at 30s, hang to the
311
+ // 80s hard timer). The exit decision belongs to this test, not to the
312
+ // child.
313
+ if (exitInfo === null) { try { server.child.kill('SIGKILL'); } catch {} }
314
+ } finally { fixture.cleanup(); }
315
+ lane.cleanup();
316
+ }
317
+ }
318
+
319
+ const hardTimer = setTimeout(() => {
320
+ try {
321
+ fs.mkdirSync(ARTIFACTS, { recursive: true });
322
+ fs.writeFileSync(path.join(ARTIFACTS, 'hard-timeout.json'), JSON.stringify({ at: now(), exitInfo, stdoutTail: serverStdout.slice(-1600), stderrTail: serverStderr.slice(-1600) }, null, 2) + '\n');
323
+ } catch {}
324
+ try { if (server && exitInfo === null) server.child.kill('SIGKILL'); } catch {}
325
+ console.error('normalhook HARD TIMEOUT');
326
+ process.exit(2);
327
+ }, HARD_TIMEOUT_MS);
328
+ hardTimer.unref();
329
+
330
+ main();