@sanqianx/project-knowledge 5.0.23 → 5.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -20
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +1361 -1346
- 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 +70 -67
- 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,459 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// P-ENGINE-REVIEW-R3 ENG-011 UI PRODUCT PROBE (explicit probe, not a canonical
|
|
4
|
+
// *-test.js entry).
|
|
5
|
+
//
|
|
6
|
+
// Why this is a probe and not a canonical test entry: it measures the UI bytes
|
|
7
|
+
// that are actually SHIPPED (the pinned/vendored copy in _modules). The
|
|
8
|
+
// knowledge-engine fix for this defect lives in the module SOURCE repo and is
|
|
9
|
+
// NOT vendored yet (no re-vendor, no publish, no pin change is authorized), so
|
|
10
|
+
// the shipped bytes still carry the defect and a default-red canonical entry
|
|
11
|
+
// would misrepresent the pinned package. This probe therefore keeps both
|
|
12
|
+
// truths visible:
|
|
13
|
+
// * default run -> vendored bytes -> RED, with the defect signature
|
|
14
|
+
// * KB_ENG011_UI_DIR=<module source ui> -> candidate bytes -> GREEN
|
|
15
|
+
// The canonical runner (_site/_test/run-all-tests.js) collects only *-test.js,
|
|
16
|
+
// so this probe is never part of the default suite.
|
|
17
|
+
//
|
|
18
|
+
// Case (qa-autopilot/config/test-cases.json ENG-011, P1):
|
|
19
|
+
// steps: UI点击指定run重试,连续双击与网络响应丢失分别测试。
|
|
20
|
+
// expected: 1) 同一意图不会重复派发。
|
|
21
|
+
// 2) 活动状态正确。
|
|
22
|
+
// 3) 请求结果未知先查询。
|
|
23
|
+
//
|
|
24
|
+
// Every acceptance observation comes from the PRODUCT'S OWN behaviour: CDP
|
|
25
|
+
// Network events emitted by the product page after native
|
|
26
|
+
// Input.dispatchMouseEvent clicks, and the product's own DOM (badge, toast,
|
|
27
|
+
// button state). The driver NEVER issues the "correct GET" on the client's
|
|
28
|
+
// behalf; driver-side API calls only prepare the failed-run precondition and
|
|
29
|
+
// read the engine's internal state for cross-checking.
|
|
30
|
+
//
|
|
31
|
+
// Topology (mirrors the product shell wiring): real vendored engine on an
|
|
32
|
+
// ephemeral port <- lane-exclusive ui-host reverse proxy (same-origin /api/*,
|
|
33
|
+
// browser Origin terminated) <- real product UI bytes served from UI_DIR.
|
|
34
|
+
//
|
|
35
|
+
// Engine-side isolation: the engine refuses to start a NEW run for a project
|
|
36
|
+
// whose previous run is still failed (coordinator.pump: "等待前一条失败提交
|
|
37
|
+
// 恢复"). Every scenario therefore gets its OWN project (own knowledge path,
|
|
38
|
+
// own commit) inside the same engine, so one failed run never blocks another
|
|
39
|
+
// scenario's precondition.
|
|
40
|
+
//
|
|
41
|
+
// Fault injection lives in the ui-host proxy only:
|
|
42
|
+
// * dropRetry - the retry really executes upstream, the response is
|
|
43
|
+
// killed mid-flight (truncated body -> BAD_JSON).
|
|
44
|
+
// * retryDelayMs - the retry response is held (real latency).
|
|
45
|
+
// * holdRetryForward - the retry POST is parked unforwarded, so the engine
|
|
46
|
+
// still reports the run as failed and every detail
|
|
47
|
+
// re-render rebuilds a retry button for the SAME
|
|
48
|
+
// in-flight intent (the re-render counter-proof).
|
|
49
|
+
// * rejectRetryStatus - the retry is refused with an explicit error status.
|
|
50
|
+
//
|
|
51
|
+
// Scenarios, in order (so a RED baseline captures the earliest defect first):
|
|
52
|
+
// 1) detail 重绘/切回 — slow response (>2s in flight), real detail rebuild +
|
|
53
|
+
// switch to another run and back, then a second native click: the
|
|
54
|
+
// product must still dispatch exactly ONE retry.
|
|
55
|
+
// 2) 网络响应丢失 - the response is lost: the client must query first.
|
|
56
|
+
// 3) 连续双击 - two native clicks in flight: exactly ONE dispatch.
|
|
57
|
+
// 4) 显式 409 拒绝 - an explicit refusal must release the lock so the user
|
|
58
|
+
// can retry again (no permanent lockout).
|
|
59
|
+
//
|
|
60
|
+
// Browser dependency: requires helpers/cdp-browser (Chrome or Edge).
|
|
61
|
+
// Isolation: own lane temp root (PK_TEST_TEMP_ROOT or the OS temp dir),
|
|
62
|
+
// ephemeral 127.0.0.1 ports, consume:false engine with a dead bridge URL,
|
|
63
|
+
// real vector-hub with deterministic test embeddings (paid embedding API NOT
|
|
64
|
+
// exercised), analyzer = deterministic stand-in (NO real model output).
|
|
65
|
+
// No quota access, no PAYG, no publish/vendor.
|
|
66
|
+
|
|
67
|
+
const assert = require('node:assert/strict');
|
|
68
|
+
const fs = require('node:fs');
|
|
69
|
+
const path = require('node:path');
|
|
70
|
+
const {
|
|
71
|
+
REPO_ROOT, makeLaneDir, buildFixtureRepo, startVector, startEngine, waitUntil, callJson,
|
|
72
|
+
readJsonl, saveEvidence, RUNTIME_IDENTITY,
|
|
73
|
+
} = require('./helpers/parallel-engine-final-harness.js');
|
|
74
|
+
const { createStandinAnalyzer } = require('./helpers/parallel-engine-final-standin-analyzer.js');
|
|
75
|
+
const { startUiHost } = require('./helpers/parallel-engine-advanced-ui-host.js');
|
|
76
|
+
const { launchCdpBrowser } = require('./helpers/cdp-browser.js');
|
|
77
|
+
const { findChrome } = require('./helpers/find-chrome.js');
|
|
78
|
+
const { attachNetworkCapture } = require('./helpers/cdp-network-capture.js');
|
|
79
|
+
|
|
80
|
+
const ARTIFACTS = process.env.KB_ENG011_EVIDENCE_DIR
|
|
81
|
+
|| path.resolve(__dirname, '../../qa-autopilot/artifacts/parallel-20261023/engine-review-r3/evidence/eng011ui');
|
|
82
|
+
const UI_DIR = process.env.KB_ENG011_UI_DIR
|
|
83
|
+
|| path.join(REPO_ROOT, '_modules', 'knowledge-engine', 'ui');
|
|
84
|
+
|
|
85
|
+
function isRetryPost(entry) {
|
|
86
|
+
return entry.method === 'POST' && /\/api\/runs\/[^/]+\/retry$/.test(new URL(entry.url).pathname);
|
|
87
|
+
}
|
|
88
|
+
function isApi(entry) {
|
|
89
|
+
return new URL(entry.url).pathname.startsWith('/api/');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function nativeClick(browser, selector) {
|
|
93
|
+
const rect = await browser.evaluate(`(() => { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) return null; const r = el.getBoundingClientRect(); return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2) }; })()`);
|
|
94
|
+
if (!rect || typeof rect.x !== 'number') throw new Error(`element not clickable: ${selector}`);
|
|
95
|
+
await browser.send('Input.dispatchMouseEvent', { type: 'mousePressed', x: rect.x, y: rect.y, button: 'left', clickCount: 1 });
|
|
96
|
+
await browser.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: rect.x, y: rect.y, button: 'left', clickCount: 1 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function domTools(browser) {
|
|
100
|
+
return {
|
|
101
|
+
badge: () => browser.evaluate(`(() => { const el = document.querySelector('#runDetail #runMeta .badge'); return el ? el.textContent.trim() : null; })()`),
|
|
102
|
+
toastShown: () => browser.evaluate(`(() => { const el = document.querySelector('#toast'); return el ? { text: el.textContent, shown: el.classList.contains('show') } : null; })()`),
|
|
103
|
+
retryDisabled: () => browser.evaluate(`(() => { const b = document.querySelector('#retryRun'); return b ? b.disabled : null; })()`),
|
|
104
|
+
detailRunId: () => browser.evaluate(`(() => { const el = document.querySelector('#runDetailId'); return el ? el.textContent.trim() : null; })()`),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function openRunsView(browser, base) {
|
|
109
|
+
await browser.send('Page.navigate', { url: `${base}/` });
|
|
110
|
+
await waitUntil(() => browser.evaluate("document.readyState === 'complete'"), 15000, 'page load');
|
|
111
|
+
await nativeClick(browser, '.nav-item[data-view="runs"]');
|
|
112
|
+
await waitUntil(() => browser.evaluate('document.querySelectorAll(\'#runsBody tr[data-run]\').length > 0'), 15000, 'runs table rows');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Select a run in the runs table and wait until the DETAIL really shows it
|
|
116
|
+
// (a real re-render of the detail panel, which is where the retry button
|
|
117
|
+
// lives).
|
|
118
|
+
async function selectRun(browser, runId) {
|
|
119
|
+
await nativeClick(browser, `#runsBody tr[data-run="${runId}"]`);
|
|
120
|
+
await waitUntil(async () => (await browser.evaluate(`(() => { const el = document.querySelector('#runDetailId'); return el ? el.textContent.trim() : null; })()`)) === runId, 15000, `detail shows run ${runId}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
const lane = makeLaneDir('eng011ui-probe');
|
|
125
|
+
const fixture = buildFixtureRepo(lane, 'eng011ui-probe', ['A', 'B', 'C', 'D', 'E']);
|
|
126
|
+
const chrome = findChrome();
|
|
127
|
+
const evidence = {
|
|
128
|
+
caseId: 'ENG-011 UI product probe (real browser native clicks)',
|
|
129
|
+
probe: 'explicit product probe - NOT a canonical *-test.js entry (the vendored/pinned UI bytes still carry the defect until the module fix is vendored, which is NOT authorized here)',
|
|
130
|
+
targetIdentity: {
|
|
131
|
+
shipped: 'knowledge-engine 0.2.7-rc.10 vendored at _modules/knowledge-engine (pinned, unchanged by this lane)',
|
|
132
|
+
candidate: 'knowledge-engine source ui/ (module repo main, uncommitted fix) - reachable only through KB_ENG011_UI_DIR',
|
|
133
|
+
},
|
|
134
|
+
runtimeIdentity: RUNTIME_IDENTITY,
|
|
135
|
+
laneDir: lane,
|
|
136
|
+
uiDir: path.resolve(UI_DIR),
|
|
137
|
+
chrome: path.resolve(chrome),
|
|
138
|
+
fixture: { repoPath: fixture.repoPath, baseline: fixture.baseline, shas: fixture.shas },
|
|
139
|
+
scenarios: {},
|
|
140
|
+
engineStandin: 'deterministic stand-in analyzer (no real model output); vector-hub real with deterministic test embeddings',
|
|
141
|
+
runs: [],
|
|
142
|
+
};
|
|
143
|
+
const persist = label => { evidence.runs.push(label); saveEvidence(ARTIFACTS, 'eng011-ui-probe-evidence.json', evidence); };
|
|
144
|
+
|
|
145
|
+
const projectIds = {
|
|
146
|
+
rerender: 'project-eng011ui-rerender',
|
|
147
|
+
other: 'project-eng011ui-other',
|
|
148
|
+
lost: 'project-eng011ui-lost',
|
|
149
|
+
double: 'project-eng011ui-double',
|
|
150
|
+
refusal: 'project-eng011ui-refusal',
|
|
151
|
+
};
|
|
152
|
+
const knowledgePath = path.join(lane, 'knowledge-ui');
|
|
153
|
+
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
154
|
+
const dataDir = path.join(lane, 'engine-data');
|
|
155
|
+
const vectorRoot = path.join(lane, 'vector-root');
|
|
156
|
+
const eventsFile = path.join(lane, 'analyzer-events.jsonl');
|
|
157
|
+
|
|
158
|
+
const vector = await startVector(vectorRoot);
|
|
159
|
+
const analyzer = createStandinAnalyzer({ eventsFile });
|
|
160
|
+
const engine = await startEngine({ dataDir, bridgeUrl: 'http://127.0.0.1:9', vectorUrl: vector.url, analyzer });
|
|
161
|
+
let browser = null;
|
|
162
|
+
let host = null;
|
|
163
|
+
try {
|
|
164
|
+
const base = await engine.listen();
|
|
165
|
+
for (const [key, projectId] of Object.entries(projectIds)) {
|
|
166
|
+
engine.runtime.store.addProject({
|
|
167
|
+
projectId, name: `eng011ui-${key}`, repoPath: fixture.repoPath,
|
|
168
|
+
knowledgePath: path.join(knowledgePath, key), trackingStartCommit: fixture.baseline,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
host = startUiHost({ uiDir: UI_DIR, engineBase: base });
|
|
173
|
+
const hostBase = await host.listen();
|
|
174
|
+
evidence.uiHashes = host.uiHashes;
|
|
175
|
+
evidence.engineBase = base;
|
|
176
|
+
evidence.hostBase = hostBase;
|
|
177
|
+
|
|
178
|
+
const runsOf = async projectId => (await callJson(base, `/api/runs?projectId=${projectId}`)).body.runs;
|
|
179
|
+
const waitState = (projectId, runId, state, ms, what) => waitUntil(async () => {
|
|
180
|
+
const run = (await runsOf(projectId)).find(r => r.runId === runId);
|
|
181
|
+
return run?.state === state ? run : null;
|
|
182
|
+
}, ms, what);
|
|
183
|
+
const invocationsFor = sha => readJsonl(eventsFile).filter(e => e.kind === 'start' && e.commitSha === sha).length;
|
|
184
|
+
// Precondition: a real run that fails with the engine's debug simulated
|
|
185
|
+
// fault (retryable) - the UI then renders its "重试失败阶段" button.
|
|
186
|
+
const createFailedRun = async (projectId, sha) => {
|
|
187
|
+
const created = await callJson(base, '/api/runs', { projectId, commitSha: sha, simulateFailure: true });
|
|
188
|
+
assert.equal(created.status, 200, `precondition run creation failed: ${JSON.stringify(created.body)}`);
|
|
189
|
+
const run = created.body.run;
|
|
190
|
+
const failed = await waitState(projectId, run.runId, 'failed', 30000, `precondition: first attempt fails (${projectId}, debug simulated fault, retryable)`);
|
|
191
|
+
assert.equal(failed.error.code, 'DEBUG_SIMULATED_FAILURE');
|
|
192
|
+
assert.equal(failed.error.retryable, true, 'precondition: failed run must expose a retryable error for the UI button');
|
|
193
|
+
return { run, failed };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const target = await createFailedRun(projectIds.rerender, fixture.shas.A);
|
|
197
|
+
const other = await createFailedRun(projectIds.other, fixture.shas.B);
|
|
198
|
+
const lost = await createFailedRun(projectIds.lost, fixture.shas.C);
|
|
199
|
+
const twice = await createFailedRun(projectIds.double, fixture.shas.D);
|
|
200
|
+
const refused = await createFailedRun(projectIds.refusal, fixture.shas.E);
|
|
201
|
+
evidence.preconditions = {
|
|
202
|
+
rerender: { projectId: projectIds.rerender, runId: target.run.runId, state: target.failed.state, error: target.failed.error },
|
|
203
|
+
other: { projectId: projectIds.other, runId: other.run.runId, state: other.failed.state, error: other.failed.error },
|
|
204
|
+
lost: { projectId: projectIds.lost, runId: lost.run.runId, state: lost.failed.state, error: lost.failed.error },
|
|
205
|
+
double: { projectId: projectIds.double, runId: twice.run.runId, state: twice.failed.state, error: twice.failed.error },
|
|
206
|
+
refusal: { projectId: projectIds.refusal, runId: refused.run.runId, state: refused.failed.state, error: refused.failed.error },
|
|
207
|
+
};
|
|
208
|
+
persist('preconditions ready');
|
|
209
|
+
|
|
210
|
+
browser = await launchCdpBrowser({
|
|
211
|
+
chrome,
|
|
212
|
+
profileDir: path.join(lane, 'browser-profile'),
|
|
213
|
+
url: `${hostBase}/`,
|
|
214
|
+
});
|
|
215
|
+
const capture = attachNetworkCapture(browser);
|
|
216
|
+
await capture.enable();
|
|
217
|
+
const networkFailures = [];
|
|
218
|
+
browser.socket.on('message', raw => {
|
|
219
|
+
try {
|
|
220
|
+
const message = JSON.parse(String(raw));
|
|
221
|
+
if (message.method === 'Network.loadingFailed') {
|
|
222
|
+
networkFailures.push({ requestId: message.params.requestId, errorText: message.params.errorText, canceled: message.params.canceled });
|
|
223
|
+
}
|
|
224
|
+
} catch {}
|
|
225
|
+
});
|
|
226
|
+
const dom = domTools(browser);
|
|
227
|
+
|
|
228
|
+
// ---------- Scenario 1: detail 重绘/切回后同一在途意图仍只派发一次 ----------
|
|
229
|
+
await openRunsView(browser, hostBase);
|
|
230
|
+
await selectRun(browser, target.run.runId);
|
|
231
|
+
host.configure({ retryDelayMs: 2000, dropRetry: false, rejectRetryStatus: 0, holdRetryForward: true });
|
|
232
|
+
const markRerender = capture.requests.length;
|
|
233
|
+
const receivedBefore = host.state.receivedRetryPosts;
|
|
234
|
+
const clickedAt = Date.now();
|
|
235
|
+
await nativeClick(browser, '#retryRun');
|
|
236
|
+
await waitUntil(() => host.state.receivedRetryPosts > receivedBefore, 15000, 'the retry intent reached the ui-host');
|
|
237
|
+
assert.equal(host.state.forwardedRetryPosts, 0, 'the parked intent has not reached the engine yet');
|
|
238
|
+
await new Promise(resolve => setTimeout(resolve, 2200)); // the intent stays in flight well past the 2s response delay
|
|
239
|
+
|
|
240
|
+
await selectRun(browser, other.run.runId); // switch to another failed run
|
|
241
|
+
const otherRunDisabled = await dom.retryDisabled();
|
|
242
|
+
await selectRun(browser, target.run.runId); // switch back: the detail (and the button) is rebuilt
|
|
243
|
+
const rebuiltDisabled = await dom.retryDisabled();
|
|
244
|
+
const receivedBeforeSecondClick = host.state.receivedRetryPosts;
|
|
245
|
+
await nativeClick(browser, '#retryRun'); // second native click on the REBUILT button
|
|
246
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
247
|
+
const receivedAfterSecondClick = host.state.receivedRetryPosts;
|
|
248
|
+
const inFlightWindowMs = Date.now() - clickedAt;
|
|
249
|
+
const releasedAt = Date.now();
|
|
250
|
+
const productRetryPostsRerender = capture.requests.slice(markRerender).filter(isRetryPost);
|
|
251
|
+
evidence.scenarios.detailRerender = {
|
|
252
|
+
faultInjection: 'ui-host parked the retry POST (holdRetryForward) and answers the released retry with a 2s delayed response; the engine keeps reporting the run as failed so every detail re-render rebuilds a retry button for the SAME in-flight intent',
|
|
253
|
+
nativeClicks: 2,
|
|
254
|
+
inFlightWindowMs,
|
|
255
|
+
rebuiltButtonDisabledDuringFlight: rebuiltDisabled,
|
|
256
|
+
otherRunButtonDisabled: otherRunDisabled,
|
|
257
|
+
hostReceivedRetryPosts: { before: receivedBeforeSecondClick, after: receivedAfterSecondClick },
|
|
258
|
+
hostForwardedRetryPosts: host.state.forwardedRetryPosts,
|
|
259
|
+
productRetryPostCount: productRetryPostsRerender.length,
|
|
260
|
+
productRetryPosts: productRetryPostsRerender.map(r => ({ url: r.url, status: r.status })),
|
|
261
|
+
};
|
|
262
|
+
persist('scenario detailRerender observed');
|
|
263
|
+
assert.equal(receivedAfterSecondClick, receivedBeforeSecondClick, 'ENG-011 UI defect: the rebuilt button re-dispatched the same in-flight retry intent (the guard must be stable per runId, not per DOM element)');
|
|
264
|
+
assert.equal(productRetryPostsRerender.length, 1, 'ENG-011 UI defect: the product dispatched the same retry intent twice across the detail re-render');
|
|
265
|
+
assert.equal(rebuiltDisabled, true, 'ENG-011 UI defect: the rebuilt retry button does not reflect the in-flight state');
|
|
266
|
+
assert.equal(otherRunDisabled, false, 'ENG-011 UI defect: an unrelated run is blocked by the in-flight guard');
|
|
267
|
+
assert.ok(inFlightWindowMs >= 2000, `the in-flight window must exceed the 2s response delay (got ${inFlightWindowMs}ms)`);
|
|
268
|
+
|
|
269
|
+
host.configure({ holdRetryForward: false });
|
|
270
|
+
const released = host.releaseHeldRetries();
|
|
271
|
+
const healed = await waitState(projectIds.rerender, target.run.runId, 'succeeded', 60000, 'the released retry really executed');
|
|
272
|
+
await waitUntil(async () => (await dom.badge()) === '可检索', 30000, 'the badge converges after the delayed response');
|
|
273
|
+
const invocationsA = invocationsFor(fixture.shas.A);
|
|
274
|
+
const runsForA = (await runsOf(projectIds.rerender)).filter(r => r.commitSha === fixture.shas.A);
|
|
275
|
+
evidence.scenarios.detailRerender.releasedHeldRetries = released;
|
|
276
|
+
evidence.scenarios.detailRerender.totalInFlightMs = Date.now() - clickedAt;
|
|
277
|
+
evidence.scenarios.detailRerender.responseDeliveredAfterReleaseMs = Date.now() - releasedAt;
|
|
278
|
+
evidence.scenarios.detailRerender.engineTruth = {
|
|
279
|
+
state: healed.state, phase: healed.phase, indexState: healed.indexState,
|
|
280
|
+
analyzerInvocationsForCommit: invocationsA, runRecordsForCommit: runsForA.length,
|
|
281
|
+
};
|
|
282
|
+
assert.equal(host.state.forwardedRetryPosts, 1, 'exactly one retry reached the engine');
|
|
283
|
+
assert.equal(invocationsA, 1, `the retry dispatched exactly one analysis (got ${invocationsA})`);
|
|
284
|
+
assert.equal(runsForA.length, 1, 'no duplicate run record');
|
|
285
|
+
persist('scenario detailRerender PASS');
|
|
286
|
+
|
|
287
|
+
// ---------- Scenario 2: 网络响应丢失 (retry really executes, browser never sees the answer) ----------
|
|
288
|
+
await selectRun(browser, lost.run.runId);
|
|
289
|
+
const mark1 = capture.requests.length;
|
|
290
|
+
const forwardsBeforeClick = host.state.forwardedRetryPosts;
|
|
291
|
+
host.configure({ retryDelayMs: 0, dropRetry: true, rejectRetryStatus: 0, holdRetryForward: false });
|
|
292
|
+
await nativeClick(browser, '#retryRun');
|
|
293
|
+
await waitUntil(() => host.state.droppedRetryResponses >= 1, 15000, 'ui-host dropped the retry response after the engine executed it');
|
|
294
|
+
await new Promise(resolve => setTimeout(resolve, 1200)); // early sample: the toast auto-hides after 2.6s
|
|
295
|
+
const toastEarly = await dom.toastShown();
|
|
296
|
+
await new Promise(resolve => setTimeout(resolve, 1800)); // rest of the observation window (3s total)
|
|
297
|
+
const toastLate = await dom.toastShown();
|
|
298
|
+
const badgeStale = await dom.badge();
|
|
299
|
+
|
|
300
|
+
const pageAfterDrop = capture.requests.slice(mark1);
|
|
301
|
+
const droppedPost = pageAfterDrop.filter(isRetryPost)[0] || null;
|
|
302
|
+
const droppedPostFailure = droppedPost ? networkFailures.find(f => f.requestId === droppedPost.id) : null;
|
|
303
|
+
const apiAfterPost = pageAfterDrop.filter(entry => isApi(entry) && entry !== droppedPost);
|
|
304
|
+
const specificQuery = apiAfterPost.find(entry => entry.method === 'GET' && new URL(entry.url).pathname === `/api/runs/${lost.run.runId}`);
|
|
305
|
+
const healedLost = await waitState(projectIds.lost, lost.run.runId, 'succeeded', 60000, 'the retry behind the dropped response really executed');
|
|
306
|
+
const invocationsC = invocationsFor(fixture.shas.C);
|
|
307
|
+
const runsForC = (await runsOf(projectIds.lost)).filter(r => r.commitSha === fixture.shas.C);
|
|
308
|
+
const ledgerC = path.join(dataDir, 'runtime', 'processed-commits', projectIds.lost, `${fixture.shas.C}.json`);
|
|
309
|
+
evidence.scenarios.lostResponse = {
|
|
310
|
+
faultInjection: 'ui-host forwarded the retry POST (engine executed it), started the response, then killed the socket mid-flight',
|
|
311
|
+
droppedPostObservedByPage: droppedPost ? { url: droppedPost.url, status: droppedPost.status, loadingFailed: droppedPostFailure ? { errorText: droppedPostFailure.errorText, canceled: droppedPostFailure.canceled === true } : null } : null,
|
|
312
|
+
hostForwardedRetryPostsDuringScenario: host.state.forwardedRetryPosts - forwardsBeforeClick,
|
|
313
|
+
hostDroppedRetryResponses: host.state.droppedRetryResponses,
|
|
314
|
+
productApiRequestsAfterDrop: apiAfterPost.map(r => ({ method: r.method, url: r.url, status: r.status })),
|
|
315
|
+
productSpecificRunQueryFired: !!specificQuery,
|
|
316
|
+
productFirstApiRequestAfterDrop: apiAfterPost[0] ? { method: apiAfterPost[0].method, url: apiAfterPost[0].url } : null,
|
|
317
|
+
toastEarly,
|
|
318
|
+
toastLate,
|
|
319
|
+
domBadgeWhileEngineHadSucceeded: badgeStale,
|
|
320
|
+
engineTruth: {
|
|
321
|
+
stateBehindLostResponse: healedLost.state,
|
|
322
|
+
analyzerInvocationsForCommit: invocationsC,
|
|
323
|
+
runRecordsForCommit: runsForC.length,
|
|
324
|
+
ledgerStatus: fs.existsSync(ledgerC) ? JSON.parse(fs.readFileSync(ledgerC, 'utf8')).status : null,
|
|
325
|
+
},
|
|
326
|
+
browserConsoleErrors: browser.consoleErrors.map(entry => entry.text).slice(-10),
|
|
327
|
+
};
|
|
328
|
+
persist('scenario lostResponse observed');
|
|
329
|
+
assert.ok(droppedPost, 'the page dispatched the retry POST (captured from the browser network layer)');
|
|
330
|
+
assert.ok(droppedPostFailure && droppedPostFailure.canceled !== true, `the retry fetch failed in the browser (unknown result), loadingFailed=${droppedPostFailure ? droppedPostFailure.errorText : 'missing'}`);
|
|
331
|
+
assert.equal(host.state.forwardedRetryPosts - forwardsBeforeClick, 1, 'exactly one retry POST reached the engine in this scenario (no transport-level re-dispatch)');
|
|
332
|
+
assert.ok(specificQuery, 'ENG-011 UI defect: after the lost response the product client never queried the run state (请求结果未知先查询 violated)');
|
|
333
|
+
assert.ok(apiAfterPost.length >= 1 && apiAfterPost[0] === specificQuery, 'ENG-011 UI defect: the first product API call after the unknown result was not the specific run query');
|
|
334
|
+
assert.equal(pageAfterDrop.filter(isRetryPost).length, 1, 'ENG-011 UI defect: the product client re-dispatched the retry after the unknown result');
|
|
335
|
+
assert.ok(toastEarly && toastEarly.shown === true && toastEarly.text, 'ENG-011 UI defect: the product client left the unknown result invisible to the user (no toast)');
|
|
336
|
+
assert.notEqual(badgeStale, '失败', 'ENG-011 UI defect: the UI kept showing 失败 while the engine had already executed the retry');
|
|
337
|
+
await waitUntil(async () => (await dom.badge()) === '可检索', 20000, 'UI badge converges to 可检索 after the query');
|
|
338
|
+
assert.equal(invocationsC, 1, `the retry behind the lost response dispatched exactly one analysis (got ${invocationsC})`);
|
|
339
|
+
assert.equal(runsForC.length, 1, 'no duplicate run record after the unknown-result retry');
|
|
340
|
+
assert.ok(fs.existsSync(ledgerC), 'completed SHA recorded in the processing ledger');
|
|
341
|
+
persist('scenario lostResponse PASS');
|
|
342
|
+
|
|
343
|
+
// ---------- Scenario 3: 连续双击 (two native clicks while the retry response is in flight) ----------
|
|
344
|
+
await selectRun(browser, twice.run.runId);
|
|
345
|
+
const mark2 = capture.requests.length;
|
|
346
|
+
host.configure({ retryDelayMs: 800, dropRetry: false });
|
|
347
|
+
await nativeClick(browser, '#retryRun');
|
|
348
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
349
|
+
const disabledDuringFlight = await dom.retryDisabled();
|
|
350
|
+
await nativeClick(browser, '#retryRun');
|
|
351
|
+
await new Promise(resolve => setTimeout(resolve, 2200)); // delayed responses (800ms) settle + refresh
|
|
352
|
+
|
|
353
|
+
const pageAfterClick1 = capture.requests.slice(mark2);
|
|
354
|
+
const retryPostsDoubleClick = pageAfterClick1.filter(isRetryPost);
|
|
355
|
+
const healedTwice = await waitState(projectIds.double, twice.run.runId, 'succeeded', 60000, 'retried run reaches 可检索');
|
|
356
|
+
const invocationsD = invocationsFor(fixture.shas.D);
|
|
357
|
+
const runsForD = (await runsOf(projectIds.double)).filter(r => r.commitSha === fixture.shas.D);
|
|
358
|
+
const badgeD = await dom.badge();
|
|
359
|
+
evidence.scenarios.doubleClick = {
|
|
360
|
+
faultInjection: 'ui-host holds retry responses 800ms so the second native click lands in-flight',
|
|
361
|
+
nativeClicks: 2,
|
|
362
|
+
productRetryPostCount: retryPostsDoubleClick.length,
|
|
363
|
+
productRetryPosts: retryPostsDoubleClick.map(r => ({ url: r.url, status: r.status })),
|
|
364
|
+
buttonDisabledDuringFlight: disabledDuringFlight,
|
|
365
|
+
engineContainment: {
|
|
366
|
+
analyzerInvocationsForCommit: invocationsD,
|
|
367
|
+
runRecordsForCommit: runsForD.length,
|
|
368
|
+
finalState: healedTwice.state,
|
|
369
|
+
finalPhase: healedTwice.phase,
|
|
370
|
+
indexState: healedTwice.indexState,
|
|
371
|
+
},
|
|
372
|
+
finalDomBadge: badgeD,
|
|
373
|
+
};
|
|
374
|
+
persist('scenario doubleClick observed');
|
|
375
|
+
assert.equal(retryPostsDoubleClick.length, 1, `ENG-011 UI defect: the product page dispatched the same retry intent ${retryPostsDoubleClick.length} times under a double click (expected exactly 1)`);
|
|
376
|
+
assert.equal(disabledDuringFlight, true, 'ENG-011 UI defect: the retry button stayed enabled while the intent was in flight');
|
|
377
|
+
assert.equal(invocationsD, 1, `the accepted retry dispatched exactly one analysis (got ${invocationsD})`);
|
|
378
|
+
assert.equal(runsForD.length, 1, 'no duplicate run record for the same commit');
|
|
379
|
+
assert.equal(healedTwice.state, 'succeeded');
|
|
380
|
+
assert.equal(healedTwice.phase, 'ready');
|
|
381
|
+
assert.equal(healedTwice.indexState, 'succeeded');
|
|
382
|
+
assert.equal(badgeD, '可检索', 'UI run detail badge must reach 可检索 after the retry');
|
|
383
|
+
persist('scenario doubleClick PASS');
|
|
384
|
+
|
|
385
|
+
// ---------- Scenario 4: 显式 409 拒绝后可恢复重试 ----------
|
|
386
|
+
await selectRun(browser, refused.run.runId);
|
|
387
|
+
host.configure({ retryDelayMs: 0, dropRetry: false, rejectRetryStatus: 409 });
|
|
388
|
+
const forwardsBeforeRefusal = host.state.forwardedRetryPosts;
|
|
389
|
+
const mark4 = capture.requests.length;
|
|
390
|
+
await nativeClick(browser, '#retryRun');
|
|
391
|
+
await waitUntil(() => host.state.rejectedRetryResponses >= 1, 15000, 'the explicit refusal reached the product');
|
|
392
|
+
const refusalToastSamples = [];
|
|
393
|
+
for (let index = 0; index < 8; index += 1) {
|
|
394
|
+
refusalToastSamples.push(await dom.toastShown());
|
|
395
|
+
await new Promise(resolve => setTimeout(resolve, 120));
|
|
396
|
+
}
|
|
397
|
+
const refusalToast = refusalToastSamples.find(sample => sample && sample.shown === true) || refusalToastSamples[0];
|
|
398
|
+
const forwardedAfterRefusal = host.state.forwardedRetryPosts;
|
|
399
|
+
await selectRun(browser, refused.run.runId); // the detail is rebuilt after the refusal
|
|
400
|
+
const disabledAfterRefusal = await dom.retryDisabled();
|
|
401
|
+
host.configure({ rejectRetryStatus: 0 });
|
|
402
|
+
await nativeClick(browser, '#retryRun');
|
|
403
|
+
await waitUntil(() => host.state.forwardedRetryPosts > forwardedAfterRefusal, 15000, 'the second intent reached the engine');
|
|
404
|
+
const healedRefused = await waitState(projectIds.refusal, refused.run.runId, 'succeeded', 60000, 'the retried run converges');
|
|
405
|
+
await waitUntil(async () => (await dom.badge()) === '可检索', 30000, 'the badge converges after the recovered retry');
|
|
406
|
+
const invocationsE = invocationsFor(fixture.shas.E);
|
|
407
|
+
const runsForE = (await runsOf(projectIds.refusal)).filter(r => r.commitSha === fixture.shas.E);
|
|
408
|
+
evidence.scenarios.explicitRefusalRecovery = {
|
|
409
|
+
faultInjection: 'ui-host refuses the retry POST with an explicit 409 (the intent never reaches the engine)',
|
|
410
|
+
toastAfterRefusal: refusalToast,
|
|
411
|
+
toastSamplesAfterRefusal: refusalToastSamples,
|
|
412
|
+
productRequestsAfterRefusal: capture.requests.slice(mark4).map(r => ({ method: r.method, url: r.url, status: r.status })),
|
|
413
|
+
browserConsoleErrors: browser.consoleErrors.map(entry => entry.text).slice(-10),
|
|
414
|
+
hostForwardedRetryPostsBeforeRefusal: forwardsBeforeRefusal,
|
|
415
|
+
hostForwardedRetryPostsAfterRefusal: forwardedAfterRefusal,
|
|
416
|
+
rebuiltButtonDisabledAfterRefusal: disabledAfterRefusal,
|
|
417
|
+
hostForwardedRetryPostsAfterRecovery: host.state.forwardedRetryPosts,
|
|
418
|
+
engineTruth: {
|
|
419
|
+
state: healedRefused.state, phase: healedRefused.phase, indexState: healedRefused.indexState,
|
|
420
|
+
analyzerInvocationsForCommit: invocationsE, runRecordsForCommit: runsForE.length,
|
|
421
|
+
},
|
|
422
|
+
finalDomBadge: await dom.badge(),
|
|
423
|
+
};
|
|
424
|
+
persist('scenario explicitRefusalRecovery observed');
|
|
425
|
+
assert.ok(refusalToast && refusalToast.shown === true && /refusal/.test(refusalToast.text), `ENG-011 UI defect: the explicit refusal was not surfaced to the user (toast: ${JSON.stringify(refusalToast)})`);
|
|
426
|
+
assert.equal(forwardedAfterRefusal, forwardsBeforeRefusal, 'the refused intent must not reach the engine');
|
|
427
|
+
assert.equal(disabledAfterRefusal, false, 'ENG-011 UI defect: an explicit API refusal left the retry permanently locked');
|
|
428
|
+
assert.equal(invocationsE, 1, `the recovered retry dispatched exactly one analysis (got ${invocationsE})`);
|
|
429
|
+
assert.equal(runsForE.length, 1, 'no duplicate run record');
|
|
430
|
+
persist('scenario explicitRefusalRecovery PASS');
|
|
431
|
+
|
|
432
|
+
evidence.verdict = {
|
|
433
|
+
'expected1 同一意图不会重复派发': 'PASS (product client: exactly 1 retry POST under a native double click AND across a real detail re-render/switch-back; the guard is stable per runId, not per DOM element; button disabled in flight; engine: 1 analysis, 1 run record)',
|
|
434
|
+
'expected2 活动状态正确': 'PASS (engine state machine preparing/analyzing/promoting/ready -> succeeded; UI badge converges to 可检索 in every scenario, including after the unknown result)',
|
|
435
|
+
'expected3 请求结果未知先查询': 'PASS (after the dropped response the product client queried GET /api/runs/<runId> FIRST and never re-dispatched; user informed via toast)',
|
|
436
|
+
'explicit refusal recovery': 'PASS (a 409 releases the lock: the rebuilt button is enabled and the next click dispatches again)',
|
|
437
|
+
boundary: 'engine-level guards (per-project single writer, 409 on non-retryable, completed-SHA ledger) are covered by parallel-engine-final-eng011-test.js; this probe covers the product UI client behaviour with a real browser',
|
|
438
|
+
targetIdentity: 'these observations describe the UI bytes served from ' + path.resolve(UI_DIR) + '; the pinned/vendored bytes are unchanged by this lane',
|
|
439
|
+
};
|
|
440
|
+
await browser.close(); browser = null;
|
|
441
|
+
await host.close(); host = null;
|
|
442
|
+
await engine.close();
|
|
443
|
+
await vector.close();
|
|
444
|
+
saveEvidence(ARTIFACTS, 'eng011-ui-probe-evidence.json', evidence);
|
|
445
|
+
console.log('ENG-011 UI PROBE PASS');
|
|
446
|
+
process.exit(0);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
evidence.verdict = { failed: String((error && error.stack) || error) };
|
|
449
|
+
try { if (browser) await browser.close(); } catch {}
|
|
450
|
+
try { if (host) await host.close(); } catch {}
|
|
451
|
+
try { await engine.close(); } catch {}
|
|
452
|
+
try { await vector.close(); } catch {}
|
|
453
|
+
saveEvidence(ARTIFACTS, 'eng011-ui-probe-evidence.json', evidence);
|
|
454
|
+
console.error(`ENG-011 UI PROBE FAIL: ${error.message}`);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
main();
|