@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
|
@@ -1,252 +1,252 @@
|
|
|
1
|
-
// Run: node _site/_test/claude-session-lifecycle-sweep-test.js
|
|
2
|
-
//
|
|
3
|
-
// Regression test for the stale-active-session sweep in claude-cli-runner.
|
|
4
|
-
//
|
|
5
|
-
// Without the sweeper, three failure modes leave sessions stuck in
|
|
6
|
-
// state=running forever after the subprocess is gone:
|
|
7
|
-
// 1. dashboard restart kills the SDK parent query; claude.exe becomes
|
|
8
|
-
// orphaned; on reload the persisted record still says "running"
|
|
9
|
-
// 2. SIGKILL of the dashboard before the SDK for-await loop emits its
|
|
10
|
-
// terminal message
|
|
11
|
-
// 3. child Claude.exe dies under IPC drop; in-memory session has
|
|
12
|
-
// subprocess=null while state=running
|
|
13
|
-
//
|
|
14
|
-
// Symptom in production: a project keeps showing the pulsing "running"
|
|
15
|
-
// badge and the embedded terminal view stays empty because no live
|
|
16
|
-
// subprocess streams into it. Until the sweeper ships, the only manual
|
|
17
|
-
// workaround is a dashboard restart that the user has to remember.
|
|
18
|
-
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
const os = require('os');
|
|
21
|
-
const path = require('path');
|
|
22
|
-
|
|
23
|
-
const TMP_DATA = fs.mkdtempSync(path.join(os.tmpdir(), `kb-sweep-test-${process.pid}-`));
|
|
24
|
-
process.env.KB_DATA_DIR = TMP_DATA;
|
|
25
|
-
process.env.KB_STALE_ACTIVE_MS = '60000';
|
|
26
|
-
require(path.resolve(__dirname, '..', '..', '_site', 'lib', 'data-dir'))._resetCache();
|
|
27
|
-
|
|
28
|
-
// Seed the v2 registry and per-project config/state before loading the runner.
|
|
29
|
-
const PROJECT_IDS = ['demo-zombie-persisted', 'demo-zombie-memory', 'demo-fresh', 'demo-broadcast', 'demo-callback'];
|
|
30
|
-
const { defaultProjectConfig, defaultProjectState } = require('../lib/project-store');
|
|
31
|
-
const now = new Date().toISOString();
|
|
32
|
-
fs.mkdirSync(path.join(TMP_DATA, 'projects'), { recursive: true });
|
|
33
|
-
for (const projectId of PROJECT_IDS) {
|
|
34
|
-
const knowledgePath = path.join(TMP_DATA, 'knowledge', projectId);
|
|
35
|
-
const metadataPath = path.join(TMP_DATA, 'projects', projectId);
|
|
36
|
-
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
37
|
-
fs.mkdirSync(metadataPath, { recursive: true });
|
|
38
|
-
fs.writeFileSync(path.join(metadataPath, 'config.json'), JSON.stringify(defaultProjectConfig(projectId, {
|
|
39
|
-
displayName: projectId, storageName: projectId, repoPath: TMP_DATA, knowledgePath,
|
|
40
|
-
}), null, 2) + '\n', 'utf8');
|
|
41
|
-
fs.writeFileSync(path.join(metadataPath, 'state.json'), JSON.stringify(defaultProjectState(), null, 2) + '\n', 'utf8');
|
|
42
|
-
}
|
|
43
|
-
fs.writeFileSync(path.join(TMP_DATA, 'projects.json'), JSON.stringify({
|
|
44
|
-
schema: 'project-registry/v2', schemaVersion: 2, projectOrder: PROJECT_IDS,
|
|
45
|
-
projects: Object.fromEntries(PROJECT_IDS.map(projectId => [projectId, { createdAt: now, displayNameSnapshot: projectId }])),
|
|
46
|
-
revision: 1, updatedAt: now,
|
|
47
|
-
}, null, 2) + '\n', 'utf8');
|
|
48
|
-
fs.writeFileSync(path.join(TMP_DATA, 'claude-prompts.json'), '{"schema":"prompts/v1","prompts":{}}', 'utf-8');
|
|
49
|
-
|
|
50
|
-
const runner = require('../lib/claude-cli-runner');
|
|
51
|
-
const {
|
|
52
|
-
createSession,
|
|
53
|
-
demoteStaleActiveSessions,
|
|
54
|
-
ACTIVE_STATES,
|
|
55
|
-
TERMINAL_STATES,
|
|
56
|
-
listSessions,
|
|
57
|
-
subscribeList,
|
|
58
|
-
} = runner;
|
|
59
|
-
|
|
60
|
-
function assert(cond, msg) { if (!cond) throw new Error('ASSERT: ' + msg); }
|
|
61
|
-
|
|
62
|
-
function writePersistedRecord(projectSlug, record) {
|
|
63
|
-
const aiDir = path.join(TMP_DATA, 'runtime', 'claude-sessions', projectSlug);
|
|
64
|
-
fs.mkdirSync(aiDir, { recursive: true });
|
|
65
|
-
const target = path.join(aiDir, `${record.sessionId}.json`);
|
|
66
|
-
// Default updatedAt to the same stale time used for startedAt so the
|
|
67
|
-
// sweeper treats the record as orphan-aged. Callers can override either.
|
|
68
|
-
const longAgo = record.startedAt || new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
69
|
-
fs.writeFileSync(target, JSON.stringify({
|
|
70
|
-
schema: 'claude-workbench-session/v1',
|
|
71
|
-
updatedAt: record.updatedAt || longAgo,
|
|
72
|
-
...record,
|
|
73
|
-
}, null, 2) + '\n', 'utf-8');
|
|
74
|
-
return target;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function makeStalePersistedRecord(projectSlug, sessionId, state) {
|
|
78
|
-
const longAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
79
|
-
return writePersistedRecord(projectSlug, {
|
|
80
|
-
sessionId,
|
|
81
|
-
projectSlug,
|
|
82
|
-
projectPath: 'D:/proj/' + projectSlug,
|
|
83
|
-
kbPath: path.join(TMP_DATA, 'knowledge', projectSlug),
|
|
84
|
-
promptKey: 'post-commit-automation',
|
|
85
|
-
runner: 'sdk',
|
|
86
|
-
state,
|
|
87
|
-
startedAt: longAgo,
|
|
88
|
-
endedAt: null,
|
|
89
|
-
exitCode: null,
|
|
90
|
-
turns: 1,
|
|
91
|
-
error: null,
|
|
92
|
-
pendingPermission: null,
|
|
93
|
-
source: 'git-hook',
|
|
94
|
-
automation: true,
|
|
95
|
-
automationRunId: 'auto-test-1',
|
|
96
|
-
metadata: { source: 'git-hook', automation: true, automationRunId: 'auto-test-1' },
|
|
97
|
-
permissionMode: 'bypassPermissions',
|
|
98
|
-
events: [{ type: 'claude/system-prompt', text: 'fake', promptKey: 'post-commit-automation' }],
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
(() => {
|
|
103
|
-
// The sweeper itself is the unit under test.
|
|
104
|
-
assert(typeof demoteStaleActiveSessions === 'function',
|
|
105
|
-
'demoteStaleActiveSessions must be exported');
|
|
106
|
-
assert(ACTIVE_STATES.has('running'),
|
|
107
|
-
'ACTIVE_STATES must include running');
|
|
108
|
-
assert(TERMINAL_STATES.has('failed'),
|
|
109
|
-
'TERMINAL_STATES must include failed');
|
|
110
|
-
|
|
111
|
-
// (1) Persisted record that's been "running" for 10 minutes with no live
|
|
112
|
-
// subprocess — must be demoted. The user's exact scenario: dashboard
|
|
113
|
-
// restart orphaned the SDK parent query and the record on disk still
|
|
114
|
-
// says running.
|
|
115
|
-
{
|
|
116
|
-
const sessionId = 'sess-zombie-persisted-' + Date.now();
|
|
117
|
-
makeStalePersistedRecord('demo-zombie-persisted', sessionId, 'running');
|
|
118
|
-
|
|
119
|
-
const demoted = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
120
|
-
const hit = demoted.find(d => d.sessionId === sessionId);
|
|
121
|
-
assert(hit, 'sweeper should have demoted the persisted zombie');
|
|
122
|
-
assert(hit.source === 'persisted', `expected source=persisted, got ${hit.source}`);
|
|
123
|
-
|
|
124
|
-
// The rehydrated session in memory should now show failed + endedAt.
|
|
125
|
-
const state = runner.getState(sessionId);
|
|
126
|
-
assert(state.state === 'failed',
|
|
127
|
-
`expected state=failed after sweep, got ${state.state}`);
|
|
128
|
-
assert(state.endedAt, 'endedAt should be set after demotion');
|
|
129
|
-
assert(state.error && /subprocess lost/i.test(state.error),
|
|
130
|
-
`error message should explain the orphan, got ${state.error}`);
|
|
131
|
-
|
|
132
|
-
// Subsequent sweeps should be idempotent — already-failed sessions are
|
|
133
|
-
// not in ACTIVE_STATES so they don't get touched again.
|
|
134
|
-
const secondSweep = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
135
|
-
const hitAgain = secondSweep.find(d => d.sessionId === sessionId);
|
|
136
|
-
assert(!hitAgain,
|
|
137
|
-
`sweeper must not re-demote already-failed session (hit again: ${JSON.stringify(secondSweep)})`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// (2) In-memory session with state=running AND subprocess=null AND a
|
|
141
|
-
// stale updatedAt — must be demoted. This is the runtime-orphan case:
|
|
142
|
-
// SDK query was kicked off, but the subprocess ref was cleared without
|
|
143
|
-
// a terminal setState (e.g. the dashboard process died between the
|
|
144
|
-
// subprocess=null assignment and the setState(failed) on the catch path).
|
|
145
|
-
{
|
|
146
|
-
const projectSlug = 'demo-zombie-memory';
|
|
147
|
-
const session = createSession({
|
|
148
|
-
projectSlug,
|
|
149
|
-
projectPath: 'D:/proj/' + projectSlug,
|
|
150
|
-
kbPath: path.join(TMP_DATA, 'knowledge', projectSlug),
|
|
151
|
-
promptKey: 'post-commit-automation',
|
|
152
|
-
source: 'git-hook',
|
|
153
|
-
metadata: { source: 'git-hook', automation: true, automationRunId: 'auto-mem' },
|
|
154
|
-
});
|
|
155
|
-
// Force it into the orphan shape: state=running, subprocess=null,
|
|
156
|
-
// updatedAt way in the past.
|
|
157
|
-
session.state = 'running';
|
|
158
|
-
session.subprocess = null;
|
|
159
|
-
session.updatedAt = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
160
|
-
session.startedAt = session.updatedAt;
|
|
161
|
-
|
|
162
|
-
const demoted = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
163
|
-
const hit = demoted.find(d => d.sessionId === session.sessionId);
|
|
164
|
-
assert(hit, 'sweeper should have demoted the in-memory zombie');
|
|
165
|
-
assert(hit.source === 'memory', `expected source=memory, got ${hit.source}`);
|
|
166
|
-
|
|
167
|
-
const state = runner.getState(session.sessionId);
|
|
168
|
-
assert(state.state === 'failed',
|
|
169
|
-
`expected in-memory session to be failed, got ${state.state}`);
|
|
170
|
-
assert(state.error && /subprocess exited/i.test(state.error),
|
|
171
|
-
`expected subprocess-null error, got ${state.error}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// (3) Fresh session (started 5s ago) must NOT be demoted even if
|
|
175
|
-
// subprocess happens to be null. Threshold exists exactly to keep
|
|
176
|
-
// legitimately-thinking sessions from being killed prematurely.
|
|
177
|
-
{
|
|
178
|
-
const projectSlug = 'demo-fresh';
|
|
179
|
-
const session = createSession({
|
|
180
|
-
projectSlug,
|
|
181
|
-
projectPath: 'D:/proj/' + projectSlug,
|
|
182
|
-
kbPath: path.join(TMP_DATA, 'knowledge', projectSlug),
|
|
183
|
-
promptKey: 'post-commit-automation',
|
|
184
|
-
source: 'git-hook',
|
|
185
|
-
metadata: { source: 'git-hook', automation: true, automationRunId: 'auto-fresh' },
|
|
186
|
-
});
|
|
187
|
-
session.state = 'running';
|
|
188
|
-
session.subprocess = null;
|
|
189
|
-
// updatedAt defaults to createdAt via createSession, well under 60s old.
|
|
190
|
-
|
|
191
|
-
const demoted = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
192
|
-
const hit = demoted.find(d => d.sessionId === session.sessionId);
|
|
193
|
-
assert(!hit,
|
|
194
|
-
`fresh session (age <60s) must NOT be demoted; demoted list = ${JSON.stringify(demoted)}`);
|
|
195
|
-
|
|
196
|
-
// Cleanup so it doesn't leak into the next test.
|
|
197
|
-
runner.deleteSession(session.sessionId);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// (4) Demotion must fire the global SSE broadcast so the UI sees the
|
|
201
|
-
// running→failed transition. Without this the blue light stays on even
|
|
202
|
-
// after the in-memory state is wrong.
|
|
203
|
-
{
|
|
204
|
-
const projectSlug = 'demo-broadcast';
|
|
205
|
-
const broadcastEvents = [];
|
|
206
|
-
subscribeList(ev => broadcastEvents.push(ev));
|
|
207
|
-
|
|
208
|
-
const sessionId = 'sess-broadcast-' + Date.now();
|
|
209
|
-
makeStalePersistedRecord(projectSlug, sessionId, 'running');
|
|
210
|
-
|
|
211
|
-
demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
212
|
-
const sawTransition = broadcastEvents.some(
|
|
213
|
-
ev => ev.sessionId === sessionId && ev.state === 'failed' && ev.kind === 'stale-active-sweep'
|
|
214
|
-
);
|
|
215
|
-
assert(sawTransition,
|
|
216
|
-
`expected SSE broadcast for ${sessionId} with state=failed kind=stale-active-sweep; got ${JSON.stringify(broadcastEvents)}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// (5) The onSessionEnded hook must fire for failed sessions, so the
|
|
220
|
-
// automation queue (post-commit-automation.js) can promote the next
|
|
221
|
-
// queued run. Without this, stranded sessions would also strand any
|
|
222
|
-
// queued successor forever.
|
|
223
|
-
{
|
|
224
|
-
const projectSlug = 'demo-callback';
|
|
225
|
-
const sessionId = 'sess-callback-' + Date.now();
|
|
226
|
-
makeStalePersistedRecord(projectSlug, sessionId, 'running');
|
|
227
|
-
|
|
228
|
-
let endedWithFailed = null;
|
|
229
|
-
const off = runner.onSessionEnded(s => {
|
|
230
|
-
if (s.sessionId === sessionId && s.state === 'failed') {
|
|
231
|
-
endedWithFailed = s;
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
236
|
-
// onSessionEnded fires via setImmediate, so wait one tick.
|
|
237
|
-
return Promise.resolve()
|
|
238
|
-
.then(() => new Promise(resolve => setImmediate(resolve)))
|
|
239
|
-
.then(() => {
|
|
240
|
-
off();
|
|
241
|
-
assert(endedWithFailed,
|
|
242
|
-
`onSessionEnded should have fired for the demoted session`);
|
|
243
|
-
assert(endedWithFailed.sessionId === sessionId,
|
|
244
|
-
`wrong sessionId in callback: ${endedWithFailed && endedWithFailed.sessionId}`);
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
})().then(() => {
|
|
248
|
-
console.log('claude-session-lifecycle-sweep-test PASS');
|
|
249
|
-
}).catch(e => {
|
|
250
|
-
console.error(e.stack || e.message);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
});
|
|
1
|
+
// Run: node _site/_test/claude-session-lifecycle-sweep-test.js
|
|
2
|
+
//
|
|
3
|
+
// Regression test for the stale-active-session sweep in claude-cli-runner.
|
|
4
|
+
//
|
|
5
|
+
// Without the sweeper, three failure modes leave sessions stuck in
|
|
6
|
+
// state=running forever after the subprocess is gone:
|
|
7
|
+
// 1. dashboard restart kills the SDK parent query; claude.exe becomes
|
|
8
|
+
// orphaned; on reload the persisted record still says "running"
|
|
9
|
+
// 2. SIGKILL of the dashboard before the SDK for-await loop emits its
|
|
10
|
+
// terminal message
|
|
11
|
+
// 3. child Claude.exe dies under IPC drop; in-memory session has
|
|
12
|
+
// subprocess=null while state=running
|
|
13
|
+
//
|
|
14
|
+
// Symptom in production: a project keeps showing the pulsing "running"
|
|
15
|
+
// badge and the embedded terminal view stays empty because no live
|
|
16
|
+
// subprocess streams into it. Until the sweeper ships, the only manual
|
|
17
|
+
// workaround is a dashboard restart that the user has to remember.
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const os = require('os');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
const TMP_DATA = fs.mkdtempSync(path.join(os.tmpdir(), `kb-sweep-test-${process.pid}-`));
|
|
24
|
+
process.env.KB_DATA_DIR = TMP_DATA;
|
|
25
|
+
process.env.KB_STALE_ACTIVE_MS = '60000';
|
|
26
|
+
require(path.resolve(__dirname, '..', '..', '_site', 'lib', 'data-dir'))._resetCache();
|
|
27
|
+
|
|
28
|
+
// Seed the v2 registry and per-project config/state before loading the runner.
|
|
29
|
+
const PROJECT_IDS = ['demo-zombie-persisted', 'demo-zombie-memory', 'demo-fresh', 'demo-broadcast', 'demo-callback'];
|
|
30
|
+
const { defaultProjectConfig, defaultProjectState } = require('../lib/project-store');
|
|
31
|
+
const now = new Date().toISOString();
|
|
32
|
+
fs.mkdirSync(path.join(TMP_DATA, 'projects'), { recursive: true });
|
|
33
|
+
for (const projectId of PROJECT_IDS) {
|
|
34
|
+
const knowledgePath = path.join(TMP_DATA, 'knowledge', projectId);
|
|
35
|
+
const metadataPath = path.join(TMP_DATA, 'projects', projectId);
|
|
36
|
+
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
37
|
+
fs.mkdirSync(metadataPath, { recursive: true });
|
|
38
|
+
fs.writeFileSync(path.join(metadataPath, 'config.json'), JSON.stringify(defaultProjectConfig(projectId, {
|
|
39
|
+
displayName: projectId, storageName: projectId, repoPath: TMP_DATA, knowledgePath,
|
|
40
|
+
}), null, 2) + '\n', 'utf8');
|
|
41
|
+
fs.writeFileSync(path.join(metadataPath, 'state.json'), JSON.stringify(defaultProjectState(), null, 2) + '\n', 'utf8');
|
|
42
|
+
}
|
|
43
|
+
fs.writeFileSync(path.join(TMP_DATA, 'projects.json'), JSON.stringify({
|
|
44
|
+
schema: 'project-registry/v2', schemaVersion: 2, projectOrder: PROJECT_IDS,
|
|
45
|
+
projects: Object.fromEntries(PROJECT_IDS.map(projectId => [projectId, { createdAt: now, displayNameSnapshot: projectId }])),
|
|
46
|
+
revision: 1, updatedAt: now,
|
|
47
|
+
}, null, 2) + '\n', 'utf8');
|
|
48
|
+
fs.writeFileSync(path.join(TMP_DATA, 'claude-prompts.json'), '{"schema":"prompts/v1","prompts":{}}', 'utf-8');
|
|
49
|
+
|
|
50
|
+
const runner = require('../lib/claude-cli-runner');
|
|
51
|
+
const {
|
|
52
|
+
createSession,
|
|
53
|
+
demoteStaleActiveSessions,
|
|
54
|
+
ACTIVE_STATES,
|
|
55
|
+
TERMINAL_STATES,
|
|
56
|
+
listSessions,
|
|
57
|
+
subscribeList,
|
|
58
|
+
} = runner;
|
|
59
|
+
|
|
60
|
+
function assert(cond, msg) { if (!cond) throw new Error('ASSERT: ' + msg); }
|
|
61
|
+
|
|
62
|
+
function writePersistedRecord(projectSlug, record) {
|
|
63
|
+
const aiDir = path.join(TMP_DATA, 'runtime', 'claude-sessions', projectSlug);
|
|
64
|
+
fs.mkdirSync(aiDir, { recursive: true });
|
|
65
|
+
const target = path.join(aiDir, `${record.sessionId}.json`);
|
|
66
|
+
// Default updatedAt to the same stale time used for startedAt so the
|
|
67
|
+
// sweeper treats the record as orphan-aged. Callers can override either.
|
|
68
|
+
const longAgo = record.startedAt || new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
69
|
+
fs.writeFileSync(target, JSON.stringify({
|
|
70
|
+
schema: 'claude-workbench-session/v1',
|
|
71
|
+
updatedAt: record.updatedAt || longAgo,
|
|
72
|
+
...record,
|
|
73
|
+
}, null, 2) + '\n', 'utf-8');
|
|
74
|
+
return target;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeStalePersistedRecord(projectSlug, sessionId, state) {
|
|
78
|
+
const longAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
79
|
+
return writePersistedRecord(projectSlug, {
|
|
80
|
+
sessionId,
|
|
81
|
+
projectSlug,
|
|
82
|
+
projectPath: 'D:/proj/' + projectSlug,
|
|
83
|
+
kbPath: path.join(TMP_DATA, 'knowledge', projectSlug),
|
|
84
|
+
promptKey: 'post-commit-automation',
|
|
85
|
+
runner: 'sdk',
|
|
86
|
+
state,
|
|
87
|
+
startedAt: longAgo,
|
|
88
|
+
endedAt: null,
|
|
89
|
+
exitCode: null,
|
|
90
|
+
turns: 1,
|
|
91
|
+
error: null,
|
|
92
|
+
pendingPermission: null,
|
|
93
|
+
source: 'git-hook',
|
|
94
|
+
automation: true,
|
|
95
|
+
automationRunId: 'auto-test-1',
|
|
96
|
+
metadata: { source: 'git-hook', automation: true, automationRunId: 'auto-test-1' },
|
|
97
|
+
permissionMode: 'bypassPermissions',
|
|
98
|
+
events: [{ type: 'claude/system-prompt', text: 'fake', promptKey: 'post-commit-automation' }],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
(() => {
|
|
103
|
+
// The sweeper itself is the unit under test.
|
|
104
|
+
assert(typeof demoteStaleActiveSessions === 'function',
|
|
105
|
+
'demoteStaleActiveSessions must be exported');
|
|
106
|
+
assert(ACTIVE_STATES.has('running'),
|
|
107
|
+
'ACTIVE_STATES must include running');
|
|
108
|
+
assert(TERMINAL_STATES.has('failed'),
|
|
109
|
+
'TERMINAL_STATES must include failed');
|
|
110
|
+
|
|
111
|
+
// (1) Persisted record that's been "running" for 10 minutes with no live
|
|
112
|
+
// subprocess — must be demoted. The user's exact scenario: dashboard
|
|
113
|
+
// restart orphaned the SDK parent query and the record on disk still
|
|
114
|
+
// says running.
|
|
115
|
+
{
|
|
116
|
+
const sessionId = 'sess-zombie-persisted-' + Date.now();
|
|
117
|
+
makeStalePersistedRecord('demo-zombie-persisted', sessionId, 'running');
|
|
118
|
+
|
|
119
|
+
const demoted = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
120
|
+
const hit = demoted.find(d => d.sessionId === sessionId);
|
|
121
|
+
assert(hit, 'sweeper should have demoted the persisted zombie');
|
|
122
|
+
assert(hit.source === 'persisted', `expected source=persisted, got ${hit.source}`);
|
|
123
|
+
|
|
124
|
+
// The rehydrated session in memory should now show failed + endedAt.
|
|
125
|
+
const state = runner.getState(sessionId);
|
|
126
|
+
assert(state.state === 'failed',
|
|
127
|
+
`expected state=failed after sweep, got ${state.state}`);
|
|
128
|
+
assert(state.endedAt, 'endedAt should be set after demotion');
|
|
129
|
+
assert(state.error && /subprocess lost/i.test(state.error),
|
|
130
|
+
`error message should explain the orphan, got ${state.error}`);
|
|
131
|
+
|
|
132
|
+
// Subsequent sweeps should be idempotent — already-failed sessions are
|
|
133
|
+
// not in ACTIVE_STATES so they don't get touched again.
|
|
134
|
+
const secondSweep = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
135
|
+
const hitAgain = secondSweep.find(d => d.sessionId === sessionId);
|
|
136
|
+
assert(!hitAgain,
|
|
137
|
+
`sweeper must not re-demote already-failed session (hit again: ${JSON.stringify(secondSweep)})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// (2) In-memory session with state=running AND subprocess=null AND a
|
|
141
|
+
// stale updatedAt — must be demoted. This is the runtime-orphan case:
|
|
142
|
+
// SDK query was kicked off, but the subprocess ref was cleared without
|
|
143
|
+
// a terminal setState (e.g. the dashboard process died between the
|
|
144
|
+
// subprocess=null assignment and the setState(failed) on the catch path).
|
|
145
|
+
{
|
|
146
|
+
const projectSlug = 'demo-zombie-memory';
|
|
147
|
+
const session = createSession({
|
|
148
|
+
projectSlug,
|
|
149
|
+
projectPath: 'D:/proj/' + projectSlug,
|
|
150
|
+
kbPath: path.join(TMP_DATA, 'knowledge', projectSlug),
|
|
151
|
+
promptKey: 'post-commit-automation',
|
|
152
|
+
source: 'git-hook',
|
|
153
|
+
metadata: { source: 'git-hook', automation: true, automationRunId: 'auto-mem' },
|
|
154
|
+
});
|
|
155
|
+
// Force it into the orphan shape: state=running, subprocess=null,
|
|
156
|
+
// updatedAt way in the past.
|
|
157
|
+
session.state = 'running';
|
|
158
|
+
session.subprocess = null;
|
|
159
|
+
session.updatedAt = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
160
|
+
session.startedAt = session.updatedAt;
|
|
161
|
+
|
|
162
|
+
const demoted = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
163
|
+
const hit = demoted.find(d => d.sessionId === session.sessionId);
|
|
164
|
+
assert(hit, 'sweeper should have demoted the in-memory zombie');
|
|
165
|
+
assert(hit.source === 'memory', `expected source=memory, got ${hit.source}`);
|
|
166
|
+
|
|
167
|
+
const state = runner.getState(session.sessionId);
|
|
168
|
+
assert(state.state === 'failed',
|
|
169
|
+
`expected in-memory session to be failed, got ${state.state}`);
|
|
170
|
+
assert(state.error && /subprocess exited/i.test(state.error),
|
|
171
|
+
`expected subprocess-null error, got ${state.error}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// (3) Fresh session (started 5s ago) must NOT be demoted even if
|
|
175
|
+
// subprocess happens to be null. Threshold exists exactly to keep
|
|
176
|
+
// legitimately-thinking sessions from being killed prematurely.
|
|
177
|
+
{
|
|
178
|
+
const projectSlug = 'demo-fresh';
|
|
179
|
+
const session = createSession({
|
|
180
|
+
projectSlug,
|
|
181
|
+
projectPath: 'D:/proj/' + projectSlug,
|
|
182
|
+
kbPath: path.join(TMP_DATA, 'knowledge', projectSlug),
|
|
183
|
+
promptKey: 'post-commit-automation',
|
|
184
|
+
source: 'git-hook',
|
|
185
|
+
metadata: { source: 'git-hook', automation: true, automationRunId: 'auto-fresh' },
|
|
186
|
+
});
|
|
187
|
+
session.state = 'running';
|
|
188
|
+
session.subprocess = null;
|
|
189
|
+
// updatedAt defaults to createdAt via createSession, well under 60s old.
|
|
190
|
+
|
|
191
|
+
const demoted = demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
192
|
+
const hit = demoted.find(d => d.sessionId === session.sessionId);
|
|
193
|
+
assert(!hit,
|
|
194
|
+
`fresh session (age <60s) must NOT be demoted; demoted list = ${JSON.stringify(demoted)}`);
|
|
195
|
+
|
|
196
|
+
// Cleanup so it doesn't leak into the next test.
|
|
197
|
+
runner.deleteSession(session.sessionId);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// (4) Demotion must fire the global SSE broadcast so the UI sees the
|
|
201
|
+
// running→failed transition. Without this the blue light stays on even
|
|
202
|
+
// after the in-memory state is wrong.
|
|
203
|
+
{
|
|
204
|
+
const projectSlug = 'demo-broadcast';
|
|
205
|
+
const broadcastEvents = [];
|
|
206
|
+
subscribeList(ev => broadcastEvents.push(ev));
|
|
207
|
+
|
|
208
|
+
const sessionId = 'sess-broadcast-' + Date.now();
|
|
209
|
+
makeStalePersistedRecord(projectSlug, sessionId, 'running');
|
|
210
|
+
|
|
211
|
+
demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
212
|
+
const sawTransition = broadcastEvents.some(
|
|
213
|
+
ev => ev.sessionId === sessionId && ev.state === 'failed' && ev.kind === 'stale-active-sweep'
|
|
214
|
+
);
|
|
215
|
+
assert(sawTransition,
|
|
216
|
+
`expected SSE broadcast for ${sessionId} with state=failed kind=stale-active-sweep; got ${JSON.stringify(broadcastEvents)}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// (5) The onSessionEnded hook must fire for failed sessions, so the
|
|
220
|
+
// automation queue (post-commit-automation.js) can promote the next
|
|
221
|
+
// queued run. Without this, stranded sessions would also strand any
|
|
222
|
+
// queued successor forever.
|
|
223
|
+
{
|
|
224
|
+
const projectSlug = 'demo-callback';
|
|
225
|
+
const sessionId = 'sess-callback-' + Date.now();
|
|
226
|
+
makeStalePersistedRecord(projectSlug, sessionId, 'running');
|
|
227
|
+
|
|
228
|
+
let endedWithFailed = null;
|
|
229
|
+
const off = runner.onSessionEnded(s => {
|
|
230
|
+
if (s.sessionId === sessionId && s.state === 'failed') {
|
|
231
|
+
endedWithFailed = s;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
demoteStaleActiveSessions({ thresholdMs: 60 * 1000 });
|
|
236
|
+
// onSessionEnded fires via setImmediate, so wait one tick.
|
|
237
|
+
return Promise.resolve()
|
|
238
|
+
.then(() => new Promise(resolve => setImmediate(resolve)))
|
|
239
|
+
.then(() => {
|
|
240
|
+
off();
|
|
241
|
+
assert(endedWithFailed,
|
|
242
|
+
`onSessionEnded should have fired for the demoted session`);
|
|
243
|
+
assert(endedWithFailed.sessionId === sessionId,
|
|
244
|
+
`wrong sessionId in callback: ${endedWithFailed && endedWithFailed.sessionId}`);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
})().then(() => {
|
|
248
|
+
console.log('claude-session-lifecycle-sweep-test PASS');
|
|
249
|
+
}).catch(e => {
|
|
250
|
+
console.error(e.stack || e.message);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
});
|