@sanqianx/project-knowledge 5.0.24 → 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.
- package/.agents/plugins/marketplace.json +20 -20
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +1361 -1353
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/README.md +349 -349
- package/_modules/knowledge-engine/package.json +1 -1
- package/_modules/knowledge-engine/src/coordinator.js +48 -3
- package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -140
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +238 -238
- package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -26
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +193 -193
- package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +394 -394
- package/_modules/knowledge-engine/src/runtime/contracts.js +282 -282
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +389 -389
- package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -228
- package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -62
- package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +505 -505
- package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +400 -400
- package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +171 -171
- package/_modules/knowledge-engine/src/runtime/scanner.js +298 -298
- package/_modules/knowledge-engine/ui/app.js +23 -2
- package/_site/README.md +30 -30
- package/_site/_test/agent-capture-mcp-ui-test.js +68 -68
- package/_site/_test/agent-capture-ui-test.js +101 -101
- package/_site/_test/ai-profile-resolver-test.js +137 -137
- package/_site/_test/ai-profile-test.js +75 -75
- package/_site/_test/ai-vendor-presets-test.js +91 -91
- package/_site/_test/automatic-context-test.js +70 -70
- package/_site/_test/automation-queue-test.js +14 -14
- package/_site/_test/automation-ui-test.js +40 -40
- package/_site/_test/background-task-registry-test.js +43 -43
- package/_site/_test/baseline-schema-test.js +91 -91
- package/_site/_test/bin-cli-test.js +230 -230
- package/_site/_test/bridge-adapter-test.js +95 -95
- package/_site/_test/bridge-consumer-service-test.js +310 -310
- package/_site/_test/candidate-bin-content-test.js +35 -35
- package/_site/_test/candidate-product-assets-test.js +13 -13
- package/_site/_test/candidate-release-contract-test.js +14 -14
- package/_site/_test/capture-hook-dedup-test.js +158 -158
- package/_site/_test/cdp-browser-failure-test.js +103 -103
- package/_site/_test/chat-claudecodeui-match-test.js +92 -92
- package/_site/_test/claude-executable-discovery-test.js +90 -90
- package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
- package/_site/_test/claude-workbench-test.js +174 -174
- package/_site/_test/cli-instance-management-test.js +62 -62
- package/_site/_test/client-capture-restart-e2e-test.js +189 -189
- package/_site/_test/codex-conversation-projection-test.js +60 -60
- package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
- package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
- package/_site/_test/codex-notify-fanout-test.js +141 -134
- package/_site/_test/codex-notify-upgrade-test.js +253 -253
- package/_site/_test/commit-boundary-freeze-test.js +147 -147
- package/_site/_test/commit-conversation-binding-test.js +99 -99
- package/_site/_test/commit-evidence-test.js +110 -110
- package/_site/_test/configuration-generation-test.js +22 -22
- package/_site/_test/context-pack-test.js +200 -200
- package/_site/_test/conversation-api-test.js +119 -119
- package/_site/_test/conversation-origin-test.js +15 -15
- package/_site/_test/conversation-store-test.js +138 -138
- package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
- package/_site/_test/desktop-browser-compat-test.js +28 -28
- package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
- package/_site/_test/embedding-config-test.js +32 -32
- package/_site/_test/explicit-commit-processor-test.js +37 -37
- package/_site/_test/fixtures/fake-browser-exit.js +10 -10
- package/_site/_test/fixtures/fake-browser-hang.js +10 -10
- package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
- package/_site/_test/fixtures/make-git-repos.js +92 -92
- package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
- package/_site/_test/folder-picker-output-test.js +19 -19
- package/_site/_test/fresh-hook-reimport-test.js +53 -53
- package/_site/_test/fresh-product-data-test.js +56 -56
- package/_site/_test/full-integration-e2e-test.js +167 -167
- package/_site/_test/git-path-compatibility-test.js +80 -80
- package/_site/_test/git-validation-test.js +81 -81
- package/_site/_test/glm-ui-refresh-review-test.js +313 -404
- package/_site/_test/harness-isolation-test.js +138 -0
- package/_site/_test/helpers/cdp-browser.js +383 -0
- package/_site/_test/helpers/cdp-network-capture.js +66 -0
- package/_site/_test/helpers/find-chrome.js +34 -0
- package/_site/_test/helpers/free-port.js +8 -0
- package/_site/_test/helpers/logging-ui-fixture.js +131 -0
- package/_site/_test/helpers/module-fixture.js +282 -0
- package/_site/_test/helpers/parallel-engine-advanced-engine-child.js +45 -0
- package/_site/_test/helpers/parallel-engine-advanced-fault-writer.js +171 -0
- package/_site/_test/helpers/parallel-engine-advanced-harness.js +245 -0
- package/_site/_test/helpers/parallel-engine-advanced-standin-analyzer.js +80 -0
- package/_site/_test/helpers/parallel-engine-advanced-ui-host.js +177 -0
- package/_site/_test/helpers/parallel-engine-final-harness.js +314 -0
- package/_site/_test/helpers/parallel-engine-final-standin-analyzer.js +159 -0
- package/_site/_test/helpers/parallel-knowledge-fixture.js +255 -0
- package/_site/_test/helpers/parallel-logging-acceptance-fixture.js +111 -0
- package/_site/_test/helpers/parallel-logging-acceptance-proxy.js +106 -0
- package/_site/_test/helpers/parallel-logging-fix-r2-proxy.js +205 -0
- package/_site/_test/helpers/parallel-logging-integration-5025-env.js +432 -0
- package/_site/_test/helpers/parallel-memory-analyzer.js +81 -0
- package/_site/_test/helpers/parallel-memory-event-bridge.js +128 -0
- package/_site/_test/helpers/parallel-memory-shell.js +470 -0
- package/_site/_test/helpers/parallel-nav-isolation-env.js +389 -0
- package/_site/_test/helpers/parallel-nav-module-ui.js +186 -0
- package/_site/_test/helpers/parallel-nav-proxy.js +148 -0
- package/_site/_test/helpers/parallel-nav-shell.js +361 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-guard.js +206 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-isolate.js +257 -0
- package/_site/_test/helpers/parallel-retrieval-followup-corpus.js +293 -0
- package/_site/_test/helpers/parallel-retrieval-followup-next-session.js +70 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-shell.js +737 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-wb-host.js +82 -0
- package/_site/_test/helpers/parallel-retrieval-followup-shell.js +279 -0
- package/_site/_test/helpers/parallel-workbench-fixture.js +371 -0
- package/_site/_test/helpers/parallel-workbench-host.js +80 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-fixture.js +408 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-harness.js +593 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-host.js +70 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-launcher.js +240 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-runtime-harness.js +630 -0
- package/_site/_test/helpers/parallel-workbench-source-map.js +196 -0
- package/_site/_test/helpers/spawn-server.js +106 -0
- package/_site/_test/helpers/version-visibility-fixture.js +251 -0
- package/_site/_test/hook-runtime-endpoint-test.js +88 -88
- package/_site/_test/hook-status-repair-api-test.js +152 -152
- package/_site/_test/hook-trigger-test.js +85 -85
- package/_site/_test/hook-worktree-test.js +42 -42
- package/_site/_test/import-preflight-api-test.js +234 -234
- package/_site/_test/index-builder-test.js +41 -41
- package/_site/_test/index-error-display-contract-test.js +10 -10
- package/_site/_test/index-writer-concurrency-test.js +135 -135
- package/_site/_test/integration-adapters-test.js +262 -262
- package/_site/_test/integration-auto-repair-test.js +113 -113
- package/_site/_test/integration-capture-setup-test.js +118 -118
- package/_site/_test/integration-intent-test.js +81 -81
- package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
- package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
- package/_site/_test/integration-runtime-binding-test.js +473 -473
- package/_site/_test/integration-status-scan-test.js +29 -29
- package/_site/_test/integration-surface-coverage-test.js +164 -164
- package/_site/_test/kb-framework-test.js +57 -57
- package/_site/_test/kbpath-follow-test.js +63 -63
- package/_site/_test/knowledge-db-test.js +144 -144
- package/_site/_test/knowledge-engine-client-test.js +120 -109
- package/_site/_test/knowledge-language-control-test.js +170 -170
- package/_site/_test/knowledge-maintenance-test.js +40 -40
- package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
- package/_site/_test/knowledge-query-test.js +54 -54
- package/_site/_test/knowledge-retrieval-service-test.js +77 -77
- package/_site/_test/knowledge-scopes-test.js +37 -37
- package/_site/_test/knowledge-storage-location-test.js +28 -28
- package/_site/_test/knowledge-storage-startup-test.js +52 -52
- package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
- package/_site/_test/launcher-readiness-test.js +80 -80
- package/_site/_test/legacy-claude-session-alias-test.js +106 -106
- package/_site/_test/legacy-routes-removed-test.js +47 -47
- package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
- package/_site/_test/live-gateway-test.js +48 -48
- package/_site/_test/local-marketplace-test.js +16 -16
- package/_site/_test/log-redaction-test.js +58 -58
- package/_site/_test/logging-api-test.js +89 -89
- package/_site/_test/logging-sse-no-gap-test.js +88 -88
- package/_site/_test/logging-ui-test.js +107 -107
- package/_site/_test/markdown-delta-overlay-test.js +85 -85
- package/_site/_test/markdown-maintenance-api-test.js +75 -75
- package/_site/_test/markdown-maintenance-test.js +11 -11
- package/_site/_test/mcp-server-test.js +149 -149
- package/_site/_test/model-context-windows-test.js +89 -89
- package/_site/_test/module-agent-start-proxy-test.js +65 -65
- package/_site/_test/module-artifact-integrity-test.js +27 -27
- package/_site/_test/module-boundary-test.js +30 -30
- package/_site/_test/module-bridge-eventbridge-test.js +202 -202
- package/_site/_test/module-dynamic-port-test.js +118 -118
- package/_site/_test/module-index-proxy-test.js +33 -33
- package/_site/_test/module-model-configuration-test.js +44 -44
- package/_site/_test/module-process-lifecycle-test.js +35 -35
- package/_site/_test/module-stream-proxy-test.js +34 -34
- package/_site/_test/module-terminal-restart-test.js +63 -63
- package/_site/_test/module-wiring-unbind-race-test.js +59 -59
- package/_site/_test/native-context-integrations-test.js +86 -86
- package/_site/_test/non-release-ci-test.js +34 -34
- package/_site/_test/offline-boundary-isolation-test.js +32 -32
- package/_site/_test/orphaned-claim-recovery-test.js +80 -80
- package/_site/_test/package-boundary-test.js +43 -43
- package/_site/_test/package-startup-test.js +56 -56
- package/_site/_test/packaged-ui-smoke-test.js +88 -88
- package/_site/_test/parallel-ci-r9-posix-socket-budget-test.js +156 -0
- package/_site/_test/parallel-engine-advanced-eng005-test.js +136 -0
- package/_site/_test/parallel-engine-advanced-eng006-test.js +171 -0
- package/_site/_test/parallel-engine-advanced-eng007-reconcile-test.js +231 -0
- package/_site/_test/parallel-engine-advanced-eng009-test.js +208 -0
- package/_site/_test/parallel-engine-advanced-eng010-test.js +213 -0
- package/_site/_test/parallel-engine-advanced-eng011-ui-probe.js +459 -0
- package/_site/_test/parallel-engine-final-eng002-test.js +255 -0
- package/_site/_test/parallel-engine-final-eng007-test.js +269 -0
- package/_site/_test/parallel-engine-final-eng011-test.js +197 -0
- package/_site/_test/parallel-engine-final-eng012-test.js +354 -0
- package/_site/_test/parallel-knowledge-client-error-code-test.js +184 -0
- package/_site/_test/parallel-knowledge-engine-test.js +353 -0
- package/_site/_test/parallel-knowledge-retrieval-test.js +292 -0
- package/_site/_test/parallel-knowledge-vector-test.js +168 -0
- package/_site/_test/parallel-linux-ci-r7-log007-day-test.js +110 -0
- package/_site/_test/parallel-logging-acceptance-log003-test.js +224 -0
- package/_site/_test/parallel-logging-acceptance-log004-test.js +231 -0
- package/_site/_test/parallel-logging-acceptance-log005-test.js +208 -0
- package/_site/_test/parallel-logging-acceptance-log006-test.js +214 -0
- package/_site/_test/parallel-logging-acceptance-log007-test.js +223 -0
- package/_site/_test/parallel-logging-acceptance-log008-test.js +180 -0
- package/_site/_test/parallel-logging-fix-r2-crash-probe.js +271 -0
- package/_site/_test/parallel-logging-fix-r2-gap-test.js +294 -0
- package/_site/_test/parallel-logging-fix-r2-normalhook-test.js +330 -0
- package/_site/_test/parallel-logging-integration-5025-portability-test.js +554 -0
- package/_site/_test/parallel-memory-candidate-test.js +301 -0
- package/_site/_test/parallel-memory-manual-test.js +197 -0
- package/_site/_test/parallel-memory-scope-test.js +228 -0
- package/_site/_test/parallel-memory-source-test.js +227 -0
- package/_site/_test/parallel-memory-sync-test.js +238 -0
- package/_site/_test/parallel-memory-view-r8-reload-gate-test.js +123 -0
- package/_site/_test/parallel-nav-failure-test.js +119 -0
- package/_site/_test/parallel-nav-isolation-test.js +308 -0
- package/_site/_test/parallel-nav-isolation-vector-path-test.js +90 -0
- package/_site/_test/parallel-nav-offline-test.js +272 -0
- package/_site/_test/parallel-nav-operability-test.js +257 -0
- package/_site/_test/parallel-nav-switch-test.js +247 -0
- package/_site/_test/parallel-nav-theme-test.js +130 -0
- package/_site/_test/parallel-nav-viewport-test.js +310 -0
- package/_site/_test/parallel-qa-engine-endpoint-guard-test.js +273 -0
- package/_site/_test/parallel-retrieval-followup-citation-test.js +219 -0
- package/_site/_test/parallel-retrieval-followup-corpus-test.js +287 -0
- package/_site/_test/parallel-workbench-independent-host.js +262 -0
- package/_site/_test/parallel-workbench-integration-5025-abort-probe.js +340 -0
- package/_site/_test/parallel-workbench-integration-5025-drafts-probe.js +588 -0
- package/_site/_test/parallel-workbench-integration-5025-env-check.js +317 -0
- package/_site/_test/parallel-workbench-integration-5025-model-probe.js +342 -0
- package/_site/_test/parallel-workbench-integration-5025-ordering-probe.js +326 -0
- package/_site/_test/parallel-workbench-model-probe.js +336 -0
- package/_site/_test/parallel-workbench-offline-test.js +169 -0
- package/_site/_test/parallel-workbench-provenance-trace.js +134 -0
- package/_site/_test/parallel-workbench-source-match.js +206 -0
- package/_site/_test/parallel-workbench-standalone-turn.js +349 -0
- package/_site/_test/path-consistency-test.js +151 -151
- package/_site/_test/pending-sweep-test.js +9 -9
- package/_site/_test/post-commit-automation-test.js +87 -87
- package/_site/_test/pr-consumer-contract-test.js +55 -55
- package/_site/_test/product-brand-contract-test.js +25 -25
- package/_site/_test/product-diagnostics-ui-test.js +93 -93
- package/_site/_test/product-empty-state-test.js +148 -0
- package/_site/_test/product-import-ui-test.js +219 -104
- package/_site/_test/product-navigation-test.js +152 -0
- package/_site/_test/product-progress-status-test.js +84 -84
- package/_site/_test/project-delete-recovery-test.js +64 -64
- package/_site/_test/project-domain-create-test.js +91 -0
- package/_site/_test/project-domain-scope-retrieval-test.js +214 -0
- package/_site/_test/project-git-init-confirm-test.js +115 -0
- package/_site/_test/project-goal-editor-test.js +144 -144
- package/_site/_test/project-import-duplicate-alias-test.js +134 -0
- package/_site/_test/project-import-unicode-restart-test.js +140 -0
- package/_site/_test/project-lifecycle-transaction-test.js +106 -106
- package/_site/_test/project-module-partial-recovery-test.js +222 -0
- package/_site/_test/project-remove-running-guard-test.js +79 -79
- package/_site/_test/project-store-test.js +90 -90
- package/_site/_test/project-unbind-late-read-ui-test.js +250 -0
- package/_site/_test/project-unbind-writer-guard-test.js +231 -0
- package/_site/_test/prompt-settings-test.js +115 -115
- package/_site/_test/protected-architecture-gate-test.js +122 -122
- package/_site/_test/refactor-characterization-test.js +36 -36
- package/_site/_test/registry-events-test.js +38 -38
- package/_site/_test/release-version-sync-test.js +115 -115
- package/_site/_test/requirement-binding-test.js +113 -113
- package/_site/_test/requirement-recorder-test.js +173 -173
- package/_site/_test/run-all-tests.js +173 -161
- package/_site/_test/runtime-endpoint-test.js +64 -64
- package/_site/_test/scanner-test.js +83 -83
- package/_site/_test/server-security-test.js +96 -96
- package/_site/_test/sessions-stream-test.js +93 -93
- package/_site/_test/shared-contracts-test.js +58 -58
- package/_site/_test/shared-scope-root-binding-test.js +74 -67
- package/_site/_test/simple-import-test.js +98 -98
- package/_site/_test/startup-analysis-disabled-test.js +16 -16
- package/_site/_test/storage-foundation-test.js +78 -78
- package/_site/_test/structured-logger-test.js +101 -101
- package/_site/_test/t07b-cln-005-coverage-test.js +212 -0
- package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
- package/_site/_test/team-knowledge-store-test.js +132 -132
- package/_site/_test/test-server-cleanup-test.js +35 -35
- package/_site/_test/test-server-module-isolation-test.js +37 -37
- package/_site/_test/tracking-start-test.js +144 -144
- package/_site/_test/vector-storage-root-test.js +75 -75
- package/_site/_test/version-visibility-browser-probe.js +249 -0
- package/_site/_test/version-visibility-test.js +92 -0
- package/_site/_test/workbench-context-receipts-test.js +22 -22
- package/_site/_test/workbench-permission-test.js +117 -117
- package/_site/_test/zcode-capture-integration-test.js +112 -112
- package/_site/lib/ai-profile-resolver.js +78 -78
- package/_site/lib/ai-vendor-presets.js +83 -83
- package/_site/lib/ai-workspace.js +104 -104
- package/_site/lib/atomic-file.js +204 -204
- package/_site/lib/automation-config.js +140 -140
- package/_site/lib/bridge-adapter.js +260 -260
- package/_site/lib/bridge-consumer-service.js +402 -402
- package/_site/lib/capture-hook-config.js +108 -108
- package/_site/lib/claude-cli-runner.js +1599 -1599
- package/_site/lib/cli-instances.js +43 -43
- package/_site/lib/codex-context-status.js +17 -17
- package/_site/lib/codex-notify-config.js +193 -193
- package/_site/lib/commit-conversation-binder.js +247 -247
- package/_site/lib/commit-processing-ledger.js +27 -27
- package/_site/lib/commit-prompt.js +202 -202
- package/_site/lib/commit-reconciler.js +444 -444
- package/_site/lib/context-pack-builder.js +299 -299
- package/_site/lib/contracts.js +291 -291
- package/_site/lib/conversation-exclusions.js +204 -204
- package/_site/lib/conversation-query-service.js +161 -161
- package/_site/lib/conversation-store.js +387 -387
- package/_site/lib/data-dir.js +13 -13
- package/_site/lib/embedding-service.js +85 -85
- package/_site/lib/engine-connection.js +93 -93
- package/_site/lib/engine-knowledge-client.js +76 -72
- package/_site/lib/evidence-bundle.js +228 -228
- package/_site/lib/folder-picker-output.js +24 -24
- package/_site/lib/git-path.js +14 -14
- package/_site/lib/git-runner.js +62 -62
- package/_site/lib/github-team-store.js +1175 -1175
- package/_site/lib/hook-manager.js +203 -203
- package/_site/lib/index-builder.js +157 -157
- package/_site/lib/index-service.js +173 -173
- package/_site/lib/integration-auto-repair.js +77 -77
- package/_site/lib/integration-installer.js +1240 -1240
- package/_site/lib/integration-intent.js +88 -88
- package/_site/lib/integration-status-scan.js +99 -99
- package/_site/lib/integration-status-worker.js +14 -14
- package/_site/lib/kb-framework.js +191 -191
- package/_site/lib/kb-validator.js +124 -124
- package/_site/lib/knowledge-db.js +363 -363
- package/_site/lib/knowledge-promotion.js +435 -435
- package/_site/lib/knowledge-retrieval-service.js +401 -401
- package/_site/lib/knowledge-schema.js +121 -121
- package/_site/lib/knowledge-tool-runtime.js +366 -366
- package/_site/lib/live-gateway.js +78 -78
- package/_site/lib/llm-client.js +173 -173
- package/_site/lib/markdown-knowledge-indexer.js +230 -230
- package/_site/lib/model-context-windows.js +82 -82
- package/_site/lib/module-bridge.js +874 -874
- package/_site/lib/module-port-resolver.js +57 -57
- package/_site/lib/post-commit-automation.js +141 -141
- package/_site/lib/product-data.js +35 -35
- package/_site/lib/project-lifecycle-service.js +554 -554
- package/_site/lib/project-registry-store.js +129 -129
- package/_site/lib/project-store.js +268 -268
- package/_site/lib/prompt-registry.js +148 -148
- package/_site/lib/public-version-info.js +37 -0
- package/_site/lib/registry-events.js +16 -16
- package/_site/lib/requirement-adapters.js +10 -10
- package/_site/lib/requirement-binder.js +112 -112
- package/_site/lib/requirement-recorder.js +278 -278
- package/_site/lib/runtime-control.js +89 -89
- package/_site/lib/runtime-endpoint.js +160 -160
- package/_site/lib/scanner.js +298 -298
- package/_site/lib/server-app.js +1672 -1664
- package/_site/lib/settings-store.js +153 -153
- package/_site/lib/storage-layout.js +166 -166
- package/_site/lib/structured-logger.js +574 -574
- package/_site/lib/zcode-context-integration.js +60 -60
- package/_site/scripts/codex-notify-fanout.cjs +95 -95
- package/_site/scripts/folder-picker.ps1 +156 -156
- package/_site/scripts/hook-trigger.js +158 -158
- package/_site/scripts/install-module-candidates.js +69 -69
- package/_site/scripts/pack-module-candidates.js +89 -89
- package/_site/scripts/sync-product-content.js +28 -28
- package/_site/scripts/sync-release-version.js +164 -164
- package/_site/scripts/vendor-modules.js +49 -49
- package/_site/scripts/verify-module-runtime.js +50 -50
- package/_site/scripts/verify-product-runtime.js +36 -36
- package/_site/scripts/verify-retrieval-gold.js +83 -83
- package/_site/server.js +15 -15
- package/_site/start.bat +26 -26
- package/_site/stop.bat +11 -11
- package/bin/project-knowledge-integrations.js +254 -254
- package/bin/project-knowledge-kb.js +67 -67
- package/bin/project-knowledge-mcp.js +201 -201
- package/bin/project-knowledge.js +269 -170
- package/claude-prompts.json +21 -21
- package/docs/README.zh-CN.md +207 -205
- package/docs/assets/architecture.svg +181 -181
- package/docs/assets/logo.svg +49 -49
- package/docs/fresh-v5-start.md +44 -44
- package/docs/leader-glm-workbench-playbook.md +122 -122
- package/docs/project-registry-schema.md +33 -33
- package/docs/release-5.0.16-acceptance.md +176 -176
- package/docs/release-5.0.17-acceptance.md +117 -117
- package/docs/release-5.0.19-acceptance.md +39 -39
- package/docs/team-knowledge-mode-a-plan.md +73 -73
- package/docs/testing-strategy.md +33 -33
- package/module-runtime-manifest.json +66 -63
- package/package.json +113 -110
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/templates/change.md +30 -30
- package/templates/project-readme.md +52 -52
- package/ui/index.html +3 -2
- package/ui/version-info.css +44 -0
- package/ui/version-info.js +24 -0
- package/vendor-manifest.json +55 -55
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Lane-owned fixture helpers for the parallel knowledge verification batch.
|
|
3
|
+
// Portability contract: nothing here may hard-code a developer path.
|
|
4
|
+
// The temp root is resolved as PK_TEST_TEMP_ROOT -> TEMP/TMP/TMPDIR ->
|
|
5
|
+
// os.tmpdir(), and every artefact is created with mkdtemp outside the
|
|
6
|
+
// repository, so the same helpers work on a developer machine and on CI.
|
|
7
|
+
// Isolation contract: the helpers themselves clear the ambient module URLs
|
|
8
|
+
// and the live data directories, so a test entry point never depends on the
|
|
9
|
+
// caller remembering `env -u`, and never reaches the developer's real
|
|
10
|
+
// 5757/5790/8787/8790 endpoints or the real HOME.
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const os = require('node:os');
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
const crypto = require('node:crypto');
|
|
15
|
+
const net = require('node:net');
|
|
16
|
+
const { execFileSync, spawn } = require('node:child_process');
|
|
17
|
+
|
|
18
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
|
|
19
|
+
|
|
20
|
+
// The developer's live surfaces. A test may never bind or talk to them.
|
|
21
|
+
const FORBIDDEN_PORTS = new Set([5757, 5790, 8787, 8790]);
|
|
22
|
+
|
|
23
|
+
// Ambient module addresses that would otherwise point a test at a live
|
|
24
|
+
// instance. The product falls back to them when no explicit URL is given.
|
|
25
|
+
const AMBIENT_MODULE_URLS = [
|
|
26
|
+
'KB_KNOWLEDGE_ENGINE_URL',
|
|
27
|
+
'KB_VECTORHUB_URL',
|
|
28
|
+
'KB_TERMINAL_URL',
|
|
29
|
+
'KB_EVENTBRIDGE_URL',
|
|
30
|
+
'KB_EVENT_BRIDGE_URL',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function isInside(candidate, parent) {
|
|
34
|
+
const relative = path.relative(parent, candidate);
|
|
35
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveTempRoot() {
|
|
39
|
+
const candidates = [process.env.PK_TEST_TEMP_ROOT, process.env.TEMP, process.env.TMP, process.env.TMPDIR];
|
|
40
|
+
const chosen = candidates.find(value => value && String(value).trim());
|
|
41
|
+
const root = path.resolve(chosen ? String(chosen).trim() : os.tmpdir());
|
|
42
|
+
if (isInside(root, REPO_ROOT)) {
|
|
43
|
+
throw new Error(`refusing to use a temp root inside the repository: ${root}`);
|
|
44
|
+
}
|
|
45
|
+
return root;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ensureTempRoot() {
|
|
49
|
+
const root = resolveTempRoot();
|
|
50
|
+
fs.mkdirSync(root, { recursive: true });
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeTempDir(prefix = 'parallel-knowledge') {
|
|
55
|
+
const root = ensureTempRoot();
|
|
56
|
+
const dir = fs.mkdtempSync(path.join(root, `${prefix}-`));
|
|
57
|
+
if (isInside(dir, REPO_ROOT)) throw new Error(`refusing to create a fixture inside the repository: ${dir}`);
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function git(cwd, args) {
|
|
62
|
+
return execFileSync('git', args, { cwd, windowsHide: true, encoding: 'utf8' }).trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function sha256Buffer(value) {
|
|
66
|
+
return crypto.createHash('sha256').update(value).digest('hex');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sha256File(filePath) {
|
|
70
|
+
return fs.existsSync(filePath) ? sha256Buffer(fs.readFileSync(filePath)) : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// A real Git worktree with real commits. `files` are committed in order.
|
|
74
|
+
function makeRepo(root, name, commits = []) {
|
|
75
|
+
const repoPath = path.join(root, name);
|
|
76
|
+
fs.mkdirSync(repoPath, { recursive: true });
|
|
77
|
+
git(repoPath, ['init', '-q']);
|
|
78
|
+
git(repoPath, ['config', 'user.email', 'parallel-knowledge@local.invalid']);
|
|
79
|
+
git(repoPath, ['config', 'user.name', 'Parallel Knowledge Test']);
|
|
80
|
+
git(repoPath, ['config', 'commit.gpgsign', 'false']);
|
|
81
|
+
const shas = [];
|
|
82
|
+
for (const commit of commits) {
|
|
83
|
+
for (const [file, content] of Object.entries(commit.files || {})) {
|
|
84
|
+
const target = path.join(repoPath, file);
|
|
85
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
86
|
+
fs.writeFileSync(target, content, 'utf8');
|
|
87
|
+
}
|
|
88
|
+
git(repoPath, ['add', '-A']);
|
|
89
|
+
git(repoPath, ['commit', '-q', '-m', commit.message || 'fixture commit']);
|
|
90
|
+
shas.push(git(repoPath, ['rev-parse', 'HEAD']));
|
|
91
|
+
}
|
|
92
|
+
return { repoPath, shas, head: shas[shas.length - 1] || null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function makeKnowledgeDir(root, name) {
|
|
96
|
+
const knowledgePath = path.join(root, name);
|
|
97
|
+
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
98
|
+
return knowledgePath;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Relative path -> sha256 for every file below `dir`.
|
|
102
|
+
function hashTree(dir) {
|
|
103
|
+
const out = {};
|
|
104
|
+
if (!fs.existsSync(dir)) return out;
|
|
105
|
+
const walk = current => {
|
|
106
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
107
|
+
const absolute = path.join(current, entry.name);
|
|
108
|
+
if (entry.isDirectory()) walk(absolute);
|
|
109
|
+
else out[path.relative(dir, absolute).replace(/\\/g, '/')] = sha256File(absolute);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
walk(dir);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function assertAllowedPort(port, what = 'port') {
|
|
117
|
+
if (FORBIDDEN_PORTS.has(Number(port))) {
|
|
118
|
+
throw new Error(`refusing to use the live ${what} ${port}`);
|
|
119
|
+
}
|
|
120
|
+
return Number(port);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function assertAllowedEndpoint(url, what = 'endpoint') {
|
|
124
|
+
const parsed = new URL(String(url));
|
|
125
|
+
const port = parsed.port ? Number(parsed.port) : (parsed.protocol === 'https:' ? 443 : 80);
|
|
126
|
+
if (FORBIDDEN_PORTS.has(port)) throw new Error(`refusing to contact the live ${what} ${url}`);
|
|
127
|
+
return url;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function freePort() {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const server = net.createServer();
|
|
133
|
+
server.unref();
|
|
134
|
+
server.on('error', reject);
|
|
135
|
+
server.listen(0, '127.0.0.1', () => {
|
|
136
|
+
const { port } = server.address();
|
|
137
|
+
server.close(() => {
|
|
138
|
+
try { resolve(assertAllowedPort(port)); } catch (error) { reject(error); }
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function waitForHttp(url, timeoutMs = 20000) {
|
|
145
|
+
assertAllowedEndpoint(url, 'service');
|
|
146
|
+
const until = Date.now() + timeoutMs;
|
|
147
|
+
let lastError = null;
|
|
148
|
+
while (Date.now() < until) {
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(2000) });
|
|
151
|
+
if (response.ok) return true;
|
|
152
|
+
lastError = new Error(`HTTP ${response.status}`);
|
|
153
|
+
} catch (error) { lastError = error; }
|
|
154
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Timed out waiting for ${url}: ${lastError && lastError.message}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// The environment a child may inherit: no ambient module address, no live
|
|
160
|
+
// data directory, no real HOME.
|
|
161
|
+
function sanitizedEnv(extra = {}) {
|
|
162
|
+
const env = { ...process.env, ...extra };
|
|
163
|
+
for (const key of AMBIENT_MODULE_URLS) delete env[key];
|
|
164
|
+
return env;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function spawnNode(args, env = {}) {
|
|
168
|
+
const child = spawn(process.execPath, args, {
|
|
169
|
+
env: sanitizedEnv(env),
|
|
170
|
+
windowsHide: true,
|
|
171
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
172
|
+
});
|
|
173
|
+
const output = { stdout: '', stderr: '' };
|
|
174
|
+
child.stdout.on('data', chunk => { output.stdout += chunk; });
|
|
175
|
+
child.stderr.on('data', chunk => { output.stderr += chunk; });
|
|
176
|
+
const exited = new Promise(resolve => child.once('close', code => resolve({ code, ...output })));
|
|
177
|
+
return { child, exited, output };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function eventually(fn, timeoutMs = 15000, intervalMs = 25) {
|
|
181
|
+
const until = Date.now() + timeoutMs;
|
|
182
|
+
while (Date.now() < until) {
|
|
183
|
+
if (await fn()) return true;
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function removeTree(dir) {
|
|
190
|
+
if (!dir) return;
|
|
191
|
+
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Isolate the current process. This MUST run before any product module is
|
|
195
|
+
// required, because the product reads its data directories and module
|
|
196
|
+
// addresses from the environment. It returns the isolated paths so the
|
|
197
|
+
// caller can clean them up.
|
|
198
|
+
function isolateProcess() {
|
|
199
|
+
if (isolateProcess.done) return isolateProcess.state;
|
|
200
|
+
const root = makeTempDir('parallel-knowledge-isolate');
|
|
201
|
+
const dataHome = path.join(root, 'kb-data');
|
|
202
|
+
const knowledgeHome = path.join(root, 'kb-knowledge-data');
|
|
203
|
+
const bridgeHome = path.join(root, 'event-bridge-home');
|
|
204
|
+
const home = path.join(root, 'home');
|
|
205
|
+
for (const dir of [dataHome, knowledgeHome, bridgeHome, home]) fs.mkdirSync(dir, { recursive: true });
|
|
206
|
+
|
|
207
|
+
for (const key of AMBIENT_MODULE_URLS) delete process.env[key];
|
|
208
|
+
process.env.KB_DATA_DIR = dataHome;
|
|
209
|
+
process.env.KB_KNOWLEDGE_DATA_DIR = knowledgeHome;
|
|
210
|
+
process.env.AI_CODING_EVENT_BRIDGE_HOME = bridgeHome;
|
|
211
|
+
process.env.KB_MODULES_AUTOSTART = '0';
|
|
212
|
+
|
|
213
|
+
// The real HOME must never be read or written by a lane test.
|
|
214
|
+
process.env.HOME = home;
|
|
215
|
+
process.env.USERPROFILE = home;
|
|
216
|
+
const parsed = path.parse(home);
|
|
217
|
+
process.env.HOMEDRIVE = parsed.root;
|
|
218
|
+
process.env.HOMEPATH = parsed.root === path.sep ? home.slice(1) : home.slice(parsed.root.length);
|
|
219
|
+
|
|
220
|
+
isolateProcess.state = { root, dataHome, knowledgeHome, bridgeHome, home };
|
|
221
|
+
isolateProcess.done = true;
|
|
222
|
+
return isolateProcess.state;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function cleanupIsolatedProcess() {
|
|
226
|
+
if (!isolateProcess.done) return;
|
|
227
|
+
removeTree(isolateProcess.state.root);
|
|
228
|
+
isolateProcess.done = false;
|
|
229
|
+
isolateProcess.state = null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
REPO_ROOT,
|
|
234
|
+
FORBIDDEN_PORTS,
|
|
235
|
+
AMBIENT_MODULE_URLS,
|
|
236
|
+
resolveTempRoot,
|
|
237
|
+
ensureTempRoot,
|
|
238
|
+
makeTempDir,
|
|
239
|
+
makeRepo,
|
|
240
|
+
makeKnowledgeDir,
|
|
241
|
+
hashTree,
|
|
242
|
+
sha256File,
|
|
243
|
+
sha256Buffer,
|
|
244
|
+
git,
|
|
245
|
+
freePort,
|
|
246
|
+
waitForHttp,
|
|
247
|
+
spawnNode,
|
|
248
|
+
sanitizedEnv,
|
|
249
|
+
eventually,
|
|
250
|
+
removeTree,
|
|
251
|
+
assertAllowedPort,
|
|
252
|
+
assertAllowedEndpoint,
|
|
253
|
+
isolateProcess,
|
|
254
|
+
cleanupIsolatedProcess,
|
|
255
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LOG-ACCEPTANCE-01 lane-exclusive fixture (prefix parallel-logging-acceptance-*).
|
|
3
|
+
//
|
|
4
|
+
// Builds an isolated product data dir with N registered projects and
|
|
5
|
+
// pre-written structured log records, mirroring helpers/logging-ui-fixture.js
|
|
6
|
+
// (read-only reference) but with multi-project isolation support for
|
|
7
|
+
// LOG-003/LOG-005/LOG-006. All directories live under the lane temp root the
|
|
8
|
+
// driver installed into TEMP/TMP/TMPDIR (D:/SanQian.Xu/.project-knowledge-test-temp),
|
|
9
|
+
// never the developer's real HOME.
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const { StorageLayout } = require('../../lib/storage-layout');
|
|
14
|
+
const { SettingsStore } = require('../../lib/settings-store');
|
|
15
|
+
const { ProjectRegistryStore } = require('../../lib/project-registry-store');
|
|
16
|
+
const { ProjectStore } = require('../../lib/project-store');
|
|
17
|
+
const { Logger } = require('../../lib/structured-logger');
|
|
18
|
+
const { controlPath } = require('../../lib/runtime-control');
|
|
19
|
+
const { makeRepo } = require('../fixtures/make-git-repos');
|
|
20
|
+
|
|
21
|
+
// The product's runtime-control listens on a filesystem unix socket at
|
|
22
|
+
// <dataDir>/runtime-control.sock on POSIX (runtime-control.js:14; Windows uses
|
|
23
|
+
// a hashed named pipe). Linux sun_path allows 107 chars (macOS 103), so the
|
|
24
|
+
// dataDir path this fixture builds under os.tmpdir() must leave room for the
|
|
25
|
+
// 21-char socket name. CI 40075242420 (linux24): the old 'pk-logging-acc-'
|
|
26
|
+
// prefix made the logfixr2-normal dataDir 88 chars -> a 109-char socket ->
|
|
27
|
+
// net.listen refused it, the server exited 1 before the health gate with empty
|
|
28
|
+
// pipes, and normalhook timed out. 'pk-la-' keeps every lane case <= 100 even
|
|
29
|
+
// on the CI temp root (/home/runner/work/_temp, 23 chars).
|
|
30
|
+
const POSIX_SOCKET_CHAR_LIMIT = 103; // strictest common budget (macOS); all lane cases fit
|
|
31
|
+
|
|
32
|
+
function assertPosixSocketBudget(dataDir) {
|
|
33
|
+
if (process.platform === 'win32') return dataDir; // named pipe, no sun_path
|
|
34
|
+
const socketPath = controlPath(dataDir);
|
|
35
|
+
if (socketPath.length > POSIX_SOCKET_CHAR_LIMIT) {
|
|
36
|
+
throw new Error(`fixture dataDir makes the POSIX control socket ${socketPath.length} chars (limit ${POSIX_SOCKET_CHAR_LIMIT}): ${dataDir}`);
|
|
37
|
+
}
|
|
38
|
+
return dataDir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function createLoggingAcceptanceFixture(options = {}) {
|
|
42
|
+
const caseId = options.caseId || 'case';
|
|
43
|
+
const dataDir = assertPosixSocketBudget(fs.mkdtempSync(path.join(require('node:os').tmpdir(), `pk-la-${caseId}-`)));
|
|
44
|
+
if (options.freshProduct !== false) require('../../lib/product-data').initializeProductData(dataDir);
|
|
45
|
+
const knowledgeRoot = path.join(dataDir, 'knowledge-root');
|
|
46
|
+
fs.mkdirSync(knowledgeRoot, { recursive: true });
|
|
47
|
+
const layout = new StorageLayout({ dataDir });
|
|
48
|
+
const settingsStore = new SettingsStore({ layout });
|
|
49
|
+
const registryStore = new ProjectRegistryStore({ layout });
|
|
50
|
+
const projectStore = new ProjectStore({ layout });
|
|
51
|
+
|
|
52
|
+
await settingsStore.initialize({
|
|
53
|
+
knowledge: { rootPath: knowledgeRoot },
|
|
54
|
+
logging: { levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'], retentionDays: 365, maxTotalSizeMB: 2048 },
|
|
55
|
+
});
|
|
56
|
+
await registryStore.initialize();
|
|
57
|
+
|
|
58
|
+
const projects = [];
|
|
59
|
+
for (const spec of options.projects || []) {
|
|
60
|
+
const repo = makeRepo({ kind: 'one-commit' });
|
|
61
|
+
const knowledgePath = path.join(knowledgeRoot, spec.projectId);
|
|
62
|
+
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
63
|
+
await projectStore.create(spec.projectId, {
|
|
64
|
+
displayName: spec.displayName,
|
|
65
|
+
storageName: spec.projectId,
|
|
66
|
+
repoPath: repo.path,
|
|
67
|
+
knowledgePath,
|
|
68
|
+
}, {
|
|
69
|
+
trackingStartCommit: repo.headCommit,
|
|
70
|
+
lastAnalyzedCommit: repo.headCommit,
|
|
71
|
+
hook: { managedVersion: 2, migrationVersion: 2, lastVerifiedAt: new Date().toISOString() },
|
|
72
|
+
index: { dirty: false, generation: 1 },
|
|
73
|
+
});
|
|
74
|
+
await registryStore.add(spec.projectId, { displayName: spec.displayName });
|
|
75
|
+
projects.push({ projectId: spec.projectId, displayName: spec.displayName, repo, knowledgePath, headCommit: repo.headCommit });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const settings = settingsStore.read().logging;
|
|
79
|
+
const logger = new Logger({ layout, settingsProvider: () => settings });
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
caseId,
|
|
83
|
+
dataDir,
|
|
84
|
+
layout,
|
|
85
|
+
projects,
|
|
86
|
+
logger,
|
|
87
|
+
cleanup() {
|
|
88
|
+
for (const project of projects) { try { project.repo.cleanup(); } catch {} }
|
|
89
|
+
try { fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); } catch {}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Fixture-time log writer: records carry the caller's fields verbatim so
|
|
95
|
+
// write-time redaction (LOG-006) happens through the real normalizeRecord path.
|
|
96
|
+
async function writeProjectLogs(logger, projectId, records) {
|
|
97
|
+
for (const record of records) {
|
|
98
|
+
await logger.log(record.level || 'info', record.event, record.message, {
|
|
99
|
+
projectId,
|
|
100
|
+
projectSlug: projectId,
|
|
101
|
+
projectDisplayName: record.projectDisplayName || projectId,
|
|
102
|
+
operationId: record.operationId || '',
|
|
103
|
+
component: record.component || 'acceptance-driver',
|
|
104
|
+
commitSha: record.commitSha || '',
|
|
105
|
+
context: record.context || {},
|
|
106
|
+
...(record.error ? { error: record.error } : {}),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { createLoggingAcceptanceFixture, writeProjectLogs, assertPosixSocketBudget, POSIX_SOCKET_CHAR_LIMIT };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LOG-ACCEPTANCE-01 lane-exclusive observation proxy (prefix
|
|
3
|
+
// parallel-logging-acceptance-*). The real product page points at this proxy;
|
|
4
|
+
// the proxy forwards to the isolated server. It exists only to observe and
|
|
5
|
+
// shape the network the real browser sees:
|
|
6
|
+
// * count open connections per pathname prefix (LOG-004: the diagnostics
|
|
7
|
+
// dialog's SSE stream must actually disconnect when the dialog closes);
|
|
8
|
+
// * optionally delay a pathname prefix (LOG-008: the real 3s UI read timeout
|
|
9
|
+
// fires against a slow /api/logs).
|
|
10
|
+
// SSE responses are piped unbuffered both ways - the page's EventSource
|
|
11
|
+
// behaves exactly as against the origin server.
|
|
12
|
+
|
|
13
|
+
const http = require('node:http');
|
|
14
|
+
const net = require('node:net');
|
|
15
|
+
|
|
16
|
+
// One fresh connection per forwarded request: avoids keep-alive reset races
|
|
17
|
+
// between the proxy and the isolated origin during bursty page loads.
|
|
18
|
+
const proxyAgent = new http.Agent({ keepAlive: false });
|
|
19
|
+
|
|
20
|
+
function createObservationProxy({ targetPort, targetHost = '127.0.0.1', port, host = '127.0.0.1' }) {
|
|
21
|
+
const state = {
|
|
22
|
+
activeByPrefix: new Map(),
|
|
23
|
+
requests: [],
|
|
24
|
+
upgrades: [],
|
|
25
|
+
delays: new Map(),
|
|
26
|
+
setDelay(prefix, ms) { if (ms === null) state.delays.delete(prefix); else state.delays.set(prefix, ms); },
|
|
27
|
+
snapshot() {
|
|
28
|
+
return {
|
|
29
|
+
activeByPrefix: Object.fromEntries(state.activeByPrefix),
|
|
30
|
+
requests: state.requests.slice(-200),
|
|
31
|
+
upgrades: state.upgrades.slice(-200),
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const server = http.createServer((req, res) => {
|
|
37
|
+
const pathname = String(req.url || '').split('?')[0];
|
|
38
|
+
const entry = { method: req.method, pathname, url: String(req.url || ''), openedAt: new Date().toISOString(), status: null, closedAt: null };
|
|
39
|
+
state.requests.push(entry);
|
|
40
|
+
for (const [prefix] of state.activeByPrefix) {
|
|
41
|
+
if (pathname.startsWith(prefix)) state.activeByPrefix.set(prefix, state.activeByPrefix.get(prefix) + 1);
|
|
42
|
+
}
|
|
43
|
+
const trackedPrefix = [...state.activeByPrefix.keys()].find(prefix => pathname.startsWith(prefix));
|
|
44
|
+
const closeTracking = () => {
|
|
45
|
+
if (entry.closedAt) return;
|
|
46
|
+
entry.closedAt = new Date().toISOString();
|
|
47
|
+
if (trackedPrefix) state.activeByPrefix.set(trackedPrefix, Math.max(0, state.activeByPrefix.get(trackedPrefix) - 1));
|
|
48
|
+
};
|
|
49
|
+
res.on('close', closeTracking);
|
|
50
|
+
|
|
51
|
+
const forward = () => {
|
|
52
|
+
const upstream = http.request({ host: targetHost, port: targetPort, method: req.method, path: req.url, agent: proxyAgent, headers: { ...req.headers, host: `${targetHost}:${targetPort}` } }, upstreamRes => {
|
|
53
|
+
entry.status = upstreamRes.statusCode;
|
|
54
|
+
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers);
|
|
55
|
+
upstreamRes.pipe(res);
|
|
56
|
+
});
|
|
57
|
+
upstream.on('error', error => { entry.status = 502; entry.error = String(error.message || error); if (!res.headersSent) res.writeHead(502, { 'content-type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { message: 'proxy upstream failure' } })); });
|
|
58
|
+
req.pipe(upstream);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const delay = [...state.delays.entries()].find(([prefix]) => pathname.startsWith(prefix));
|
|
62
|
+
if (delay) setTimeout(forward, delay[1]);
|
|
63
|
+
else forward();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// WebSocket passthrough (the product page multiplexes /api/live over WS);
|
|
67
|
+
// host/origin are rewritten to the target so the server's same-origin
|
|
68
|
+
// security check still passes while the page stays pointed at the proxy.
|
|
69
|
+
server.on('upgrade', (req, socket, head) => {
|
|
70
|
+
const pathname = String(req.url || '').split('?')[0];
|
|
71
|
+
const entry = { method: 'UPGRADE', pathname, url: String(req.url || ''), openedAt: new Date().toISOString(), closedAt: null };
|
|
72
|
+
state.upgrades.push(entry);
|
|
73
|
+
const target = `${targetHost}:${targetPort}`;
|
|
74
|
+
const headers = [];
|
|
75
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
76
|
+
const name = req.rawHeaders[i].toLowerCase();
|
|
77
|
+
const value = name === 'host' || name === 'origin' ? (name === 'host' ? target : `http://${target}`) : req.rawHeaders[i + 1];
|
|
78
|
+
headers.push(`${req.rawHeaders[i]}: ${value}`);
|
|
79
|
+
}
|
|
80
|
+
const upstream = net.connect(targetPort, targetHost, () => {
|
|
81
|
+
upstream.write(`${req.method} ${req.url} HTTP/1.1\r\n${headers.join('\r\n')}\r\n\r\n`);
|
|
82
|
+
if (head && head.length) upstream.write(head);
|
|
83
|
+
socket.pipe(upstream);
|
|
84
|
+
upstream.pipe(socket);
|
|
85
|
+
});
|
|
86
|
+
const kill = () => { entry.closedAt = entry.closedAt || new Date().toISOString(); socket.destroy(); upstream.destroy(); };
|
|
87
|
+
socket.on('error', kill);
|
|
88
|
+
upstream.on('error', kill);
|
|
89
|
+
socket.on('close', kill);
|
|
90
|
+
upstream.on('close', kill);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return new Promise(resolve => server.listen(port, host, () => resolve({
|
|
94
|
+
port: server.address().port,
|
|
95
|
+
url: `http://${host}:${server.address().port}`,
|
|
96
|
+
state,
|
|
97
|
+
trackPrefix(prefix) { state.activeByPrefix.set(prefix, 0); },
|
|
98
|
+
setDelay(prefix, ms) { state.setDelay(prefix, ms); },
|
|
99
|
+
async close() {
|
|
100
|
+
server.closeAllConnections?.();
|
|
101
|
+
await new Promise(done => server.close(done));
|
|
102
|
+
},
|
|
103
|
+
})));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { createObservationProxy };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-LOG-SAFETY-R2 lane-exclusive observation proxy (prefix
|
|
3
|
+
// parallel-logging-fix-r2-*). Distinct from the R1 proxy (read-only reuse of
|
|
4
|
+
// that file is not extended): this one adds a SNAPSHOT BARRIER for the
|
|
5
|
+
// deterministic LOG-004 handoff proof.
|
|
6
|
+
//
|
|
7
|
+
// Forwarding rules (only real traffic is delayed/released, never rewritten):
|
|
8
|
+
// - HTTP: forwarded to the isolated server with host rewritten. GET
|
|
9
|
+
// /api/logs?... (the diagnostics snapshot) can be held AFTER the server
|
|
10
|
+
// produced the response: the exact original bytes are released later.
|
|
11
|
+
// - WebSocket /api/live: raw upgrade passthrough (host/origin rewritten to
|
|
12
|
+
// the target) with text-frame TAPPING in both directions so the wire
|
|
13
|
+
// timeline (subscribe frames with streamCursor, gateway event frames) is
|
|
14
|
+
// auditable evidence.
|
|
15
|
+
|
|
16
|
+
const http = require('node:http');
|
|
17
|
+
const crypto = require('node:crypto');
|
|
18
|
+
|
|
19
|
+
function createGapProxy(options) {
|
|
20
|
+
const { targetPort, port } = options;
|
|
21
|
+
const targetHost = '127.0.0.1';
|
|
22
|
+
const events = [];
|
|
23
|
+
let seq = 0;
|
|
24
|
+
const record = (kind, detail = {}) => {
|
|
25
|
+
events.push({ seq: ++seq, at: new Date().toISOString(), kind, ...detail });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let barrierArmed = false;
|
|
29
|
+
let barrierResolve = null;
|
|
30
|
+
const barrierHeld = new Promise(resolve => { barrierResolve = resolve; });
|
|
31
|
+
let barrierInfo = null;
|
|
32
|
+
let releaseResolve = null;
|
|
33
|
+
const released = new Promise(resolve => { releaseResolve = resolve; });
|
|
34
|
+
|
|
35
|
+
const sockets = new Set();
|
|
36
|
+
const server = http.createServer((req, res) => {
|
|
37
|
+
const url = req.url || '/';
|
|
38
|
+
const isSnapshot = req.method === 'GET' && url.startsWith('/api/logs?');
|
|
39
|
+
const held = barrierArmed && isSnapshot;
|
|
40
|
+
if (held) barrierArmed = false;
|
|
41
|
+
record('request', { method: req.method, url, held });
|
|
42
|
+
|
|
43
|
+
const upstream = http.request({
|
|
44
|
+
host: targetHost,
|
|
45
|
+
port: targetPort,
|
|
46
|
+
method: req.method,
|
|
47
|
+
path: url,
|
|
48
|
+
headers: { ...req.headers, host: `${targetHost}:${targetPort}` },
|
|
49
|
+
agent: false,
|
|
50
|
+
}, upstreamRes => {
|
|
51
|
+
const chunks = [];
|
|
52
|
+
upstreamRes.on('data', chunk => chunks.push(chunk));
|
|
53
|
+
upstreamRes.on('end', () => {
|
|
54
|
+
const body = Buffer.concat(chunks);
|
|
55
|
+
const deliver = () => {
|
|
56
|
+
const headers = { ...upstreamRes.headers };
|
|
57
|
+
delete headers['transfer-encoding'];
|
|
58
|
+
delete headers['content-length'];
|
|
59
|
+
headers['content-length'] = String(body.length);
|
|
60
|
+
res.writeHead(upstreamRes.statusCode, headers);
|
|
61
|
+
res.end(body);
|
|
62
|
+
record('response', { method: req.method, url, status: upstreamRes.statusCode, byteLength: body.length, releasedFromBarrier: held });
|
|
63
|
+
};
|
|
64
|
+
if (!held) return deliver();
|
|
65
|
+
barrierInfo = {
|
|
66
|
+
url,
|
|
67
|
+
status: upstreamRes.statusCode,
|
|
68
|
+
byteLength: body.length,
|
|
69
|
+
sha256: crypto.createHash('sha256').update(body).digest('hex'),
|
|
70
|
+
heldAt: new Date().toISOString(),
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(body.toString('utf8'));
|
|
74
|
+
barrierInfo.entries = (parsed.entries || []).length;
|
|
75
|
+
barrierInfo.newestEntryId = (parsed.entries || [])[0]?.id || null;
|
|
76
|
+
barrierInfo.streamCursor = parsed.streamCursor || '';
|
|
77
|
+
barrierInfo.health = parsed.health || null;
|
|
78
|
+
} catch (error) { barrierInfo.parseError = String(error.message); }
|
|
79
|
+
record('barrier-held', { ...barrierInfo });
|
|
80
|
+
barrierResolve(barrierInfo);
|
|
81
|
+
released.then(() => {
|
|
82
|
+
record('barrier-released', { url, at: new Date().toISOString() });
|
|
83
|
+
deliver();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
upstream.on('error', error => {
|
|
88
|
+
record('upstream-error', { url, error: String(error.message) });
|
|
89
|
+
if (!res.headersSent) res.writeHead(502, { 'content-type': 'application/json' });
|
|
90
|
+
res.end(JSON.stringify({ error: 'proxy upstream error' }));
|
|
91
|
+
});
|
|
92
|
+
req.pipe(upstream);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Minimal RFC6455 text-frame tap (no negotiation of compression happens:
|
|
96
|
+
// the gateway's ws server runs with default options).
|
|
97
|
+
const makeFrameTap = direction => {
|
|
98
|
+
let tapBuffer = Buffer.alloc(0);
|
|
99
|
+
return chunk => {
|
|
100
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
101
|
+
tapBuffer = Buffer.concat([tapBuffer, buf]);
|
|
102
|
+
for (;;) {
|
|
103
|
+
if (tapBuffer.length < 2) break;
|
|
104
|
+
const second = tapBuffer[1];
|
|
105
|
+
const opcode = tapBuffer[0] & 0x0f;
|
|
106
|
+
const masked = (second & 0x80) !== 0;
|
|
107
|
+
let len = second & 0x7f;
|
|
108
|
+
let offset = 2;
|
|
109
|
+
if (len === 126) { if (tapBuffer.length < offset + 2) break; len = tapBuffer.readUInt16BE(offset); offset += 2; }
|
|
110
|
+
else if (len === 127) { if (tapBuffer.length < offset + 8) break; len = Number(tapBuffer.readBigUInt64BE(offset)); offset += 8; }
|
|
111
|
+
let maskKey = null;
|
|
112
|
+
if (masked) { if (tapBuffer.length < offset + 4) break; maskKey = tapBuffer.subarray(offset, offset + 4); offset += 4; }
|
|
113
|
+
if (tapBuffer.length < offset + len) break;
|
|
114
|
+
let payload = tapBuffer.subarray(offset, offset + len);
|
|
115
|
+
if (masked) { const out = Buffer.alloc(len); for (let i = 0; i < len; i++) out[i] = payload[i] ^ maskKey[i % 4]; payload = out; }
|
|
116
|
+
tapBuffer = tapBuffer.subarray(offset + len);
|
|
117
|
+
if (opcode === 0x1) {
|
|
118
|
+
const text = payload.toString('utf8');
|
|
119
|
+
const detail = { direction, length: len };
|
|
120
|
+
try {
|
|
121
|
+
const message = JSON.parse(text);
|
|
122
|
+
detail.type = message.type || null;
|
|
123
|
+
if (message.type === 'subscribe' || message.type === 'unsubscribe') { detail.id = message.id; detail.route = String(message.route || '').slice(0, 400); }
|
|
124
|
+
if (message.type === 'event') { detail.eventId = message.id; detail.event = message.event; detail.cursor = message.cursor || null; detail.dataSnippet = String(message.data || '').slice(0, 1200); }
|
|
125
|
+
if (message.type === 'open' || message.type === 'error') detail.id = message.id;
|
|
126
|
+
} catch { detail.textSnippet = text.slice(0, 200); }
|
|
127
|
+
record('ws-frame', detail);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
server.on('upgrade', (req, socket, head) => {
|
|
134
|
+
record('upgrade', { url: req.url || '/', origin: req.headers.origin || null });
|
|
135
|
+
const upstream = http.request({
|
|
136
|
+
host: targetHost,
|
|
137
|
+
port: targetPort,
|
|
138
|
+
method: 'GET',
|
|
139
|
+
path: req.url,
|
|
140
|
+
headers: {
|
|
141
|
+
...req.headers,
|
|
142
|
+
host: `${targetHost}:${targetPort}`,
|
|
143
|
+
origin: `http://${targetHost}:${targetPort}`,
|
|
144
|
+
},
|
|
145
|
+
agent: false,
|
|
146
|
+
});
|
|
147
|
+
upstream.on('upgrade', (upstreamRes, upstreamSocket, upstreamHead) => {
|
|
148
|
+
socket.write(
|
|
149
|
+
`HTTP/1.1 101 Switching Protocols\r\n` +
|
|
150
|
+
`Upgrade: websocket\r\n` +
|
|
151
|
+
`Connection: Upgrade\r\n` +
|
|
152
|
+
`Sec-WebSocket-Accept: ${upstreamRes.headers['sec-websocket-accept']}\r\n` +
|
|
153
|
+
(upstreamRes.headers['sec-websocket-protocol'] ? `Sec-WebSocket-Protocol: ${upstreamRes.headers['sec-websocket-protocol']}\r\n` : '') +
|
|
154
|
+
`\r\n`
|
|
155
|
+
);
|
|
156
|
+
if (upstreamHead?.length) socket.write(upstreamHead);
|
|
157
|
+
if (head?.length) upstreamSocket.write(head);
|
|
158
|
+
const clientTap = makeFrameTap('client->server');
|
|
159
|
+
const serverTap = makeFrameTap('server->client');
|
|
160
|
+
socket.on('data', chunk => { clientTap(chunk); upstreamSocket.write(chunk); });
|
|
161
|
+
upstreamSocket.on('data', chunk => { serverTap(chunk); socket.write(chunk); });
|
|
162
|
+
const kill = () => { try { socket.destroy(); } catch {} try { upstreamSocket.destroy(); } catch {} sockets.delete(socket); sockets.delete(upstreamSocket); };
|
|
163
|
+
socket.on('error', kill); upstreamSocket.on('error', kill);
|
|
164
|
+
socket.on('close', kill); upstreamSocket.on('close', kill);
|
|
165
|
+
sockets.add(socket); sockets.add(upstreamSocket);
|
|
166
|
+
});
|
|
167
|
+
upstream.on('error', error => {
|
|
168
|
+
record('upgrade-error', { error: String(error.message) });
|
|
169
|
+
socket.destroy();
|
|
170
|
+
});
|
|
171
|
+
upstream.end();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
server.once('error', reject);
|
|
176
|
+
server.listen(port, '127.0.0.1', () => resolve({
|
|
177
|
+
url: `http://127.0.0.1:${server.address().port}`,
|
|
178
|
+
events,
|
|
179
|
+
armSnapshotBarrier() { barrierArmed = true; record('barrier-armed', {}); },
|
|
180
|
+
async waitBarrier(timeoutMs = 10000) {
|
|
181
|
+
const timer = new Promise((_, reject) => setTimeout(() => reject(new Error('barrier timeout')), timeoutMs));
|
|
182
|
+
return Promise.race([barrierHeld, timer]);
|
|
183
|
+
},
|
|
184
|
+
releaseBarrier() { record('barrier-release-command', {}); releaseResolve(); },
|
|
185
|
+
get barrierInfo() { return barrierInfo; },
|
|
186
|
+
close() {
|
|
187
|
+
return new Promise(resolve => {
|
|
188
|
+
for (const socket of sockets) { try { socket.destroy(); } catch {} }
|
|
189
|
+
server.close(() => resolve());
|
|
190
|
+
setTimeout(resolve, 2000);
|
|
191
|
+
server.closeAllConnections?.();
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function decodeStreamCursor(value) {
|
|
199
|
+
if (!value) return null;
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(Buffer.from(String(value), 'base64url').toString('utf8'));
|
|
202
|
+
} catch { return null; }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { createGapProxy, decodeStreamCursor };
|