@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,310 @@
1
+ 'use strict';
2
+ // NAV-007 — 分辨率矩阵。
3
+ //
4
+ // The six pages of the shell must render without unintended horizontal
5
+ // overflow and without a control being covered, in four desktop viewports
6
+ // and in both themes.
7
+ //
8
+ // The regression drives the real product UI in a real browser against an
9
+ // isolated instance (own port, own data dir, own knowledge dir, own browser
10
+ // profile). Every viewport is a real CDP device metrics override, every page
11
+ // is opened by a real pointer click on the real navigation control, and every
12
+ // screenshot is a real PNG of the real rendered page.
13
+ //
14
+ // The two kinds of evidence are kept apart on purpose:
15
+ //
16
+ // * The geometry evidence is machine checked: the bounding rect of every
17
+ // visible control, the horizontal overflow of every container, and the
18
+ // hit test of every control that proves nothing stands in front of it.
19
+ // These assertions decide the exit code of this test.
20
+ // * The screenshot evidence is for a human reviewer. This repository holds
21
+ // no approved visual baseline for the product, so the test does NOT
22
+ // compare the screenshots against a baseline and does NOT claim a
23
+ // baseline match. It writes the screenshots, their pixel statistics and
24
+ // an index, and the human review of that material stays an open item.
25
+ //
26
+ // Screenshots are written to PK_TEST_EVIDENCE_DIR when the batch sets it, so
27
+ // this tracked file stays free of machine-specific paths.
28
+
29
+ const fs = require('node:fs');
30
+ const path = require('node:path');
31
+ const assert = require('node:assert/strict');
32
+
33
+ const { startHarness, makeRepo } = require('./helpers/parallel-nav-shell');
34
+
35
+ const EVIDENCE = process.env.PK_TEST_EVIDENCE_DIR ? path.resolve(process.env.PK_TEST_EVIDENCE_DIR) : null;
36
+
37
+ const VIEWPORTS = [
38
+ { name: '1366x768', width: 1366, height: 768 },
39
+ { name: '1440x900', width: 1440, height: 900 },
40
+ { name: '1600x900', width: 1600, height: 900 },
41
+ { name: '1920x1080', width: 1920, height: 1080 },
42
+ ];
43
+
44
+ const THEMES = ['light', 'dark'];
45
+
46
+ const PAGES = [
47
+ { key: 'home', view: 'home' },
48
+ { key: 'work', view: 'work' },
49
+ { key: 'search', view: 'search' },
50
+ { key: 'memory', view: 'memory' },
51
+ { key: 'projects', view: 'projects' },
52
+ { key: 'activity', view: 'activity' },
53
+ ];
54
+
55
+ /**
56
+ * The bounding rect evidence of the rendered page: the overflow of every
57
+ * container and the rect and hit test of every visible control.
58
+ */
59
+ const geometry = harness => harness.run(`(() => {
60
+ const active = document.querySelector('.view.active');
61
+ const doc = document.documentElement;
62
+ const describe = el => {
63
+ const parts = [];
64
+ if (el.id) parts.push('#' + el.id);
65
+ if (el.className && typeof el.className === 'string') parts.push('.' + el.className.trim().split(/\\s+/).join('.'));
66
+ return el.tagName.toLowerCase() + parts.join('');
67
+ };
68
+ const overflowing = [];
69
+ for (const el of document.querySelectorAll('html, body, .app, .sidebar, main, .content, .view, .topbar')) {
70
+ const style = getComputedStyle(el);
71
+ if (style.display === 'none') continue;
72
+ if (el.scrollWidth > el.clientWidth + 1) {
73
+ overflowing.push({ element: describe(el), scrollWidth: el.scrollWidth, clientWidth: el.clientWidth });
74
+ }
75
+ }
76
+ const controls = [];
77
+ for (const el of document.querySelectorAll('button, select, input, textarea, a[href]')) {
78
+ if (el.closest('iframe')) continue;
79
+ const style = getComputedStyle(el);
80
+ if (style.display === 'none' || style.visibility === 'hidden') continue;
81
+ const rect = el.getBoundingClientRect();
82
+ if (rect.width <= 0 || rect.height <= 0) continue;
83
+ // The part of the control the user can really see. A control that
84
+ // crosses the lower edge of the viewport is not covered: the page
85
+ // scrolls, and the visible part of it must be reachable.
86
+ const visibleLeft = Math.max(rect.left, 0);
87
+ const visibleRight = Math.min(rect.right, innerWidth);
88
+ const visibleTop = Math.max(rect.top, 0);
89
+ const visibleBottom = Math.min(rect.bottom, innerHeight);
90
+ const visibleWidth = Math.round(visibleRight - visibleLeft);
91
+ const visibleHeight = Math.round(visibleBottom - visibleTop);
92
+ const inViewport = visibleWidth > 0 && visibleHeight > 0;
93
+ let covered = false;
94
+ let coveredBy = '';
95
+ let hitX = 0;
96
+ let hitY = 0;
97
+ if (inViewport) {
98
+ // The hit test runs on the centre of the VISIBLE part of the control.
99
+ // The centre of the whole control may lie below the fold, where the
100
+ // browser reports no element at all - that is a scroll position, not
101
+ // an occlusion.
102
+ hitX = Math.round((visibleLeft + visibleRight) / 2);
103
+ hitY = Math.round((visibleTop + visibleBottom) / 2);
104
+ const hit = document.elementFromPoint(hitX, hitY);
105
+ covered = !(hit && (hit === el || el.contains(hit) || el.contains(hit.parentElement)));
106
+ coveredBy = hit ? (hit.id || hit.tagName) : 'nothing';
107
+ }
108
+ controls.push({
109
+ element: describe(el),
110
+ id: el.id || '',
111
+ label: (el.textContent || '').trim().slice(0, 24),
112
+ left: Math.round(rect.left), right: Math.round(rect.right),
113
+ top: Math.round(rect.top), bottom: Math.round(rect.bottom),
114
+ width: Math.round(rect.width), height: Math.round(rect.height),
115
+ visibleWidth, visibleHeight,
116
+ hitX, hitY,
117
+ belowFold: rect.top >= innerHeight,
118
+ outsideHorizontally: rect.left < -1 || rect.right > innerWidth + 1,
119
+ inViewport,
120
+ covered,
121
+ coveredBy,
122
+ });
123
+ }
124
+ return {
125
+ viewport: { width: innerWidth, height: innerHeight },
126
+ theme: document.documentElement.dataset.theme || '',
127
+ activeView: active ? active.id : '',
128
+ docScrollWidth: doc.scrollWidth,
129
+ docClientWidth: doc.clientWidth,
130
+ viewScrollWidth: active ? active.scrollWidth : 0,
131
+ viewClientWidth: active ? active.clientWidth : 0,
132
+ overflowing,
133
+ controls,
134
+ };
135
+ })()`);
136
+
137
+ /**
138
+ * The pixel statistics of a real screenshot. The PNG is decoded by the
139
+ * browser itself, so the numbers come from the rendered pixels and not from
140
+ * the DOM.
141
+ */
142
+ async function pixelStats(harness, base64) {
143
+ return harness.run(`(async () => {
144
+ const img = new Image();
145
+ img.src = 'data:image/png;base64,${base64}';
146
+ await img.decode();
147
+ // The picture is sampled on a small grid: the browser still decodes the
148
+ // real PNG, but the statistics cost a fraction of a full-size scan.
149
+ const canvas = document.createElement('canvas');
150
+ canvas.width = 96;
151
+ canvas.height = 54;
152
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
153
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
154
+ const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
155
+ let sum = 0;
156
+ const buckets = new Set();
157
+ for (let i = 0; i < data.length; i += 4) {
158
+ const r = data[i], g = data[i + 1], b = data[i + 2];
159
+ sum += 0.2126 * r + 0.7152 * g + 0.0722 * b;
160
+ buckets.add(((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4));
161
+ }
162
+ const pixels = data.length / 4;
163
+ return {
164
+ width: img.naturalWidth,
165
+ height: img.naturalHeight,
166
+ sampledWidth: canvas.width,
167
+ sampledHeight: canvas.height,
168
+ meanLuminance: Math.round((sum / pixels) * 100) / 100,
169
+ distinctColors: buckets.size,
170
+ };
171
+ })()`);
172
+ }
173
+
174
+ /**
175
+ * One line per control: the geometry the case really measured. The runner
176
+ * shows only the last lines of a failure, so a dump of the whole array would
177
+ * hide the control that matters behind the tail of the list.
178
+ */
179
+ const controlSummary = control => `${control.element}${control.id ? '#' + control.id : ''} "${control.label}" rect=[${control.left},${control.top} ${control.right},${control.bottom}] visible=${control.visibleWidth}x${control.visibleHeight} hit=(${control.hitX},${control.hitY}) coveredBy=${control.coveredBy}`;
180
+
181
+ const overflowSummary = entry => `${entry.element} scrollWidth=${entry.scrollWidth} clientWidth=${entry.clientWidth}`;
182
+
183
+ async function ensureTheme(harness, theme) {
184
+ const current = await harness.run("document.documentElement.dataset.theme || ''");
185
+ if (current === theme) return;
186
+ await harness.nativeClick('#themeToggle');
187
+ await harness.waitFor(
188
+ () => harness.run(`document.documentElement.dataset.theme === ${JSON.stringify(theme)}`),
189
+ `the shell shows the ${theme} theme`,
190
+ );
191
+ }
192
+
193
+ async function main() {
194
+ const harness = await startHarness({ tag: 'nav007', evidenceDir: EVIDENCE || undefined });
195
+ const index = [];
196
+ try {
197
+ const repo = makeRepo(harness.fixture, 'nav007-a');
198
+ await harness.importProject(repo, 'NAV007 甲');
199
+ await harness.requestRegistryRefresh();
200
+ await harness.waitFor(
201
+ () => harness.run("document.getElementById('homeContent').textContent.includes('NAV007 甲')"),
202
+ 'the imported project is listed',
203
+ 20000,
204
+ );
205
+
206
+ for (const viewport of VIEWPORTS) {
207
+ await harness.setViewport(viewport.width, viewport.height);
208
+ for (const theme of THEMES) {
209
+ await ensureTheme(harness, theme);
210
+ for (const page of PAGES) {
211
+ await harness.nativeClick(`nav [data-view="${page.view}"]`);
212
+ await harness.waitFor(
213
+ () => harness.run(`document.getElementById('view-${page.view}').classList.contains('active')`),
214
+ `the ${page.view} view is open`,
215
+ 10000,
216
+ );
217
+ // The module views fill their frame asynchronously; the shell
218
+ // chrome is laid out already, so one frame of settling is enough
219
+ // for the rects to be final.
220
+ await harness.wait(300);
221
+
222
+ const info = await geometry(harness);
223
+ const label = `${viewport.name}-${theme}-${page.key}`;
224
+ const where = `${label} viewport=${info.viewport.width}x${info.viewport.height} theme=${info.theme} view=${info.activeView}`;
225
+
226
+ // The page that is open is the page the matrix asked for.
227
+ assert.equal(info.activeView, `view-${page.view}`, `the open page is the one under test: ${where}`);
228
+ // The theme under test is the theme the page really shows.
229
+ assert.equal(info.theme, theme, `the page really shows the ${theme} theme: ${where}`);
230
+
231
+ // NAV-007 expected 1: no unintended horizontal overflow.
232
+ assert.equal(info.overflowing.length, 0, `no container overflows horizontally: ${where} ${info.overflowing.map(overflowSummary).join(' | ')}`);
233
+ assert.ok(info.docScrollWidth <= info.docClientWidth + 1, `the document does not scroll sideways: ${where} ${info.docScrollWidth}/${info.docClientWidth}`);
234
+ assert.ok(info.viewScrollWidth <= info.viewClientWidth + 1, `the open page does not scroll sideways: ${where} ${info.viewScrollWidth}/${info.viewClientWidth}`);
235
+ const outside = info.controls.filter(control => control.outsideHorizontally);
236
+ assert.equal(outside.length, 0, `no visible control leaves the viewport sideways: ${where} ${outside.map(controlSummary).join(' | ')}`);
237
+
238
+ // NAV-007 expected 1 (continued): no control is covered by another.
239
+ const covered = info.controls.filter(control => control.covered);
240
+ assert.equal(covered.length, 0, `no visible control is covered: ${where} ${covered.map(controlSummary).join(' | ')}`);
241
+
242
+ // The page really has content: the controls of the open view exist.
243
+ assert.ok(info.controls.length > 0, `the open page exposes controls: ${where} ${info.controls.length} controls`);
244
+
245
+ // The screenshot of the real page, and its real pixels.
246
+ const shot = await harness.browser.send('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false });
247
+ const file = path.join(harness.evidence, `nav007-${label}.png`);
248
+ fs.mkdirSync(path.dirname(file), { recursive: true });
249
+ fs.writeFileSync(file, Buffer.from(shot.data, 'base64'));
250
+ const pixels = await pixelStats(harness, shot.data);
251
+ assert.equal(pixels.width, viewport.width, `the screenshot has the viewport width: ${where}`);
252
+ assert.equal(pixels.height, viewport.height, `the screenshot has the viewport height: ${where}`);
253
+ assert.ok(pixels.distinctColors >= 3, `the screenshot shows a rendered page, not a blank surface: ${where} ${JSON.stringify(pixels)}`);
254
+
255
+ index.push({
256
+ case: 'NAV-007',
257
+ label,
258
+ viewport: { width: viewport.width, height: viewport.height },
259
+ theme,
260
+ page: page.key,
261
+ screenshot: path.basename(file),
262
+ pixels,
263
+ controls: info.controls.length,
264
+ docScrollWidth: info.docScrollWidth,
265
+ docClientWidth: info.docClientWidth,
266
+ });
267
+ }
268
+ }
269
+ }
270
+
271
+ assert.equal(index.length, VIEWPORTS.length * THEMES.length * PAGES.length, 'the whole matrix was captured');
272
+
273
+ // The theme really repaints the page: the dark screenshots are darker
274
+ // than the light ones of the same page and viewport.
275
+ for (const viewport of VIEWPORTS) {
276
+ for (const page of PAGES) {
277
+ const light = index.find(entry => entry.viewport.width === viewport.width && entry.theme === 'light' && entry.page === page.key);
278
+ const dark = index.find(entry => entry.viewport.width === viewport.width && entry.theme === 'dark' && entry.page === page.key);
279
+ assert.ok(light && dark, `the matrix holds both themes of the page: ${viewport.name}/${page.key}`);
280
+ assert.ok(dark.pixels.meanLuminance < light.pixels.meanLuminance, `the dark theme really repaints the page: ${viewport.name}/${page.key} ${dark.pixels.meanLuminance} vs ${light.pixels.meanLuminance}`);
281
+ }
282
+ }
283
+
284
+ // The screenshots and their pixel statistics are written for the human
285
+ // reviewer. No visual baseline exists in this repository, so no baseline
286
+ // match is claimed here; the review stays an open item.
287
+ fs.writeFileSync(
288
+ path.join(harness.evidence, 'nav007-screenshot-index.json'),
289
+ `${JSON.stringify({
290
+ case: 'NAV-007',
291
+ generatedBy: '_site/_test/parallel-nav-viewport-test.js',
292
+ matrix: { viewports: VIEWPORTS, themes: THEMES, pages: PAGES.map(page => page.key) },
293
+ humanBaseline: {
294
+ state: 'PENDING_HUMAN_REVIEW',
295
+ note: '本仓库没有已批准的产品视觉基线,本用例只产出截图与像素统计,不声称基线一致;人工审阅未完成。',
296
+ },
297
+ screenshots: index,
298
+ }, null, 2)}\n`,
299
+ 'utf8',
300
+ );
301
+
302
+ assert.equal(harness.browser.exceptions.length, 0, `no uncaught product JavaScript exceptions: ${JSON.stringify(harness.browser.exceptions)}`);
303
+
304
+ console.log(`parallel-nav-viewport-test PASS (${index.length} screenshots of ${VIEWPORTS.length} viewports x ${THEMES.length} themes x ${PAGES.length} pages, no horizontal overflow, no covered control; the human baseline review stays open)`);
305
+ } finally {
306
+ await harness.cleanup();
307
+ }
308
+ }
309
+
310
+ main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
@@ -0,0 +1,273 @@
1
+ 'use strict';
2
+ // _site/_test/parallel-qa-engine-endpoint-guard-test.js — P-QA-ENGINE-ENDPOINT-FIX-R5
3
+ //
4
+ // Safety regression for the engine endpoint lane. The original defect: tests
5
+ // constructed engines WITHOUT vectorUrl/bridgeUrl, and the coordinator falls
6
+ // back to hardcoded LIVE ports (127.0.0.1:8787 / :8790) regardless of env, so
7
+ // confirm/scopes-sync/index/search chains dialed LIVE services. This test
8
+ // reproduces every one of those chains while an egress guard blocks protected
9
+ // ports BEFORE any I/O — no socket is ever opened toward 5757/5760/5789/5790/
10
+ // 8787/8790. The positive leg runs the same chains against an OWN ephemeral
11
+ // vector stand-in and asserts the actual requests arrived there (fixture-side
12
+ // records, independent expected origin).
13
+ //
14
+ // P-QA-ENGINE-ENDPOINT-REVIEW-R6 legs, appended after the R5 legs:
15
+ // D. IPv6 alignment — the egress judge accepts the bracketed [::1] form of
16
+ // URL.hostname (the same form assertOwnedEndpoint accepts), proven with a
17
+ // STUBBED original fetch so no real IPv6 socket is ever opened;
18
+ // E. PSModuleAnalysisCachePath file shape — parent materialized, target
19
+ // never a directory, and a REAL PowerShell child that writes its own
20
+ // cache file under the isolated root while seeing only the isolated
21
+ // home (its own echoed env is the evidence; no browser involved);
22
+ // F. offline POSIX socket-budget string contract — the CI-long root shape
23
+ // is rejected, the short POSIX components hold with margin, the boundary
24
+ // is exact, and accounting is UTF-8 bytes. Windows cannot prove Linux:
25
+ // actual Linux proof stays with CI; this leg proves the byte math.
26
+ const assert = require('node:assert/strict');
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+ const { spawnSync } = require('node:child_process');
30
+ const {
31
+ applyDriverIsolation, childEnv, LIVE_HOME,
32
+ POSIX_SOCKET_CHAR_LIMIT, worstCaseSocketPaths, socketBudgetVerdict, assertSocketBudget,
33
+ } = require('./helpers/parallel-qa-engine-endpoint-isolate');
34
+ const {
35
+ REFUSAL_CODE, EGRESS_BLOCK_CODE, PROTECTED_PORTS,
36
+ assertOwnedEndpoint, guardedCreateEngineServer, installEgressGuard,
37
+ createEngineVectorStandin, createEngineBridgeDouble,
38
+ } = require('./helpers/parallel-qa-engine-endpoint-guard');
39
+
40
+ (async () => {
41
+ const applied = applyDriverIsolation({ prefix: 'pk-qa-engine-endpoint-guard' });
42
+ const mkdtemp = name => fs.mkdtempSync(path.join(applied.layout.temp, `${name}-`));
43
+ // Shared-index jobs run on a serialized queue and may chain dirty reruns, so
44
+ // awaiting one job promise does not prove the observable effect landed. Wait
45
+ // bounded on the effect itself.
46
+ async function waitFor(predicate, label, timeoutMs = 5000) {
47
+ const started = Date.now();
48
+ while (!predicate()) {
49
+ if (Date.now() - started > timeoutMs) throw new Error(`timeout waiting for: ${label}`);
50
+ await new Promise(resolve => setImmediate(resolve));
51
+ }
52
+ }
53
+ const guard = installEgressGuard();
54
+ let negativeEngine = null, positiveEngine = null, standin = null;
55
+ try {
56
+ // ── Leg A: fail-closed constructor refusals (product code never loads) ──
57
+ for (const [label, build] of [
58
+ ['missing vectorUrl', () => guardedCreateEngineServer({})],
59
+ ['null vectorUrl', () => guardedCreateEngineServer({ vectorUrl: null })],
60
+ ['missing bridge leg', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:41234' })],
61
+ ['live vectorUrl 8787', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:8787', bridgeUrl: 'http://127.0.0.1:41235' })],
62
+ ['live bridgeUrl 8790', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:41236', bridgeUrl: 'http://127.0.0.1:8790' })],
63
+ ['non-loopback vectorUrl', () => guardedCreateEngineServer({ vectorUrl: 'http://10.9.8.7:41237', bridgeUrl: 'http://127.0.0.1:41238' })],
64
+ ['non-loopback bridgeUrl', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:41239', bridgeUrl: 'http://example.test:9' })],
65
+ ['port 0 vectorUrl', () => guardedCreateEngineServer({ vectorUrl: 'http://127.0.0.1:0', bridgeUrl: 'http://127.0.0.1:41240' })],
66
+ ['https vectorUrl', () => guardedCreateEngineServer({ vectorUrl: 'https://127.0.0.1:41241', bridgeUrl: 'http://127.0.0.1:41242' })],
67
+ ['non-http scheme', () => assertOwnedEndpoint('ws://127.0.0.1:41243', { role: 'vectorUrl' })],
68
+ ]) assert.throws(build, error => error.code === REFUSAL_CODE, `refusal: ${label}`);
69
+ for (const port of PROTECTED_PORTS) assert.throws(() => assertOwnedEndpoint(`http://127.0.0.1:${port}`, { role: 'vectorUrl' }), error => error.code === REFUSAL_CODE, `refusal: protected port ${port}`);
70
+ const probeUrl = assertOwnedEndpoint('http://127.0.0.1:41244/foo', { role: 'vectorUrl' });
71
+ assert.equal(probeUrl.port, '41244');
72
+
73
+ // Egress guard blocks non-loopback targets pre-I/O as well.
74
+ await assert.rejects(() => fetch('https://registry.npmjs.org/never-requested-r5'), error => error.code === EGRESS_BLOCK_CODE && error.reason === 'non-loopback-host:registry.npmjs.org');
75
+
76
+ // ── Leg B: negative regression — the ORIGINAL defect, safely held ──
77
+ // Raw product constructor without vectorUrl (exactly the S1/S2/S3 shape).
78
+ const { createEngineServer } = require('../../_modules/knowledge-engine/src/server');
79
+ const negRoot = mkdtemp('neg');
80
+ const negBrainDir = path.join(negRoot, 'brain-shared');
81
+ fs.mkdirSync(negBrainDir, { recursive: true });
82
+ fs.writeFileSync(path.join(negBrainDir, 'note.md'), '# 共享知识\n\n负向回归锚点:缺省构造不得触碰 LIVE 端口。\n');
83
+ negativeEngine = createEngineServer({ dataDir: path.join(negRoot, 'engine'), consume: false, runtimeToken: 'r5-guard-neg' });
84
+ await negativeEngine.ready;
85
+ assert.equal(negativeEngine.coordinator.vectorUrl, 'http://127.0.0.1:8787', 'negative leg must model the defect: hardcoded LIVE fallback');
86
+
87
+ // S3 chain: scopes sync → shared index import at LIVE 8787 — blocked pre-I/O.
88
+ await negativeEngine.coordinator.memories.configure([{ id: 'brain-r5', kind: 'brain', brainId: 'brain-r5', knowledgePath: negBrainDir }]);
89
+ const negScope = negativeEngine.store.state.sharedScopes.find(scope => scope.id === 'brain-r5');
90
+ await negativeEngine.coordinator.indexSharedScope(negScope);
91
+ assert.equal(negScope.index.status, 'pending', 'blocked import must leave the shared index pending');
92
+ assert.equal(negScope.index.error.details.upstreamCode, EGRESS_BLOCK_CODE);
93
+
94
+ // S1/S2 chain: manual memory confirm → materialize → onMaterialized → import.
95
+ const negMemory = await negativeEngine.coordinator.memories.create({ text: '负向回归:确认流必须在 LIVE 8787 之前被拦截', brainId: 'brain-r5' });
96
+ assert.equal(negMemory.status, 'confirmed');
97
+ await waitFor(() => guard.blocked.filter(entry => entry.href === 'http://127.0.0.1:8787/api/import').length >= 2, 'confirm chain attempting LIVE import while blocked');
98
+ // index() chain: run maintenance index → /api/sync at LIVE 8787.
99
+ fs.mkdirSync(path.join(negRoot, 'knowledge'), { recursive: true });
100
+ negativeEngine.store.addProject({ projectId: 'proj-neg', repoPath: path.join(negRoot, 'code'), knowledgePath: path.join(negRoot, 'knowledge') });
101
+ const negRun = negativeEngine.store.addRun({ projectId: 'proj-neg', commitSha: 'a'.repeat(40), trigger: 'manual' });
102
+ await negativeEngine.coordinator.index(negRun);
103
+ const blockedNegRun = negativeEngine.store.getRun(negRun.runId);
104
+ assert.equal(blockedNegRun.state, 'index-pending');
105
+ assert.equal(blockedNegRun.error.details.upstreamCode, EGRESS_BLOCK_CODE);
106
+ // search chain: recallIndex would GET /api/search at LIVE 8787 — blocked too.
107
+ const negSearch = await negativeEngine.coordinator.retrieval.search({ projectId: 'proj-neg', query: '向量索引守护与导入契约' });
108
+ assert.deepEqual(negSearch.retrievalModes, ['lexical'], 'blocked vector must degrade to lexical, never fake success');
109
+
110
+ const blockedImports = guard.blocked.filter(entry => entry.href === 'http://127.0.0.1:8787/api/import');
111
+ assert.ok(blockedImports.length >= 2, `expected the configure+confirm chains to attempt LIVE import at least twice (got ${blockedImports.length})`);
112
+ assert.ok(guard.blocked.some(entry => entry.href === 'http://127.0.0.1:8787/api/sync'), 'index() chain must attempt LIVE sync');
113
+ assert.ok(guard.blocked.some(entry => entry.href.startsWith('http://127.0.0.1:8787/api/search')), 'search chain must attempt LIVE search');
114
+ assert.ok(guard.blocked.every(entry => entry.reason.startsWith('protected-port:') || entry.reason.startsWith('non-loopback-host:')), 'every block must name a protected port or a non-loopback host');
115
+ assert.ok(guard.requests.every(entry => !/:87(87|90)|:57(57|60|89|90)/.test(entry.href)), 'no protected-port request may ever be passed through');
116
+
117
+ // ── Leg C: positive proof at the OWN stand-in ──
118
+ standin = createEngineVectorStandin();
119
+ const standinUrl = await standin.ready;
120
+ const bridgeDouble = createEngineBridgeDouble();
121
+ const posRoot = mkdtemp('pos');
122
+ const posBrainDir = path.join(posRoot, 'brain-shared');
123
+ fs.mkdirSync(posBrainDir, { recursive: true });
124
+ fs.writeFileSync(path.join(posBrainDir, 'note.md'), '# 共享知识\n\n正向证明锚点。\n');
125
+ fs.mkdirSync(path.join(posRoot, 'knowledge'), { recursive: true });
126
+ const blockedBeforePositive = guard.blocked.length;
127
+ positiveEngine = guardedCreateEngineServer({ dataDir: path.join(posRoot, 'engine'), consume: false, runtimeToken: 'r5-guard-pos', vectorUrl: standinUrl, bridge: bridgeDouble });
128
+ await positiveEngine.ready;
129
+ assert.equal(positiveEngine.coordinator.vectorUrl, standinUrl);
130
+ const importCount = () => standin.records.filter(record => record.method === 'POST' && record.path === '/api/import').length;
131
+
132
+ // Scopes sync imports at the OWN stand-in, and the recorded origin (server
133
+ // side) — not a driver-echoed value — proves where the request landed.
134
+ await positiveEngine.coordinator.memories.configure([{ id: 'brain-pos', kind: 'brain', brainId: 'brain-pos', knowledgePath: posBrainDir }]);
135
+ const posScope = positiveEngine.store.state.sharedScopes.find(scope => scope.id === 'brain-pos');
136
+ await positiveEngine.coordinator.indexSharedScope(posScope);
137
+ assert.equal(posScope.index.status, 'ready');
138
+ const importRecord = standin.records.find(record => record.method === 'POST' && record.path === '/api/import');
139
+ assert.equal(importRecord.body.projectName, 'scope-brain-brain-pos');
140
+ assert.equal(importRecord.body.sourceDir, posBrainDir);
141
+
142
+ // Confirm chain: memory create → materialize → real import; unchanged
143
+ // content then takes the GET /api/projects health path and skips import.
144
+ const importsAfterConfigure = importCount();
145
+ const healthGets = () => standin.records.filter(record => record.method === 'GET' && record.path === '/api/projects').length;
146
+ const posMemory = await positiveEngine.coordinator.memories.create({ text: '正向证明:确认流导入必须落到自有替身', brainId: 'brain-pos' });
147
+ assert.equal(posMemory.status, 'confirmed');
148
+ await waitFor(() => importCount() >= importsAfterConfigure + 1, 'confirm chain importing at the OWN stand-in');
149
+ assert.equal(importCount(), importsAfterConfigure + 1, 'materialized memory must trigger exactly one real import at the stand-in');
150
+ const importsAfterConfirm = importCount();
151
+ const getsBeforeHealth = healthGets();
152
+ await positiveEngine.coordinator.indexSharedScope(positiveEngine.store.state.sharedScopes.find(scope => scope.id === 'brain-pos')).catch(() => {});
153
+ await waitFor(() => healthGets() > getsBeforeHealth, 'unchanged fingerprint taking the /api/projects health check');
154
+ assert.equal(importCount(), importsAfterConfirm, 'unchanged fingerprint must skip import via the /api/projects health check');
155
+ assert.ok(healthGets() > getsBeforeHealth, 'health check must have queried the stand-in');
156
+
157
+ // index() chain with an explicit vectorProjectId mapping.
158
+ positiveEngine.store.addProject({ projectId: 'proj-pos', repoPath: path.join(posRoot, 'code'), knowledgePath: path.join(posRoot, 'knowledge'), vectorProjectId: 'vec-proj-pos' });
159
+ const posRun = positiveEngine.store.addRun({ projectId: 'proj-pos', commitSha: 'b'.repeat(40), trigger: 'manual' });
160
+ await positiveEngine.coordinator.index(posRun);
161
+ assert.equal(positiveEngine.store.getRun(posRun.runId).state, 'succeeded');
162
+ assert.equal(positiveEngine.store.readRuntime('proj-pos').index.status, 'ready');
163
+ const syncRecord = standin.records.find(record => record.method === 'POST' && record.path === '/api/sync');
164
+ assert.equal(syncRecord.body.project, 'vec-proj-pos');
165
+ assert.equal(syncRecord.body.sourceDir, path.join(posRoot, 'knowledge'));
166
+
167
+ // search chain: routed to the stand-in, whose honest 503 degrades retrieval
168
+ // to lexical. The stand-in proves routing/contract parity only — never real
169
+ // vector retrieval quality.
170
+ const posSearch = await positiveEngine.coordinator.retrieval.search({ projectId: 'proj-pos', query: '向量索引守护与导入契约' });
171
+ assert.deepEqual(posSearch.retrievalModes, ['lexical']);
172
+ assert.equal(posSearch.backend, 'markdown-hybrid-fallback');
173
+ const searchRecord = standin.records.find(record => record.method === 'GET' && record.path.startsWith('/api/search'));
174
+ assert.ok(searchRecord, 'search must have queried the stand-in');
175
+ const searchQuery = new URL(`http://127.0.0.1${searchRecord.path}`);
176
+ assert.equal(searchQuery.searchParams.get('q'), '向量索引守护与导入契约');
177
+ assert.equal(searchQuery.searchParams.get('projects'), 'vec-proj-pos');
178
+
179
+ assert.equal(guard.blocked.length, blockedBeforePositive, 'positive leg must produce zero blocked egress');
180
+ assert.ok(guard.requests.every(entry => entry.href.startsWith(standinUrl)), 'every positive-leg request must target the OWN stand-in');
181
+ assert.equal(bridgeDouble.calls.length, 0, 'bridge double must never be dialed while consume:false');
182
+
183
+ // ── Leg D: IPv6 alignment against a STUBBED original fetch ──
184
+ {
185
+ const realFetch = global.fetch;
186
+ const dialed = [];
187
+ global.fetch = async (input, init) => {
188
+ dialed.push(input instanceof URL ? input.href : String(input?.url || input));
189
+ return { ok: true, stubbed: true };
190
+ };
191
+ let stubGuard = null;
192
+ try {
193
+ stubGuard = installEgressGuard();
194
+ assert.ok(assertOwnedEndpoint('http://[::1]:41245/own-ipv6', { role: 'vectorUrl' }), 'assertOwnedEndpoint accepts the bracketed [::1] hostname');
195
+ await fetch('http://[::1]:41245/own-ipv6');
196
+ await assert.rejects(() => fetch('http://127.0.0.1:8787/api/import'), error => error.code === EGRESS_BLOCK_CODE && error.reason === 'protected-port:8787');
197
+ assert.deepEqual(dialed, ['http://[::1]:41245/own-ipv6'],
198
+ 'only the allowed [::1] target reaches the original (stub) fetch — the 8787 call is blocked before any dialing');
199
+ } finally {
200
+ if (stubGuard) stubGuard.uninstall();
201
+ global.fetch = realFetch;
202
+ }
203
+ }
204
+
205
+ // ── Leg E: PSModuleAnalysisCachePath is a FILE; a real PowerShell child
206
+ // runs under the isolated env and writes its own startup cache inside the
207
+ // isolated home, without touching the live home ──
208
+ {
209
+ assert.ok(fs.statSync(applied.layout.psModuleCacheParent).isDirectory(), 'the cache PARENT is materialized');
210
+ assert.ok(!fs.existsSync(applied.layout.psModuleCache), 'the fresh layout must not pre-create the cache target');
211
+ assert.equal(process.env.PSModuleAnalysisCachePath, applied.layout.psModuleCache, 'the driver env points at the cache FILE path');
212
+ if (process.platform === 'win32') {
213
+ // Windows PowerShell writes ModuleAnalysisCache from its idle loop,
214
+ // which -Command sessions exit before — so the module-analysis file
215
+ // itself is recorded as EVIDENCE, not asserted. The startup cache
216
+ // (StartupProfileData-NonInteractive under <USERPROFILE>\AppData)
217
+ // IS written on every -NonInteractive startup and is the reliable
218
+ // observable proof of a real PowerShell-owned cache write.
219
+ const isolatedStartupCache = path.join(applied.layout.home, 'AppData', 'Local', 'Microsoft', 'Windows', 'PowerShell', 'StartupProfileData-NonInteractive');
220
+ const liveStartupCache = path.join(LIVE_HOME, 'AppData', 'Local', 'Microsoft', 'Windows', 'PowerShell', 'StartupProfileData-NonInteractive');
221
+ const liveMtimeBefore = fs.existsSync(liveStartupCache) ? fs.statSync(liveStartupCache).mtimeMs : null;
222
+ const psCommand = '"$env:PSModuleAnalysisCachePath`n$env:USERPROFILE"; $ErrorActionPreference=\'Stop\'; Get-Module -ListAvailable | Out-Null; Write-Output "PS-CACHE-OK"';
223
+ const probe = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', psCommand],
224
+ { env: childEnv(), timeout: 120000, windowsHide: true, encoding: 'utf8' });
225
+ const lines = String(probe.stdout || '').trim().split(/\r?\n/);
226
+ assert.equal(probe.status, 0, `PowerShell probe failed (${probe.status}): ${probe.stderr}`);
227
+ assert.equal(lines[0], applied.layout.psModuleCache, 'the child must see the isolated cache FILE path');
228
+ assert.equal(lines[1], applied.layout.home, 'the child must see the isolated home');
229
+ assert.notEqual(lines[1], LIVE_HOME, 'the child home must never be the live home');
230
+ assert.equal(lines[lines.length - 1], 'PS-CACHE-OK', 'the PowerShell child really ran to completion');
231
+ assert.ok(fs.existsSync(isolatedStartupCache) && fs.statSync(isolatedStartupCache).isFile(),
232
+ 'the PowerShell child must have written its own startup cache file inside the isolated home');
233
+ const liveMtimeAfter = fs.existsSync(liveStartupCache) ? fs.statSync(liveStartupCache).mtimeMs : null;
234
+ assert.equal(liveMtimeAfter, liveMtimeBefore, 'the isolated child must not touch the live home startup cache');
235
+ assert.ok(!fs.existsSync(applied.layout.psModuleCache) || fs.statSync(applied.layout.psModuleCache).isFile(),
236
+ 'the cache target stays a FILE path, never a directory, after the child ran');
237
+ console.log(`leg E evidence: moduleAnalysisCacheWrittenAtTarget=${fs.existsSync(applied.layout.psModuleCache)} (idle-write, not asserted); startupCacheWrittenInsideIsolation=true`);
238
+ } else {
239
+ console.log('leg E: PowerShell child probe is Windows-only; cache-file shape assertions ran');
240
+ }
241
+ }
242
+
243
+ // ── Leg F: offline POSIX socket-budget string contract ──
244
+ {
245
+ const ciFailedRoot = '/home/runner/work/_temp/pk-nav-r7-9399-ChZLZ5/pk-parallel-nav-nav004-pUkYfU';
246
+ const failed = socketBudgetVerdict(ciFailedRoot);
247
+ assert.ok(!failed.ok, 'the CI-long root shape must be rejected by the socket budget');
248
+ assert.ok(failed.chromeSocketBytes > POSIX_SOCKET_CHAR_LIMIT, `rejected with the computed worst case (${failed.chromeSocketBytes} bytes)`);
249
+ assert.throws(() => assertSocketBudget(ciFailedRoot, 'ci-long-root'), /socket path budget exceeded/, 'assertSocketBudget fails fast on the over-budget root');
250
+
251
+ const shortRoot = '/home/runner/work/_temp/pkqabcdef';
252
+ const held = socketBudgetVerdict(shortRoot);
253
+ assert.ok(held.ok, `the short POSIX root this helper creates must hold: ${JSON.stringify({ chrome: held.chromeSocketBytes, control: held.controlSocketBytes })}`);
254
+ assert.ok(POSIX_SOCKET_CHAR_LIMIT - held.chromeSocketBytes >= 10, `short root holds with margin (limit ${POSIX_SOCKET_CHAR_LIMIT}, chrome worst case ${held.chromeSocketBytes})`);
255
+
256
+ assert.equal(worstCaseSocketPaths('/tmp').chromeSingletonSocket.length - '/tmp'.length, 49,
257
+ 'the Chrome socket tail is pinned at 49 bytes (12-char rand bound)');
258
+ assert.ok(socketBudgetVerdict('/tmp/' + 'a'.repeat(53)).ok, 'a root landing exactly at the byte ceiling is accepted');
259
+ assert.ok(!socketBudgetVerdict('/tmp/' + 'a'.repeat(54)).ok, 'one byte over the ceiling is rejected');
260
+
261
+ const multibyte = socketBudgetVerdict('/tmp/π');
262
+ assert.equal(multibyte.chromeSocketBytes, multibyte.paths.chromeSingletonSocket.length + 1,
263
+ 'the budget counts UTF-8 bytes, not chars');
264
+ }
265
+
266
+ console.log('parallel-qa-engine-endpoint-guard-test PASS');
267
+ } finally {
268
+ if (negativeEngine) await negativeEngine.close().catch(() => {});
269
+ if (positiveEngine) await positiveEngine.close().catch(() => {});
270
+ if (standin) await standin.close().catch(() => {});
271
+ guard.uninstall();
272
+ }
273
+ })().catch(error => { console.error(error); process.exitCode = 1; });