@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
|
+
|
|
3
|
+
// P-ENG-FINAL ENG-002 真实Hook闭环 (real hook closed loop) acceptance driver.
|
|
4
|
+
//
|
|
5
|
+
// Case (qa-autopilot/config/test-cases.json ENG-002, P1):
|
|
6
|
+
// steps: 导入fixture→新增真实git commit→等待journal→claim→晋升→查询。
|
|
7
|
+
// expected: 1) 按完整SHA绑定证据。
|
|
8
|
+
// 2) Markdown与检索引用一致。
|
|
9
|
+
// 3) 不能直接API造任务替代Hook验收。
|
|
10
|
+
//
|
|
11
|
+
// What is real here:
|
|
12
|
+
// * a real fixture Git repository and a real `git commit`;
|
|
13
|
+
// * a REAL post-commit hook (a separate OS process running the real
|
|
14
|
+
// ai-coding-event-bridge commit-boundary CLI) that appends the boundary
|
|
15
|
+
// event to an isolated bridge journal;
|
|
16
|
+
// * the real bridge console server (isolated --home) that the engine polls;
|
|
17
|
+
// * the real VENDORED knowledge-engine (consume mode) that drains the
|
|
18
|
+
// journal, binds the claim, promotes knowledge and syncs the index;
|
|
19
|
+
// * the real vector-hub server (isolated root) with deterministic test
|
|
20
|
+
// embeddings - the paid embedding API is NOT exercised.
|
|
21
|
+
//
|
|
22
|
+
// The driver NEVER calls POST /api/runs: the run must arrive through the
|
|
23
|
+
// journal (trigger 'git-hook'), while the API path would stamp
|
|
24
|
+
// trigger 'manual-debug' (server.js:137 vs coordinator.js:193).
|
|
25
|
+
//
|
|
26
|
+
// Analyzer = deterministic stand-in (NO real model output). No quota access, no
|
|
27
|
+
// PAYG, no user HOME/credentials/memory/trust, no production ports.
|
|
28
|
+
|
|
29
|
+
const assert = require('node:assert/strict');
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const {
|
|
33
|
+
makeLaneDir, buildFixtureRepo, installPostCommitHook, startBridge, startVector,
|
|
34
|
+
startEngine, waitUntil, callJson, git, gitRun, listFilesRecursive, saveEvidence, hashText,
|
|
35
|
+
} = require('./helpers/parallel-engine-final-harness.js');
|
|
36
|
+
const { createStandinAnalyzer } = require('./helpers/parallel-engine-final-standin-analyzer.js');
|
|
37
|
+
|
|
38
|
+
const ARTIFACTS = process.env.PK_TEST_EVIDENCE_ROOT
|
|
39
|
+
? path.join(process.env.PK_TEST_EVIDENCE_ROOT, 'eng002')
|
|
40
|
+
: path.resolve(__dirname, '../../qa-autopilot/artifacts/parallel-20261023/engine-review-r3/reruns/eng002');
|
|
41
|
+
const PROJECT_ID = 'project-eng002';
|
|
42
|
+
|
|
43
|
+
function findJournalFiles(root) {
|
|
44
|
+
const out = [];
|
|
45
|
+
const walk = dir => {
|
|
46
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
47
|
+
const absolute = path.join(dir, entry.name);
|
|
48
|
+
if (entry.isDirectory()) walk(absolute);
|
|
49
|
+
else if (entry.name === 'events.jsonl') out.push(absolute);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
if (fs.existsSync(root)) walk(root);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const lane = makeLaneDir('eng002');
|
|
58
|
+
const commands = [];
|
|
59
|
+
const evidence = { caseId: 'ENG-002', laneDir: lane, phases: {}, commands };
|
|
60
|
+
|
|
61
|
+
const fixture = buildFixtureRepo(lane, 'eng002', []);
|
|
62
|
+
const knowledgePath = path.join(lane, 'knowledge-eng002');
|
|
63
|
+
const storePath = path.join(knowledgePath, 'dev-conversations');
|
|
64
|
+
fs.mkdirSync(storePath, { recursive: true });
|
|
65
|
+
const home = path.join(lane, 'bridge-home');
|
|
66
|
+
const dataDir = path.join(lane, 'engine-data');
|
|
67
|
+
const vectorRoot = path.join(lane, 'vector-root');
|
|
68
|
+
const eventsFile = path.join(lane, 'analyzer-events.jsonl');
|
|
69
|
+
|
|
70
|
+
const bridge = startBridge(home);
|
|
71
|
+
let engine = null;
|
|
72
|
+
const analyzer = createStandinAnalyzer({
|
|
73
|
+
eventsFile,
|
|
74
|
+
readSnapshot: (projectId, commitSha) => engine?.runtime.coordinator.conversations.readSnapshot(projectId, commitSha),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const bridgeUrl = await bridge.listen();
|
|
79
|
+
const vector = await startVector(vectorRoot);
|
|
80
|
+
engine = await startEngine({ dataDir, bridgeUrl, vectorUrl: vector.url, analyzer });
|
|
81
|
+
const engineUrl = await engine.listen();
|
|
82
|
+
commands.push('start real bridge console (isolated home) + real vector-hub (isolated root) + vendored engine (consume mode)');
|
|
83
|
+
|
|
84
|
+
// 导入fixture: register the fixture repo as a bridge project and register
|
|
85
|
+
// the project in the engine (same shape as the engine's own integration
|
|
86
|
+
// fixture: journal id = bridge project id, repo identity from the repo).
|
|
87
|
+
const registered = await bridge.projectRegistry.addProject({ home, path: fixture.repoPath, store: storePath });
|
|
88
|
+
const { repoIdentity } = await bridge.resolveRepoContext(fixture.repoPath);
|
|
89
|
+
engine.runtime.store.addProject({
|
|
90
|
+
projectId: PROJECT_ID,
|
|
91
|
+
repoPath: fixture.repoPath,
|
|
92
|
+
knowledgePath,
|
|
93
|
+
trackingStartCommit: fixture.baseline,
|
|
94
|
+
repoIdentity,
|
|
95
|
+
journalId: registered.id,
|
|
96
|
+
conversationBaselineCursor: 0,
|
|
97
|
+
});
|
|
98
|
+
commands.push(`bridge project registered id=${registered.id.slice(0, 12)}…; engine project registered`);
|
|
99
|
+
|
|
100
|
+
// A real human conversation event so the frozen claim carries evidence.
|
|
101
|
+
const humanText = '请把所有项目的长期偏好设为:统一使用中文注释。';
|
|
102
|
+
await bridge.bridge.captureWorkbenchEvent({
|
|
103
|
+
eventType: 'user_prompt',
|
|
104
|
+
source: 'claude-code',
|
|
105
|
+
projectPath: fixture.repoPath,
|
|
106
|
+
sessionId: 'session-eng002',
|
|
107
|
+
turnId: 'turn-eng002',
|
|
108
|
+
eventKey: 'workbench:eng002:origin=human',
|
|
109
|
+
content: humanText,
|
|
110
|
+
});
|
|
111
|
+
commands.push('bridge captureWorkbenchEvent (eventKey origin=human)');
|
|
112
|
+
|
|
113
|
+
// REAL git hook: separate OS process, real bridge CLI, isolated home.
|
|
114
|
+
const hook = installPostCommitHook(fixture.repoPath, { home, mode: 'ok' });
|
|
115
|
+
commands.push(`installed real post-commit hook: ${hook.hookPath}`);
|
|
116
|
+
|
|
117
|
+
// 新增真实git commit -> the hook fires as its own process.
|
|
118
|
+
fs.writeFileSync(path.join(fixture.repoPath, 'feature.txt'), `feature X\n${'y'.repeat(64)}\n`);
|
|
119
|
+
git(fixture.repoPath, ['add', '.']);
|
|
120
|
+
const commit = gitRun(fixture.repoPath, ['commit', '-qm', 'feature X']);
|
|
121
|
+
assert.equal(commit.code, 0, `real commit failed: ${commit.stderr}`);
|
|
122
|
+
const gitSha = git(fixture.repoPath, ['rev-parse', 'HEAD']);
|
|
123
|
+
assert.match(gitSha, /^[a-f0-9]{40}$/, 'full 40-hex commit SHA');
|
|
124
|
+
commands.push(`real git commit -> ${gitSha} (exit 0)`);
|
|
125
|
+
|
|
126
|
+
// 等待journal -> claim -> 晋升
|
|
127
|
+
await callJson(engineUrl, '/api/bridge/notify', {});
|
|
128
|
+
const run = await waitUntil(async () => {
|
|
129
|
+
const runs = engine.runtime.store.listRuns(PROJECT_ID);
|
|
130
|
+
return runs.length === 1 && runs[0].state === 'succeeded' ? runs[0] : null;
|
|
131
|
+
}, 60000, 'journal-driven run reaches succeeded');
|
|
132
|
+
const trigger = run.trigger;
|
|
133
|
+
|
|
134
|
+
// --- Evidence 1: 按完整SHA绑定证据 ---
|
|
135
|
+
const journalFiles = findJournalFiles(path.join(home)).concat(findJournalFiles(storePath));
|
|
136
|
+
const boundaryEvents = [];
|
|
137
|
+
for (const file of journalFiles) {
|
|
138
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n').filter(Boolean)) {
|
|
139
|
+
const event = JSON.parse(line);
|
|
140
|
+
if (event.schema === 'git-commit-boundary/v1') boundaryEvents.push({ file, event });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const boundary = boundaryEvents.find(entry => entry.event.commitSha === gitSha);
|
|
144
|
+
assert.ok(boundary, `boundary event for ${gitSha} not found in ${journalFiles.length} journal file(s)`);
|
|
145
|
+
assert.match(boundary.event.commitSha, /^[a-f0-9]{40}$/, 'journal binds the FULL SHA');
|
|
146
|
+
assert.equal(boundary.event.repoIdentity?.workspaceRoot ? path.resolve(boundary.event.repoIdentity.workspaceRoot).toLowerCase() : null, path.resolve(fixture.repoPath).toLowerCase(), 'boundary carries the real repo identity');
|
|
147
|
+
|
|
148
|
+
assert.equal(run.commitSha, gitSha, 'engine run binds the full SHA');
|
|
149
|
+
assert.equal(run.commitSha.length, 40);
|
|
150
|
+
const stateOnDisk = JSON.parse(fs.readFileSync(path.join(dataDir, 'engine-state.json'), 'utf8'));
|
|
151
|
+
const runtimeState = stateOnDisk.projects.find(p => p.projectId === PROJECT_ID).runtime;
|
|
152
|
+
assert.equal(runtimeState.lastAnalyzedCommit, gitSha, 'pointer advanced to the full SHA');
|
|
153
|
+
const ledgerFile = path.join(dataDir, 'runtime', 'processed-commits', PROJECT_ID, `${gitSha}.json`);
|
|
154
|
+
assert.ok(fs.existsSync(ledgerFile), 'processing ledger is keyed by the FULL SHA');
|
|
155
|
+
const ledger = JSON.parse(fs.readFileSync(ledgerFile, 'utf8'));
|
|
156
|
+
assert.equal(ledger.commitSha, gitSha);
|
|
157
|
+
const promotionsDir = path.join(dataDir, 'runtime', 'promotions', PROJECT_ID);
|
|
158
|
+
const promotionFiles = fs.readdirSync(promotionsDir);
|
|
159
|
+
const promotion = JSON.parse(fs.readFileSync(path.join(promotionsDir, promotionFiles[0]), 'utf8'));
|
|
160
|
+
assert.ok(JSON.stringify(promotion).includes(gitSha), 'promotion journal binds the full SHA');
|
|
161
|
+
// The staging manifest lives under the RECONCILER's own run id (a content
|
|
162
|
+
// hash), not the store run id, so it is located by scanning the run roots.
|
|
163
|
+
const runRoots = fs.existsSync(path.join(dataDir, 'runtime', 'runs', PROJECT_ID))
|
|
164
|
+
? fs.readdirSync(path.join(dataDir, 'runtime', 'runs', PROJECT_ID))
|
|
165
|
+
: [];
|
|
166
|
+
const manifestFiles = runRoots
|
|
167
|
+
.map(root => path.join(dataDir, 'runtime', 'runs', PROJECT_ID, root, 'output', 'manifest.json'))
|
|
168
|
+
.filter(file => fs.existsSync(file));
|
|
169
|
+
assert.equal(manifestFiles.length, 1, `exactly one staging manifest persisted (found ${manifestFiles.length} under ${runRoots.length} run root(s))`);
|
|
170
|
+
const manifestFile = manifestFiles[0];
|
|
171
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
|
|
172
|
+
const commitRefs = manifest.operations.flatMap(op => op.evidenceReferences || []).filter(ref => ref.startsWith('commit:'));
|
|
173
|
+
assert.ok(commitRefs.includes(`commit:${gitSha}`), 'manifest evidence reference uses the full SHA');
|
|
174
|
+
|
|
175
|
+
evidence.phases.fullShaBinding = {
|
|
176
|
+
gitSha,
|
|
177
|
+
journalBoundary: { file: path.basename(boundary.file), sequence: boundary.event.sequence, commitSha: boundary.event.commitSha, workspaceRoot: boundary.event.repoIdentity?.workspaceRoot, parents: boundary.event.parents || null },
|
|
178
|
+
run: { runId: run.runId, trigger, state: run.state, phase: run.phase, commitSha: run.commitSha, promotedAt: run.promotedAt },
|
|
179
|
+
ledger: { path: path.relative(dataDir, ledgerFile), commitSha: ledger.commitSha, status: ledger.status },
|
|
180
|
+
manifestCommitReferences: commitRefs,
|
|
181
|
+
manifestFile: path.relative(dataDir, manifestFile).replace(/\\/g, '/'),
|
|
182
|
+
manifestRunId: manifest.runId,
|
|
183
|
+
reconcilerRunRoots: runRoots,
|
|
184
|
+
promotionJournalPhase: promotion.phase,
|
|
185
|
+
lastAnalyzedCommit: runtimeState.lastAnalyzedCommit,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// --- Evidence 2: Markdown与检索引用一致 ---
|
|
189
|
+
const promotedPath = path.join(knowledgePath, 'changes', `${gitSha.slice(0, 12)}.md`);
|
|
190
|
+
assert.ok(fs.existsSync(promotedPath), 'promoted markdown exists');
|
|
191
|
+
const markdown = fs.readFileSync(promotedPath, 'utf8');
|
|
192
|
+
assert.ok(markdown.includes(gitSha), 'promoted markdown cites the full SHA');
|
|
193
|
+
const search = await callJson(engineUrl, '/api/search', { projectId: PROJECT_ID, query: gitSha });
|
|
194
|
+
assert.equal(search.status, 200, `search failed: ${JSON.stringify(search.body).slice(0, 400)}`);
|
|
195
|
+
const hits = (search.body.results || []).filter(row => (row.chunk_text || row.snippet || '').includes(gitSha));
|
|
196
|
+
assert.ok(hits.length, `retrieval returned no hit for the full SHA (results=${(search.body.results || []).length})`);
|
|
197
|
+
const hit = hits[0];
|
|
198
|
+
const hubSearch = await vector.manager.hub.search(gitSha, { projects: [PROJECT_ID] });
|
|
199
|
+
assert.ok(hubSearch.results.length, 'real vector index holds the promoted document');
|
|
200
|
+
evidence.phases.markdownAndRetrieval = {
|
|
201
|
+
promotedFile: path.relative(knowledgePath, promotedPath).replace(/\\/g, '/'),
|
|
202
|
+
knowledgeTree: listFilesRecursive(knowledgePath),
|
|
203
|
+
markdownSha256: hashText(markdown),
|
|
204
|
+
markdownCitesFullSha: markdown.includes(gitSha),
|
|
205
|
+
retrieval: { hitCount: hits.length, entryId: hit.entry_id, spaceId: hit.space_id, score: hit.relevance_score, indexHealth: search.body.health?.index || null },
|
|
206
|
+
vectorIndex: { resultCount: hubSearch.results.length, firstUri: hubSearch.results[0]?.uri || null, snippetHasSha: (hubSearch.results[0]?.snippet || '').includes(gitSha) },
|
|
207
|
+
runIndexState: run.indexState,
|
|
208
|
+
};
|
|
209
|
+
assert.equal(hit.entry_id, `changes/${gitSha.slice(0, 12)}.md`, 'retrieval entry id matches the promoted file');
|
|
210
|
+
assert.equal(run.indexState, 'succeeded', 'index sync to the real vector service succeeded');
|
|
211
|
+
|
|
212
|
+
// --- Evidence 3: 不能直接API造任务替代Hook验收 ---
|
|
213
|
+
const cursors = engine.runtime.store.state.journalCursors || {};
|
|
214
|
+
const consumer = await bridge.bridge.getConsumer('knowledge-engine');
|
|
215
|
+
evidence.phases.hookProvenance = {
|
|
216
|
+
apiRunCallsMadeByDriver: 0,
|
|
217
|
+
runTrigger: trigger,
|
|
218
|
+
apiPathTriggerWouldBe: 'manual-debug',
|
|
219
|
+
journalCursor: cursors[registered.id],
|
|
220
|
+
bridgeConsumerCursor: consumer?.cursors?.[registered.id] ?? null,
|
|
221
|
+
totalRunsForProject: engine.runtime.store.listRuns(PROJECT_ID).length,
|
|
222
|
+
analyzerInvocations: fs.existsSync(eventsFile) ? fs.readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean).length : 0,
|
|
223
|
+
};
|
|
224
|
+
assert.equal(trigger, 'git-hook', 'run came from the journal/hook path, not from POST /api/runs');
|
|
225
|
+
assert.ok(cursors[registered.id] >= 1, 'engine consumed the journal (cursor advanced)');
|
|
226
|
+
assert.ok((consumer?.cursors?.[registered.id] ?? 0) >= 1, 'engine acked the bridge journal cursor');
|
|
227
|
+
assert.equal(engine.runtime.store.listRuns(PROJECT_ID).length, 1, 'exactly one run - no API-created duplicate');
|
|
228
|
+
|
|
229
|
+
evidence.verdict = {
|
|
230
|
+
'expected1 按完整SHA绑定证据': 'PASS',
|
|
231
|
+
'expected2 Markdown与检索引用一致': 'PASS',
|
|
232
|
+
'expected3 不能直接API造任务替代Hook验收': 'PASS',
|
|
233
|
+
realHook: 'real post-commit hook as a separate OS process running the real bridge commit-boundary CLI; journal boundary event verified on disk',
|
|
234
|
+
analyzerStandin: 'deterministic stand-in - NO real model output covered',
|
|
235
|
+
vectorService: 'real vector-hub server, deterministic test embeddings - paid embedding API NOT covered',
|
|
236
|
+
bridge: 'real ai-coding-event-bridge console + journal (isolated home) - real bridge, not a stub',
|
|
237
|
+
};
|
|
238
|
+
await engine.close();
|
|
239
|
+
await vector.close();
|
|
240
|
+
await bridge.close();
|
|
241
|
+
saveEvidence(ARTIFACTS, 'eng002-evidence.json', evidence);
|
|
242
|
+
console.log('ENG-002 PASS');
|
|
243
|
+
process.exit(0);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
evidence.verdict = { failed: String((error && error.stack) || error) };
|
|
246
|
+
try { await engine?.close(); } catch {}
|
|
247
|
+
try { await vector?.close(); } catch {}
|
|
248
|
+
try { await bridge.close(); } catch {}
|
|
249
|
+
saveEvidence(ARTIFACTS, 'eng002-evidence.json', evidence);
|
|
250
|
+
console.error(`ENG-002 FAIL: ${error.message}`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
main();
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// P-ENG-FINAL ENG-007 离线提交补偿 (offline commit compensation) acceptance driver.
|
|
4
|
+
//
|
|
5
|
+
// Case (qa-autopilot/config/test-cases.json ENG-007, P1):
|
|
6
|
+
// steps: 仅停测试服务,Git提交成功后再启动。
|
|
7
|
+
// expected: 1) Git不被Hook故障阻塞。
|
|
8
|
+
// 2) 启动对账补漏。
|
|
9
|
+
// 3) 已完成SHA不重跑。
|
|
10
|
+
//
|
|
11
|
+
// Real behaviour under test (real Git, real bridge, real vendored engine):
|
|
12
|
+
// * the real fixture repository with a REAL post-commit hook running the real
|
|
13
|
+
// ai-coding-event-bridge commit-boundary CLI as a separate OS process
|
|
14
|
+
// * a broken hook (missing CLI) proves Git itself is never blocked: the
|
|
15
|
+
// post-commit exit status cannot fail an already-created commit
|
|
16
|
+
// * the journal is durable: while the engine process is DOWN the boundary
|
|
17
|
+
// event of a real commit is written to disk by the real bridge CLI
|
|
18
|
+
// * the engine's startup drain (coordinator.initialize -> drain ->
|
|
19
|
+
// consumeJournal) replays the journal from cursor 0, so the commit that
|
|
20
|
+
// happened while the engine was down is compensated ("启动对账补漏")
|
|
21
|
+
// * the engine's processing ledger
|
|
22
|
+
// (runtime/processed-commits/<projectId>/<sha>.json) blocks a completed
|
|
23
|
+
// SHA from being dispatched again ("已完成SHA不重跑")
|
|
24
|
+
//
|
|
25
|
+
// Isolation: real bridge console + real vector-hub + real vendored engine on
|
|
26
|
+
// isolated dirs, ephemeral loopback ports, scrubbed env. The broken hook is
|
|
27
|
+
// installed inside the FIXTURE repository only; the user's global Git config
|
|
28
|
+
// and global hooks are never touched. Analyzer = deterministic stand-in (NO
|
|
29
|
+
// real model output). No quota access, no PAYG.
|
|
30
|
+
|
|
31
|
+
const assert = require('node:assert/strict');
|
|
32
|
+
const fs = require('node:fs');
|
|
33
|
+
const path = require('node:path');
|
|
34
|
+
const { spawnSync } = require('node:child_process');
|
|
35
|
+
const {
|
|
36
|
+
makeLaneDir, buildFixtureRepo, installPostCommitHook, startBridge, startVector,
|
|
37
|
+
startEngine, waitUntil, callJson, git, gitRun, readJsonl, saveEvidence,
|
|
38
|
+
isolatedEnv, BRIDGE_CLI,
|
|
39
|
+
} = require('./helpers/parallel-engine-final-harness.js');
|
|
40
|
+
const { createStandinAnalyzer } = require('./helpers/parallel-engine-final-standin-analyzer.js');
|
|
41
|
+
|
|
42
|
+
const ARTIFACTS = process.env.PK_TEST_EVIDENCE_ROOT
|
|
43
|
+
? path.join(process.env.PK_TEST_EVIDENCE_ROOT, 'eng007')
|
|
44
|
+
: path.resolve(__dirname, '../../qa-autopilot/artifacts/parallel-20261023/engine-review-r3/reruns/eng007');
|
|
45
|
+
const PROJECT_ID = 'project-eng007';
|
|
46
|
+
|
|
47
|
+
function findJournalFiles(root) {
|
|
48
|
+
const out = [];
|
|
49
|
+
const walk = dir => {
|
|
50
|
+
if (!fs.existsSync(dir)) return;
|
|
51
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
52
|
+
const absolute = path.join(dir, entry.name);
|
|
53
|
+
if (entry.isDirectory()) walk(absolute);
|
|
54
|
+
else if (entry.name === 'events.jsonl') out.push(absolute);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
walk(root);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function boundaryEvents(...roots) {
|
|
62
|
+
const events = [];
|
|
63
|
+
const files = roots.flatMap(root => findJournalFiles(root));
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n').filter(Boolean)) {
|
|
66
|
+
const event = JSON.parse(line);
|
|
67
|
+
if (event.schema === 'git-commit-boundary/v1') events.push({ file: path.basename(file), event });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return events;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function main() {
|
|
74
|
+
const lane = makeLaneDir('eng007');
|
|
75
|
+
const commands = [];
|
|
76
|
+
const evidence = { caseId: 'ENG-007', laneDir: lane, phases: {}, commands };
|
|
77
|
+
|
|
78
|
+
const fixture = buildFixtureRepo(lane, 'eng007', []);
|
|
79
|
+
const knowledgePath = path.join(lane, 'knowledge-eng007');
|
|
80
|
+
const storePath = path.join(knowledgePath, 'dev-conversations');
|
|
81
|
+
fs.mkdirSync(storePath, { recursive: true });
|
|
82
|
+
const home = path.join(lane, 'bridge-home');
|
|
83
|
+
const dataDir = path.join(lane, 'engine-data');
|
|
84
|
+
const vectorRoot = path.join(lane, 'vector-root');
|
|
85
|
+
const eventsFile = path.join(lane, 'analyzer-events.jsonl');
|
|
86
|
+
|
|
87
|
+
const bridge = startBridge(home);
|
|
88
|
+
let engine = null;
|
|
89
|
+
let vector = null;
|
|
90
|
+
const analyzer = createStandinAnalyzer({ eventsFile });
|
|
91
|
+
const invocationsFor = sha => readJsonl(eventsFile).filter(entry => entry.kind === 'start' && entry.commitSha === sha).length;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const bridgeUrl = await bridge.listen();
|
|
95
|
+
vector = await startVector(vectorRoot);
|
|
96
|
+
const registered = await bridge.projectRegistry.addProject({ home, path: fixture.repoPath, store: storePath });
|
|
97
|
+
const { repoIdentity } = await bridge.resolveRepoContext(fixture.repoPath);
|
|
98
|
+
commands.push(`bridge project registered id=${registered.id.slice(0, 12)}…; bridge console on ${bridgeUrl}`);
|
|
99
|
+
|
|
100
|
+
// ---------- Lifecycle 0: the project is registered while the engine runs ----------
|
|
101
|
+
// The engine is started WITHOUT the journal consumer so the registration is
|
|
102
|
+
// persisted before any startup drain can happen; then the engine process is
|
|
103
|
+
// stopped - this is the "仅停测试服务" state of the case.
|
|
104
|
+
engine = await startEngine({ dataDir, bridgeUrl, vectorUrl: vector.url, analyzer, consume: false });
|
|
105
|
+
await engine.listen();
|
|
106
|
+
engine.runtime.store.addProject({
|
|
107
|
+
projectId: PROJECT_ID,
|
|
108
|
+
repoPath: fixture.repoPath,
|
|
109
|
+
knowledgePath,
|
|
110
|
+
trackingStartCommit: fixture.baseline,
|
|
111
|
+
repoIdentity,
|
|
112
|
+
journalId: registered.id,
|
|
113
|
+
conversationBaselineCursor: 0,
|
|
114
|
+
});
|
|
115
|
+
await engine.close();
|
|
116
|
+
engine = null;
|
|
117
|
+
const persistedProject = JSON.parse(fs.readFileSync(path.join(dataDir, 'engine-state.json'), 'utf8'))
|
|
118
|
+
.projects.find(p => p.projectId === PROJECT_ID);
|
|
119
|
+
assert.ok(persistedProject, 'project registration is persisted before the engine is stopped');
|
|
120
|
+
commands.push('engine lifecycle 0: project registered, engine stopped (journal consumer was off during registration)');
|
|
121
|
+
|
|
122
|
+
// ---------- Expected 1: Git is never blocked by a hook fault ----------
|
|
123
|
+
const brokenHook = installPostCommitHook(fixture.repoPath, { home, mode: 'broken' });
|
|
124
|
+
fs.writeFileSync(path.join(fixture.repoPath, 'offline-a.txt'), `offline feature A\n${'a'.repeat(64)}\n`);
|
|
125
|
+
git(fixture.repoPath, ['add', '.']);
|
|
126
|
+
const commitA = gitRun(fixture.repoPath, ['commit', '-qm', 'offline feature A']);
|
|
127
|
+
assert.equal(commitA.code, 0, `Git must not be blocked by a failing hook (exit ${commitA.code}): ${commitA.stderr}`);
|
|
128
|
+
const shaA = git(fixture.repoPath, ['rev-parse', 'HEAD']);
|
|
129
|
+
assert.match(shaA, /^[a-f0-9]{40}$/);
|
|
130
|
+
assert.match(commitA.stderr, /commit-boundary\.missing\.js/, 'the hook really failed (missing CLI reported by git)');
|
|
131
|
+
assert.equal(boundaryEvents(home, storePath).filter(entry => entry.event.commitSha === shaA).length, 0, 'a failed hook writes no boundary event');
|
|
132
|
+
const headAfterBrokenHook = git(fixture.repoPath, ['log', '-1', '--format=%s']);
|
|
133
|
+
assert.equal(headAfterBrokenHook, 'offline feature A', 'the commit itself exists despite the hook failure');
|
|
134
|
+
commands.push(`real git commit with a BROKEN hook -> exit 0, HEAD=${shaA.slice(0, 8)}, hook error surfaced in stderr`);
|
|
135
|
+
|
|
136
|
+
// ---------- Expected 2 (part 1): the real commit lands in the journal while the engine is down ----------
|
|
137
|
+
const workingHook = installPostCommitHook(fixture.repoPath, { home, mode: 'ok' });
|
|
138
|
+
fs.writeFileSync(path.join(fixture.repoPath, 'offline-b.txt'), `offline feature B\n${'b'.repeat(64)}\n`);
|
|
139
|
+
git(fixture.repoPath, ['add', '.']);
|
|
140
|
+
const commitB = gitRun(fixture.repoPath, ['commit', '-qm', 'offline feature B']);
|
|
141
|
+
assert.equal(commitB.code, 0, `real commit failed: ${commitB.stderr}`);
|
|
142
|
+
const shaB = git(fixture.repoPath, ['rev-parse', 'HEAD']);
|
|
143
|
+
assert.match(shaB, /^[a-f0-9]{40}$/);
|
|
144
|
+
const offlineBoundaries = boundaryEvents(home, storePath).filter(entry => entry.event.commitSha === shaB);
|
|
145
|
+
assert.equal(offlineBoundaries.length, 1, 'the boundary event of the offline commit is on disk');
|
|
146
|
+
assert.equal(fs.existsSync(path.join(dataDir, 'engine-state.json')) ? JSON.parse(fs.readFileSync(path.join(dataDir, 'engine-state.json'), 'utf8')).journalCursors[registered.id] || 0 : 0, 0, 'the stopped engine consumed nothing');
|
|
147
|
+
commands.push(`real git commit while the engine was DOWN -> exit 0, HEAD=${shaB.slice(0, 8)}, boundary event written to the journal (sequence ${offlineBoundaries[0].event.sequence})`);
|
|
148
|
+
|
|
149
|
+
// ---------- Expected 2 (part 2): startup reconciliation compensates the missed SHA ----------
|
|
150
|
+
engine = await startEngine({ dataDir, bridgeUrl, vectorUrl: vector.url, analyzer, consume: true });
|
|
151
|
+
const engineUrl = await engine.listen();
|
|
152
|
+
const runB = await waitUntil(async () => {
|
|
153
|
+
const run = engine.runtime.store.listRuns(PROJECT_ID).find(r => r.commitSha === shaB);
|
|
154
|
+
return run && run.state === 'succeeded' ? run : null;
|
|
155
|
+
}, 60000, 'startup drain compensates the commit made while the engine was down');
|
|
156
|
+
const cursorAfterStart = engine.runtime.store.state.journalCursors[registered.id] || 0;
|
|
157
|
+
const ledgerB = path.join(dataDir, 'runtime', 'processed-commits', PROJECT_ID, `${shaB}.json`);
|
|
158
|
+
const promotedB = path.join(knowledgePath, 'changes', `${shaB.slice(0, 12)}.md`);
|
|
159
|
+
assert.ok(fs.existsSync(ledgerB), 'the compensated commit is written to the processing ledger');
|
|
160
|
+
assert.equal(JSON.parse(fs.readFileSync(ledgerB, 'utf8')).status, 'completed');
|
|
161
|
+
assert.ok(fs.existsSync(promotedB), 'the compensated commit produced promoted knowledge');
|
|
162
|
+
const runAExists = engine.runtime.store.listRuns(PROJECT_ID).some(r => r.commitSha === shaA);
|
|
163
|
+
evidence.phases.startupCompensation = {
|
|
164
|
+
offlineCommitSha: shaB,
|
|
165
|
+
journalSequence: offlineBoundaries[0].event.sequence,
|
|
166
|
+
engineWasDownDuringCommit: true,
|
|
167
|
+
run: { runId: runB.runId, trigger: runB.trigger, state: runB.state, phase: runB.phase, commitSha: runB.commitSha, indexState: runB.indexState, promotedAt: runB.promotedAt },
|
|
168
|
+
journalCursorAfterStart: cursorAfterStart,
|
|
169
|
+
ledger: { path: path.relative(dataDir, ledgerB).replace(/\\/g, '/'), status: JSON.parse(fs.readFileSync(ledgerB, 'utf8')).status },
|
|
170
|
+
promotedFile: path.relative(knowledgePath, promotedB).replace(/\\/g, '/'),
|
|
171
|
+
analyzerInvocationsForOfflineCommit: invocationsFor(shaB),
|
|
172
|
+
hookFailedCommitSha: shaA,
|
|
173
|
+
hookFailedCommitAnalyzed: runAExists,
|
|
174
|
+
hookFailedCommitJournalEvents: 0,
|
|
175
|
+
};
|
|
176
|
+
assert.equal(runB.trigger, 'git-hook', 'the compensated run came from the journal/hook path');
|
|
177
|
+
assert.ok(cursorAfterStart >= offlineBoundaries[0].event.sequence, 'the startup drain advanced the journal cursor past the offline event');
|
|
178
|
+
assert.equal(invocationsFor(shaB), 1, 'the missed commit was analysed exactly once');
|
|
179
|
+
assert.equal(runAExists, false, 'a commit whose hook failed produced no evidence and was not analysed (documented boundary)');
|
|
180
|
+
commands.push(`engine started -> startup drain compensated ${shaB.slice(0, 8)} (trigger=${runB.trigger}, cursor=${cursorAfterStart})`);
|
|
181
|
+
|
|
182
|
+
// ---------- Expected 3: a completed SHA is not run again ----------
|
|
183
|
+
// A duplicated boundary event for the already completed SHA (the same real
|
|
184
|
+
// bridge CLI, pointed at the same commit) is delivered to the journal.
|
|
185
|
+
const replay = spawnSync(process.execPath, [BRIDGE_CLI, '--home', home, '--cwd', fixture.repoPath, '--sha', shaB], {
|
|
186
|
+
cwd: fixture.repoPath, windowsHide: true, encoding: 'utf8', env: isolatedEnv(),
|
|
187
|
+
});
|
|
188
|
+
assert.equal(replay.status, 0, `replaying the boundary event failed: ${replay.stderr}`);
|
|
189
|
+
const replayed = JSON.parse(replay.stdout);
|
|
190
|
+
assert.equal(replayed.ok, true);
|
|
191
|
+
const cursorBeforeReplay = engine.runtime.store.state.journalCursors[registered.id] || 0;
|
|
192
|
+
await waitUntil(async () => (engine.runtime.store.state.journalCursors[registered.id] || 0) > cursorBeforeReplay, 20000, 'the duplicated boundary event is consumed');
|
|
193
|
+
const cursorAfterReplay = engine.runtime.store.state.journalCursors[registered.id] || 0;
|
|
194
|
+
const runsAfterReplay = engine.runtime.store.listRuns(PROJECT_ID);
|
|
195
|
+
const ledgerRecord = engine.runtime.coordinator.reconciler.processingLedger.read(PROJECT_ID, shaB);
|
|
196
|
+
const lastAnalyzed = engine.runtime.store.getProject(PROJECT_ID)?.runtime?.lastAnalyzedCommit || null;
|
|
197
|
+
const ancestor = gitRun(fixture.repoPath, ['merge-base', '--is-ancestor', shaB, lastAnalyzed || shaB]);
|
|
198
|
+
const promotedFiles = fs.existsSync(path.join(knowledgePath, 'changes'))
|
|
199
|
+
? fs.readdirSync(path.join(knowledgePath, 'changes'))
|
|
200
|
+
: [];
|
|
201
|
+
evidence.phases.completedShaNotRerun = {
|
|
202
|
+
replayedBoundary: { ok: replayed.ok, commitSha: replayed.commitSha, sequence: replayed.sequence },
|
|
203
|
+
journalCursorBefore: cursorBeforeReplay,
|
|
204
|
+
journalCursorAfter: cursorAfterReplay,
|
|
205
|
+
duplicateEventConsumed: cursorAfterReplay > cursorBeforeReplay,
|
|
206
|
+
runRecordCount: runsAfterReplay.length,
|
|
207
|
+
runsForSha: runsAfterReplay.filter(r => r.commitSha === shaB).length,
|
|
208
|
+
analyzerInvocations: invocationsFor(shaB),
|
|
209
|
+
ledgerCompleted: !!ledgerRecord,
|
|
210
|
+
ledgerStatus: ledgerRecord?.status || null,
|
|
211
|
+
guards: { completedShaLedger: !!ledgerRecord, alreadyAnalyzedAncestor: ancestor.code === 0, lastAnalyzedCommit: lastAnalyzed },
|
|
212
|
+
promotedFiles,
|
|
213
|
+
healthCounts: (await callJson(engineUrl, '/api/health')).body.counts,
|
|
214
|
+
};
|
|
215
|
+
assert.ok(cursorAfterReplay > cursorBeforeReplay, 'the duplicated event really reached the engine');
|
|
216
|
+
assert.equal(runsAfterReplay.filter(r => r.commitSha === shaB).length, 1, 'no second run for the completed SHA');
|
|
217
|
+
assert.equal(invocationsFor(shaB), 1, 'the completed SHA was not analysed again');
|
|
218
|
+
assert.ok(ledgerRecord, 'the completed SHA is still recorded in the processing ledger');
|
|
219
|
+
assert.equal(promotedFiles.length, 1, 'no duplicate promoted knowledge');
|
|
220
|
+
commands.push(`duplicated boundary event consumed (cursor ${cursorBeforeReplay}->${cursorAfterReplay}); runs=${runsAfterReplay.length}, analyzer invocations=${invocationsFor(shaB)}`);
|
|
221
|
+
|
|
222
|
+
// ---------- Restart: the completed SHA stays completed ----------
|
|
223
|
+
await engine.close();
|
|
224
|
+
engine = await startEngine({ dataDir, bridgeUrl, vectorUrl: vector.url, analyzer, consume: true });
|
|
225
|
+
await engine.listen();
|
|
226
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
227
|
+
const runsAfterRestart = engine.runtime.store.listRuns(PROJECT_ID);
|
|
228
|
+
evidence.phases.restartNoRerun = {
|
|
229
|
+
engineRestarted: true,
|
|
230
|
+
runRecordCount: runsAfterRestart.length,
|
|
231
|
+
analyzerInvocations: invocationsFor(shaB),
|
|
232
|
+
journalCursor: engine.runtime.store.state.journalCursors[registered.id] || 0,
|
|
233
|
+
ledgerStillCompleted: fs.existsSync(ledgerB) && JSON.parse(fs.readFileSync(ledgerB, 'utf8')).status === 'completed',
|
|
234
|
+
lastAnalyzedCommit: engine.runtime.store.getProject(PROJECT_ID)?.runtime?.lastAnalyzedCommit || null,
|
|
235
|
+
};
|
|
236
|
+
assert.equal(runsAfterRestart.length, 1, 'the restarted engine did not create a new run');
|
|
237
|
+
assert.equal(invocationsFor(shaB), 1, 'the restarted engine did not analyse the completed SHA again');
|
|
238
|
+
assert.equal(evidence.phases.restartNoRerun.ledgerStillCompleted, true);
|
|
239
|
+
commands.push('engine restarted on the same data dir -> no new run, no second analysis of the completed SHA');
|
|
240
|
+
|
|
241
|
+
evidence.verdict = {
|
|
242
|
+
'expected1 Git不被Hook故障阻塞': 'PASS',
|
|
243
|
+
'expected2 启动对账补漏': 'PASS',
|
|
244
|
+
'expected3 已完成SHA不重跑': 'PASS',
|
|
245
|
+
realGit: 'real fixture repository, real post-commit hook (separate OS process) running the real bridge commit-boundary CLI; a deliberately broken hook proves Git is not blocked',
|
|
246
|
+
startupReconciliation: 'the durable journal is drained from cursor 0 when the engine starts, so the commit made while the engine was down is compensated through the real git-hook path',
|
|
247
|
+
completedShaGuard: 'the processing ledger (runtime/processed-commits/<projectId>/<sha>.json) plus the already-analysed-ancestor check block a second dispatch of a completed SHA; observed via analyzer invocation count and run count',
|
|
248
|
+
analyzerStandin: 'deterministic stand-in - NO real model output covered',
|
|
249
|
+
vectorService: 'real vector-hub server, deterministic test embeddings - paid embedding API NOT covered',
|
|
250
|
+
boundary: 'A commit whose hook FAILED leaves no boundary event in the journal, so there is no evidence to compensate and that commit is never analysed (recorded as hookFailedCommitAnalyzed=false). The compensation guarantee covers commits whose real hook succeeded; the engine does not scan Git history for commits without a journal event.',
|
|
251
|
+
};
|
|
252
|
+
await engine.close();
|
|
253
|
+
await vector.close();
|
|
254
|
+
await bridge.close();
|
|
255
|
+
saveEvidence(ARTIFACTS, 'eng007-evidence.json', evidence);
|
|
256
|
+
console.log('ENG-007 PASS');
|
|
257
|
+
process.exit(0);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
evidence.verdict = { failed: String((error && error.stack) || error) };
|
|
260
|
+
try { await engine?.close(); } catch {}
|
|
261
|
+
try { await vector?.close(); } catch {}
|
|
262
|
+
try { await bridge.close(); } catch {}
|
|
263
|
+
saveEvidence(ARTIFACTS, 'eng007-evidence.json', evidence);
|
|
264
|
+
console.error(`ENG-007 FAIL: ${error.message}`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
main();
|