@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,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// NAV-003 — 空项目。
|
|
3
|
+
//
|
|
4
|
+
// A brand new data directory must show real empty states, a failed registry
|
|
5
|
+
// read must not be presented as an empty library, and nothing may create a
|
|
6
|
+
// placeholder project on the way.
|
|
7
|
+
//
|
|
8
|
+
// The registry read is failed by injecting an HTTP 503 response from a proxy
|
|
9
|
+
// in front of the product: the read fails at the HTTP level (the browser
|
|
10
|
+
// really receives a failure answer), which is deliberately not a
|
|
11
|
+
// network-level disconnection. The regression drives the real UI in a real
|
|
12
|
+
// browser against an isolated instance (own port, own data dir, own knowledge
|
|
13
|
+
// dir, own Bridge journal, own browser profile). Screenshots are written to
|
|
14
|
+
// PK_TEST_EVIDENCE_DIR when the batch sets it, so this tracked file stays
|
|
15
|
+
// free of machine-specific paths.
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const os = require('node:os');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const assert = require('node:assert/strict');
|
|
21
|
+
|
|
22
|
+
// One temp root per run, resolved from the caller's environment: the batch
|
|
23
|
+
// contract's TEMP/TMP/TMPDIR (and a CI runner's) flow into os.tmpdir(), so
|
|
24
|
+
// this tracked file carries no machine-specific path and never rewrites the
|
|
25
|
+
// process environment.
|
|
26
|
+
const TEMP_ROOT = os.tmpdir();
|
|
27
|
+
|
|
28
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
29
|
+
const { findChrome } = require('./helpers/find-chrome');
|
|
30
|
+
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
31
|
+
const { attachNetworkCapture } = require('./helpers/cdp-network-capture');
|
|
32
|
+
const { createProductProxy } = require('./helpers/module-fixture');
|
|
33
|
+
const freePort = require('./helpers/free-port');
|
|
34
|
+
|
|
35
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
const fixture = fs.mkdtempSync(path.join(TEMP_ROOT, 'pk-product-empty-state-'));
|
|
39
|
+
const evidenceDir = process.env.PK_TEST_EVIDENCE_DIR ? path.resolve(process.env.PK_TEST_EVIDENCE_DIR) : path.join(fixture, 'evidence');
|
|
40
|
+
fs.mkdirSync(evidenceDir, { recursive: true });
|
|
41
|
+
const port = await freePort();
|
|
42
|
+
const base = `http://127.0.0.1:${port}`;
|
|
43
|
+
const server = spawnServer({
|
|
44
|
+
root: ROOT,
|
|
45
|
+
port,
|
|
46
|
+
dataDir: path.join(fixture, 'data'),
|
|
47
|
+
tag: 'product-empty-state',
|
|
48
|
+
analysisOwner: 'engine',
|
|
49
|
+
});
|
|
50
|
+
const proxy = createProductProxy({ target: base, tag: 'product-empty-state' });
|
|
51
|
+
let browser;
|
|
52
|
+
try {
|
|
53
|
+
await waitFor(async () => {
|
|
54
|
+
try { return (await fetch(base + '/api/health')).ok; } catch { return false; }
|
|
55
|
+
}, 'the isolated shell', 20000);
|
|
56
|
+
const proxyUrl = await proxy.start();
|
|
57
|
+
|
|
58
|
+
// The very first registry read of this instance is answered with an
|
|
59
|
+
// injected HTTP 503 by the proxy in front of the product.
|
|
60
|
+
proxy.fail(
|
|
61
|
+
entry => entry.method === 'GET' && entry.path.startsWith('/api/projects/projection'),
|
|
62
|
+
503,
|
|
63
|
+
'注入的登记读取故障',
|
|
64
|
+
{ once: true },
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
browser = await launchCdpBrowser({
|
|
68
|
+
chrome: findChrome(),
|
|
69
|
+
profileDir: path.join(fixture, 'browser'),
|
|
70
|
+
url: proxyUrl + '/',
|
|
71
|
+
width: 1920,
|
|
72
|
+
height: 1080,
|
|
73
|
+
});
|
|
74
|
+
await browser.setViewport(1920, 1080);
|
|
75
|
+
const capture = attachNetworkCapture(browser);
|
|
76
|
+
await capture.enable();
|
|
77
|
+
const run = expression => browser.evaluate(expression);
|
|
78
|
+
|
|
79
|
+
// NAV-003 expected 2: the failed read is reported, never dressed up as an
|
|
80
|
+
// empty library.
|
|
81
|
+
await waitFor(() => run("document.getElementById('homeContent').textContent.includes('项目登记读取失败')"), 'the failed registry read is reported', 20000);
|
|
82
|
+
const failedRead = proxy.requests.find(entry => entry.path.startsWith('/api/projects/projection'));
|
|
83
|
+
assert.ok(failedRead, 'the registry read really left the browser for the product proxy');
|
|
84
|
+
assert.equal(failedRead.status, 503, 'the proxy really answered the registry read with an HTTP 503 failure');
|
|
85
|
+
assert.equal(await run("document.getElementById('homeContent').textContent.includes('从第一个项目开始')"), false, 'a failed read is not shown as an empty library');
|
|
86
|
+
assert.equal(await run("document.querySelectorAll('#homeContent [data-action=\"refresh\"]').length"), 1, 'the failed read offers a real retry');
|
|
87
|
+
await waitFor(() => run("document.getElementById('toast').textContent.includes('暂未刷新')"), 'the failed read is reported to the user', 10000);
|
|
88
|
+
await browser.screenshot(path.join(evidenceDir, 'empty-state-read-failure.png'));
|
|
89
|
+
|
|
90
|
+
// The retry reads the registry for real; now the empty state is a fact.
|
|
91
|
+
await run("document.querySelector('#homeContent [data-action=\"refresh\"]').click()");
|
|
92
|
+
await waitFor(() => run("document.getElementById('homeContent').textContent.includes('从第一个项目开始')"), 'the real empty home', 20000);
|
|
93
|
+
assert.equal(await run("document.getElementById('homeContent').textContent.includes('项目登记读取失败')"), false, 'the recovered read clears the failure');
|
|
94
|
+
await browser.screenshot(path.join(evidenceDir, 'empty-state-home.png'));
|
|
95
|
+
|
|
96
|
+
// NAV-003 expected 1: work, search, domains and projects show empty
|
|
97
|
+
// states.
|
|
98
|
+
await run("document.querySelector('[data-view=\"work\"]').click()");
|
|
99
|
+
await waitFor(() => run("document.getElementById('view-work').classList.contains('active')"), 'the work view');
|
|
100
|
+
assert.equal(await run("document.getElementById('workEmpty').hidden"), false, 'the work view shows its empty state');
|
|
101
|
+
assert.equal(await run("document.getElementById('workEmpty').textContent.includes('导入项目后')"), true, 'the work empty state explains what is missing');
|
|
102
|
+
assert.equal(await run("document.getElementById('workLayout').hidden"), true, 'the work layout stays hidden without a project');
|
|
103
|
+
await browser.screenshot(path.join(evidenceDir, 'empty-state-work.png'));
|
|
104
|
+
|
|
105
|
+
await run("document.querySelector('[data-view=\"search\"]').click()");
|
|
106
|
+
await waitFor(() => run("document.getElementById('view-search').classList.contains('active')"), 'the search view');
|
|
107
|
+
assert.equal(await run("document.getElementById('searchEmpty').hidden"), false, 'the search view shows its empty state');
|
|
108
|
+
assert.equal(await run("document.getElementById('searchEmpty').textContent.includes('尚未导入项目')"), true, 'the search empty state explains what is missing');
|
|
109
|
+
await browser.screenshot(path.join(evidenceDir, 'empty-state-search.png'));
|
|
110
|
+
|
|
111
|
+
await run("document.querySelector('[data-view=\"projects\"]').click()");
|
|
112
|
+
await waitFor(() => run("document.getElementById('view-projects').classList.contains('active')"), 'the projects view');
|
|
113
|
+
assert.equal(await run("document.getElementById('projectTree').textContent.includes('暂无项目')"), true, 'the project tree shows the empty group');
|
|
114
|
+
assert.equal(await run("document.getElementById('projectTree').textContent.includes('未分组')"), true, 'the empty project tree still shows the ungrouped domain');
|
|
115
|
+
assert.equal(await run("document.getElementById('projectDetail').textContent.includes('尚未导入项目')"), true, 'the project detail shows the empty state');
|
|
116
|
+
await browser.screenshot(path.join(evidenceDir, 'empty-state-projects.png'));
|
|
117
|
+
|
|
118
|
+
const brain = await (await fetch(base + '/api/brain')).json();
|
|
119
|
+
assert.equal((brain.brain.domains || []).length, 0, 'a fresh instance owns no domain');
|
|
120
|
+
|
|
121
|
+
// NAV-003 expected 3: nothing created a placeholder project.
|
|
122
|
+
assert.equal(await run("document.querySelectorAll('#projectTree [data-select-project]').length"), 0, 'the project tree offers no project');
|
|
123
|
+
assert.equal(await run("document.getElementById('projectSelect').disabled"), true, 'the project selector stays disabled');
|
|
124
|
+
assert.equal(await run("document.getElementById('projectSelect').value"), '', 'no project is selected');
|
|
125
|
+
assert.equal(await run("localStorage.getItem('kb.project')"), null, 'no project id is remembered');
|
|
126
|
+
const aggregated = await (await fetch(base + '/api/projects/aggregated')).json();
|
|
127
|
+
assert.equal(aggregated.projects.length, 0, 'the product registers no project');
|
|
128
|
+
const registryPath = path.join(server.dataDir, 'projects.json');
|
|
129
|
+
if (fs.existsSync(registryPath)) {
|
|
130
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
131
|
+
assert.equal(Object.keys(registry.projects || {}).length, 0, 'the project registry stays empty');
|
|
132
|
+
}
|
|
133
|
+
const projectDirs = path.join(server.dataDir, 'projects');
|
|
134
|
+
assert.equal(fs.existsSync(projectDirs) ? fs.readdirSync(projectDirs).length : 0, 0, 'no project metadata directory is created');
|
|
135
|
+
const writes = capture.find(entry => entry.url.includes('/api/') && !['GET', 'HEAD', 'OPTIONS'].includes(entry.method));
|
|
136
|
+
assert.deepEqual(writes.map(entry => `${entry.method} ${entry.url}`), [], 'the empty state never writes a project');
|
|
137
|
+
assert.equal(browser.exceptions.length, 0, `no uncaught product JavaScript exceptions: ${JSON.stringify(browser.exceptions)}`);
|
|
138
|
+
|
|
139
|
+
console.log('product-empty-state-test PASS (real empty states, a failed read is not an empty library, no placeholder project)');
|
|
140
|
+
} finally {
|
|
141
|
+
await browser?.close();
|
|
142
|
+
await proxy.close();
|
|
143
|
+
await server.cleanup();
|
|
144
|
+
fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|
|
@@ -1,104 +1,219 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const assert = require('node:assert/strict');
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const os = require('node:os');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const http = require('node:http');
|
|
7
|
-
const { spawnSync } = require('node:child_process');
|
|
8
|
-
const { spawnServer } = require('./helpers/spawn-server');
|
|
9
|
-
const { findChrome } = require('./helpers/find-chrome');
|
|
10
|
-
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await click('#
|
|
66
|
-
await
|
|
67
|
-
|
|
68
|
-
assert.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await click('#
|
|
72
|
-
await waitFor(() => run("document.getElementById('
|
|
73
|
-
|
|
74
|
-
assert.ok(
|
|
75
|
-
assert.equal((
|
|
76
|
-
assert.equal(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
assert.
|
|
80
|
-
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
assert.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
assert.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
assert.
|
|
94
|
-
|
|
95
|
-
assert.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
await
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const http = require('node:http');
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
9
|
+
const { findChrome } = require('./helpers/find-chrome');
|
|
10
|
+
const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
|
|
11
|
+
const { attachNetworkCapture } = require('./helpers/cdp-network-capture');
|
|
12
|
+
|
|
13
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
14
|
+
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-product-import-ui-'));
|
|
15
|
+
const dataDir = path.join(fixture, 'data');
|
|
16
|
+
const repo = path.join(fixture, '中文 空格项目');
|
|
17
|
+
const knowledgeRoot = path.join(fixture, '知识目录');
|
|
18
|
+
const knowledge = path.join(knowledgeRoot, '项目知识');
|
|
19
|
+
const git = args => {
|
|
20
|
+
const result = spawnSync('git', args, { cwd: repo, windowsHide: true, encoding: 'utf8' });
|
|
21
|
+
assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
|
|
22
|
+
};
|
|
23
|
+
const gitIn = (dir, args) => {
|
|
24
|
+
const result = spawnSync('git', args, { cwd: dir, windowsHide: true, encoding: 'utf8' });
|
|
25
|
+
assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
|
|
26
|
+
};
|
|
27
|
+
async function main() {
|
|
28
|
+
fs.mkdirSync(repo); fs.mkdirSync(knowledgeRoot);
|
|
29
|
+
git(['init', '-q']); git(['config', 'user.email', 'ui-regression@local.invalid']); git(['config', 'user.name', 'UI Regression']);
|
|
30
|
+
fs.writeFileSync(path.join(repo, 'README.md'), '# UI regression baseline\n'); git(['add', '.']); git(['commit', '-qm', 'baseline']);
|
|
31
|
+
const baseline = git(['rev-parse', 'HEAD']);
|
|
32
|
+
// Offline modules are intentional: retain registration and report partial
|
|
33
|
+
// wiring, rather than fabricate successful links.
|
|
34
|
+
const offline = http.createServer((req, res) => { res.writeHead(503, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { code: 'MODULE_OFFLINE', message: '验收中的离线模块' } })); });
|
|
35
|
+
await new Promise(resolve => offline.listen(0, '127.0.0.1', resolve));
|
|
36
|
+
const offlineUrl = `http://127.0.0.1:${offline.address().port}`;
|
|
37
|
+
const port = 7960 + process.pid % 200;
|
|
38
|
+
const base = `http://127.0.0.1:${port}`;
|
|
39
|
+
const server = spawnServer({ root: ROOT, port, dataDir, tag: 'product-import-ui', analysisOwner: 'engine', extraEnv: {
|
|
40
|
+
KB_MODULES_REGISTER: '1', KB_TERMINAL_URL: offlineUrl, KB_VECTORHUB_URL: offlineUrl,
|
|
41
|
+
KB_EVENTBRIDGE_URL: offlineUrl, KB_KNOWLEDGE_ENGINE_URL: offlineUrl,
|
|
42
|
+
} });
|
|
43
|
+
let browser;
|
|
44
|
+
const api = async route => { const response = await fetch(base + route, { signal: AbortSignal.timeout(10000) }); assert.ok(response.ok, route); return response.json(); };
|
|
45
|
+
try {
|
|
46
|
+
await waitFor(async () => { try { return (await api('/api/health')).ok; } catch { return false; } }, 'isolated shell', 20000);
|
|
47
|
+
browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(fixture, 'browser'), url: base, width: 1920, height: 1080 });
|
|
48
|
+
await browser.setViewport(1920, 1080);
|
|
49
|
+
const capture = attachNetworkCapture(browser);
|
|
50
|
+
await capture.enable();
|
|
51
|
+
const run = expression => browser.evaluate(expression);
|
|
52
|
+
const click = selector => run(`document.querySelector(${JSON.stringify(selector)}).click()`);
|
|
53
|
+
const fill = (selector, value) => run(`(() => { const field=document.querySelector(${JSON.stringify(selector)}); field.value=${JSON.stringify(value)}; field.dispatchEvent(new Event('input',{bubbles:true})); })()`);
|
|
54
|
+
await waitFor(() => run("document.getElementById('homeContent').textContent.includes('第一个项目')"), 'real empty state');
|
|
55
|
+
for (const view of ['work', 'search', 'memory', 'projects', 'activity', 'home']) {
|
|
56
|
+
await click(`[data-view="${view}"]`);
|
|
57
|
+
assert.equal(await run(`document.getElementById('view-${view}').classList.contains('active')`), true);
|
|
58
|
+
assert.equal(await run('document.documentElement.scrollWidth > innerWidth'), false, `desktop overflow in ${view}`);
|
|
59
|
+
}
|
|
60
|
+
await click('#themeToggle'); assert.equal(await run('document.documentElement.dataset.theme'), 'dark');
|
|
61
|
+
await click('[data-view="projects"]'); await click('#createDomain');
|
|
62
|
+
// PRJ-001: an empty name is refused in the browser itself, so no request
|
|
63
|
+
// ever reaches the product and no domain is persisted.
|
|
64
|
+
await fill('#domainForm input[name="name"]', '');
|
|
65
|
+
await click('#domainForm button[type="submit"]');
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, 400));
|
|
67
|
+
assert.equal(await run("document.getElementById('domainDialog').open"), true, 'an empty domain name must not close the dialog');
|
|
68
|
+
assert.equal(await run("document.getElementById('domainForm').checkValidity()"), false, 'the domain form reports an empty name as invalid');
|
|
69
|
+
assert.equal(capture.find(entry => entry.method === 'POST' && entry.url.endsWith('/api/domains')).length, 0, 'an empty domain name must not reach the product');
|
|
70
|
+
assert.equal(((await api('/api/settings')).settings.brain.domains || []).length, 0, 'an empty domain name must not persist a domain');
|
|
71
|
+
await fill('#domainForm input[name="name"]', '回归领域'); await click('#domainForm button[type="submit"]');
|
|
72
|
+
await waitFor(() => run("!document.getElementById('domainDialog').open && document.getElementById('projectTree').textContent.includes('回归领域')"), 'domain persisted from UI');
|
|
73
|
+
const domainsOnDisk = JSON.parse(fs.readFileSync(path.join(dataDir, 'settings.json'), 'utf8')).brain.domains || [];
|
|
74
|
+
assert.ok(domainsOnDisk.some(domain => domain.name === '回归领域'), 'the domain is persisted on disk');
|
|
75
|
+
assert.equal(capture.find(entry => entry.method === 'POST' && entry.url.endsWith('/api/domains')).length, 1, 'only the named domain reached the product');
|
|
76
|
+
assert.equal((await api('/api/projects/aggregated')).projects.length, 0, 'creating a domain must not create a Git project');
|
|
77
|
+
assert.ok(!fs.existsSync(path.join(dataDir, 'projects.json')) || Object.keys(JSON.parse(fs.readFileSync(path.join(dataDir, 'projects.json'), 'utf8')).projects || {}).length === 0, 'the project registry stays empty after a domain is created');
|
|
78
|
+
await click('.view.active [data-action="import"]');
|
|
79
|
+
assert.equal(await run("document.getElementById('confirmImport').disabled"), true, 'the import stays disabled before a preflight is ready');
|
|
80
|
+
assert.equal(await run("document.getElementById('pickCode').hidden || document.getElementById('pickKnowledge').hidden"), false);
|
|
81
|
+
await fill('#importCode', repo); await fill('#importName', '回归项目'); await fill('#importKnowledge', knowledge);
|
|
82
|
+
const domainId = await run("[...document.getElementById('importDomain').options].find(o=>o.textContent==='回归领域').value");
|
|
83
|
+
await run(`document.getElementById('importDomain').value=${JSON.stringify(domainId)}`);
|
|
84
|
+
await waitFor(() => run("!document.getElementById('setupImportRoot').hidden"), 'root missing action');
|
|
85
|
+
assert.equal(await run("document.getElementById('confirmImport').disabled"), true, 'the import stays disabled while the knowledge root is missing');
|
|
86
|
+
await click('#setupImportRoot'); await fill('#knowledgeRoot', knowledgeRoot); await click('#saveBrainSettings');
|
|
87
|
+
await waitFor(() => run("document.getElementById('importDialog').open && !document.getElementById('confirmImport').disabled"), 'setup returns to ready import');
|
|
88
|
+
const values = await run("Object.fromEntries(['importCode','importKnowledge','importName','importDomain'].map(id=>[id,document.getElementById(id).value]))");
|
|
89
|
+
assert.deepEqual(values, { importCode: repo, importKnowledge: knowledge, importName: '回归项目', importDomain: domainId }, 'the original import inputs survive the knowledge-root round trip');
|
|
90
|
+
// PRJ-002/PRJ-003: the import id travels with the preflight and must
|
|
91
|
+
// survive the knowledge-root round trip unchanged.
|
|
92
|
+
const preflightRequest = capture.lastApi('POST', '/api/projects/preflight-import');
|
|
93
|
+
assert.ok(preflightRequest && preflightRequest.body && typeof preflightRequest.body.projectId === 'string' && preflightRequest.body.projectId, 'the preflight request carries the import id');
|
|
94
|
+
const preflightResponse = await capture.body(preflightRequest);
|
|
95
|
+
assert.ok(preflightResponse && preflightResponse.ready === true, 'the preflight that enables the import reports ready');
|
|
96
|
+
const racedHook = path.join(repo, '.git', 'hooks', 'post-commit');
|
|
97
|
+
fs.writeFileSync(racedHook, '#!/bin/sh\necho third-party fixture\n');
|
|
98
|
+
await click('#confirmImport');
|
|
99
|
+
await waitFor(() => run("document.getElementById('preflightStatus').textContent.includes('HOOK_CONFLICT') && document.getElementById('preflightStatus').textContent.includes('操作 ID:op-')"), 'durable import failure feedback');
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 750));
|
|
101
|
+
assert.ok(await run("document.getElementById('preflightStatus').textContent.includes('HOOK_CONFLICT')"), 'failed import must not turn back into a passed preflight');
|
|
102
|
+
assert.equal((await api('/api/projects/aggregated')).projects.length, 0, 'the refused import registers no project');
|
|
103
|
+
assert.equal(fs.readFileSync(racedHook, 'utf8'), '#!/bin/sh\necho third-party fixture\n', 'the third-party Hook bytes are preserved');
|
|
104
|
+
const failureLogs = (await api('/api/logs?event=project.import.failed')).entries;
|
|
105
|
+
assert.equal(failureLogs.length, 1, 'the failed import is persisted as one failure record'); assert.equal(failureLogs[0].error.code, 'HOOK_CONFLICT');
|
|
106
|
+
assert.ok(await run(`document.getElementById('preflightStatus').textContent.includes(${JSON.stringify(failureLogs[0].operationId)})`), 'UI operation ID resolves to the persisted failure');
|
|
107
|
+
fs.unlinkSync(racedHook);
|
|
108
|
+
await click('#confirmImport');
|
|
109
|
+
await waitFor(() => run("!document.getElementById('importDialog').open && document.getElementById('projectDetail').textContent.includes('回归项目')"), 'import feedback', 30000);
|
|
110
|
+
const selected = await run("document.getElementById('projectSelect').value");
|
|
111
|
+
const project = (await api('/api/projects/aggregated')).projects.find(p => p.projectId === selected);
|
|
112
|
+
assert.ok(project && Object.values(project.modules).every(module => !module.registered));
|
|
113
|
+
assert.equal((await api('/api/projects/aggregated')).projects.length, 1, 'the knowledge-root round trip must not create a second project');
|
|
114
|
+
assert.equal(path.resolve(project.workspacePath), path.resolve(repo)); assert.equal(path.resolve(project.knowledgePath), path.resolve(knowledge));
|
|
115
|
+
assert.equal(project.domainId, domainId);
|
|
116
|
+
// PRJ-002: the four modules are reported one by one, each with its own
|
|
117
|
+
// status, both in the import result and in the registered project view.
|
|
118
|
+
const importRequest = capture.lastApi('POST', '/api/projects/import');
|
|
119
|
+
assert.ok(importRequest && importRequest.body && importRequest.body.projectId, 'the import request carries the import id');
|
|
120
|
+
assert.equal(importRequest.body.projectId, preflightRequest.body.projectId, 'the import id survives the knowledge-root round trip');
|
|
121
|
+
assert.equal(selected, importRequest.body.projectId, 'the registered project keeps the import id');
|
|
122
|
+
const importResponse = await capture.body(importRequest);
|
|
123
|
+
assert.ok(importResponse, 'the import response is readable');
|
|
124
|
+
assert.deepEqual(Object.keys(importResponse.modules).sort(), ['engine', 'eventBridge', 'terminal', 'vectorHub'], 'the import reports each of the four modules separately');
|
|
125
|
+
for (const moduleKey of ['engine', 'eventBridge', 'terminal', 'vectorHub']) {
|
|
126
|
+
assert.equal(typeof importResponse.modules[moduleKey].status, 'string', `${moduleKey} reports its own status`);
|
|
127
|
+
}
|
|
128
|
+
assert.deepEqual(Object.keys(project.modules).sort(), ['engine', 'eventBridge', 'terminal', 'vectorHub'], 'the project view reports each of the four modules separately');
|
|
129
|
+
for (const [moduleKey, module] of Object.entries(project.modules)) {
|
|
130
|
+
assert.equal(module.registered, false, `${moduleKey} is not registered against the offline module`);
|
|
131
|
+
assert.equal(typeof module.status, 'string', `${moduleKey} reports its own status`);
|
|
132
|
+
}
|
|
133
|
+
assert.ok(fs.existsSync(path.join(repo, '.git/hooks/post-commit')));
|
|
134
|
+
assert.ok(fs.existsSync(knowledge)); assert.equal(git(['rev-parse', 'HEAD']), baseline, 'import must not commit or analyze history');
|
|
135
|
+
// PRJ-002: the source history is left alone, and analysis starts from the
|
|
136
|
+
// imported HEAD instead of walking older commits.
|
|
137
|
+
assert.deepEqual(git(['log', '--format=%H']).split('\n').filter(Boolean), [baseline], 'import must not create or rewrite commits');
|
|
138
|
+
const importedProject = (await api(`/api/projects/${selected}`)).project;
|
|
139
|
+
assert.equal(importedProject.state.trackingStartCommit, baseline, 'analysis starts from the imported HEAD');
|
|
140
|
+
assert.ok(!fs.existsSync(path.join(dataDir, 'runtime', 'processed-commits', selected)), 'import must not analyze the existing history');
|
|
141
|
+
assert.match(await run("document.getElementById('toast').textContent"), /部分接线待恢复/);
|
|
142
|
+
await click('.view.active [data-action="import"]'); await fill('#importCode', repo); await fill('#importKnowledge', knowledge);
|
|
143
|
+
await waitFor(() => run("document.getElementById('preflightStatus').textContent.includes('此项目已导入')"), 'duplicate rejected');
|
|
144
|
+
assert.equal(await run("document.getElementById('confirmImport').disabled"), true);
|
|
145
|
+
assert.equal((await api('/api/projects/aggregated')).projects.length, 1);
|
|
146
|
+
// PRJ-012: the other half of the case — the import itself succeeds, and
|
|
147
|
+
// the registry read that follows it fails. The failure is injected at the
|
|
148
|
+
// transport level in the browser (the answer is really lost), so the
|
|
149
|
+
// product never sees a broken request and the page has to cope with a
|
|
150
|
+
// read it never got.
|
|
151
|
+
const repo2 = path.join(fixture, '第二个项目');
|
|
152
|
+
const knowledge2 = path.join(knowledgeRoot, '第二个项目知识');
|
|
153
|
+
fs.mkdirSync(repo2);
|
|
154
|
+
gitIn(repo2, ['init', '-q']); gitIn(repo2, ['config', 'user.email', 'ui-regression@local.invalid']); gitIn(repo2, ['config', 'user.name', 'UI Regression']);
|
|
155
|
+
fs.writeFileSync(path.join(repo2, 'README.md'), '# second project baseline\n'); gitIn(repo2, ['add', '.']); gitIn(repo2, ['commit', '-qm', 'baseline']);
|
|
156
|
+
const fetchFaults = { armed: false, failed: [] };
|
|
157
|
+
const onFetchMessage = raw => {
|
|
158
|
+
let message; try { message = JSON.parse(String(raw)); } catch { return; }
|
|
159
|
+
if (message.method !== 'Fetch.requestPaused') return;
|
|
160
|
+
const { requestId, request } = message.params;
|
|
161
|
+
if (fetchFaults.armed) {
|
|
162
|
+
fetchFaults.failed.push({ url: request.url, at: Date.now() });
|
|
163
|
+
browser.send('Fetch.failRequest', { requestId, errorReason: 'ConnectionFailed' }).catch(() => {});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
browser.send('Fetch.continueRequest', { requestId }).catch(() => {});
|
|
167
|
+
};
|
|
168
|
+
browser.socket.on('message', onFetchMessage);
|
|
169
|
+
await browser.send('Fetch.enable', { patterns: [{ urlPattern: '*projects/projection*' }] });
|
|
170
|
+
await run(`(() => { window.__toasts = []; const el = document.getElementById('toast'); new MutationObserver(() => { const text = (el.textContent || '').trim(); if (text && window.__toasts[window.__toasts.length - 1] !== text) window.__toasts.push(text); }).observe(el, { childList: true, characterData: true, subtree: true }); })()`);
|
|
171
|
+
await click('.view.active [data-action="import"]');
|
|
172
|
+
await fill('#importCode', repo2); await fill('#importName', '第二个项目'); await fill('#importKnowledge', knowledge2);
|
|
173
|
+
await waitFor(() => run("!document.getElementById('confirmImport').disabled"), 'the second preflight is ready', 20000);
|
|
174
|
+
assert.equal((await api('/api/projects/aggregated')).projects.length, 1, 'the second import has not registered anything yet');
|
|
175
|
+
const beforeSecondImport = capture.requests.length;
|
|
176
|
+
fetchFaults.armed = true;
|
|
177
|
+
await click('#confirmImport');
|
|
178
|
+
await waitFor(() => fetchFaults.failed.length >= 1, 'the registry read that follows the import fails', 20000);
|
|
179
|
+
await waitFor(() => run("!document.getElementById('importDialog').open"), 'the successful import closes the dialog', 20000);
|
|
180
|
+
await waitFor(async () => (await run('window.__toasts || []')).some(text => text.includes('部分状态暂未刷新')), 'the failed registry read is reported', 20000);
|
|
181
|
+
const requests = capture.requests;
|
|
182
|
+
const importIndex = requests.findIndex((entry, index) => index >= beforeSecondImport && entry.method === 'POST' && entry.url.endsWith('/api/projects/import'));
|
|
183
|
+
assert.ok(importIndex >= 0, 'the second import request is recorded');
|
|
184
|
+
assert.equal(requests[importIndex].status, 201, 'the import itself succeeded');
|
|
185
|
+
const readsAfterImport = requests.slice(importIndex + 1).filter(entry => entry.method === 'GET' && entry.url.includes('/api/projects/projection'));
|
|
186
|
+
assert.ok(readsAfterImport.length >= 1, 'the import is followed by a registry read');
|
|
187
|
+
assert.ok(readsAfterImport.every(entry => entry.status === null), 'every registry read after the import really failed at the transport level');
|
|
188
|
+
assert.ok(fetchFaults.failed.length >= 1, 'the browser really lost the registry read');
|
|
189
|
+
// The import created exactly one project; the failed read created nothing.
|
|
190
|
+
const registered = (await api('/api/projects/aggregated')).projects;
|
|
191
|
+
assert.equal(registered.length, 2, 'the successful import registers exactly one more project');
|
|
192
|
+
assert.ok(registered.some(project => project.name === '第二个项目'), 'the second project is registered');
|
|
193
|
+
// The page does not present the failed read as fresh state.
|
|
194
|
+
assert.equal(await run("document.querySelectorAll('#projectTree [data-select-project]').length"), 1, 'the failed read leaves the project tree on the previous state');
|
|
195
|
+
assert.equal(await run("document.getElementById('projectTree').textContent.includes('第二个项目')"), false, 'the failed read is not shown as a refreshed list');
|
|
196
|
+
// A later read succeeds: the same import is not registered a second time.
|
|
197
|
+
fetchFaults.armed = false;
|
|
198
|
+
const laterRead = () => capture.find(entry => entry.method === 'GET' && entry.url.includes('/api/projects/projection') && entry.status === 200 && requests.indexOf(entry) > importIndex).length > 0;
|
|
199
|
+
const readDeadline = Date.now() + 20000;
|
|
200
|
+
while (!laterRead() && Date.now() < readDeadline) {
|
|
201
|
+
await run("document.dispatchEvent(new Event('visibilitychange'))");
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
203
|
+
}
|
|
204
|
+
assert.ok(laterRead(), 'the later registry read succeeds');
|
|
205
|
+
await waitFor(() => run("document.getElementById('projectTree').textContent.includes('第二个项目')"), 'the refreshed project tree', 20000);
|
|
206
|
+
assert.equal(await run("document.querySelectorAll('#projectTree [data-select-project]').length"), 2, 'the retry reads the same two projects and creates no third');
|
|
207
|
+
assert.equal((await api('/api/projects/aggregated')).projects.length, 2, 'the product registry still holds exactly two projects');
|
|
208
|
+
assert.ok(fs.existsSync(knowledge2), 'the second project keeps its knowledge directory');
|
|
209
|
+
await browser.send('Fetch.disable');
|
|
210
|
+
assert.equal(browser.exceptions.length, 0, 'no uncaught product JavaScript exceptions');
|
|
211
|
+
console.log('product-import-ui-test PASS (1920×1080; real UI → registry/Hook/directories; offline module wiring remains partial)');
|
|
212
|
+
} finally {
|
|
213
|
+
await browser?.close(); await server.cleanup();
|
|
214
|
+
offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve));
|
|
215
|
+
assert.equal(path.dirname(fixture), path.resolve(os.tmpdir())); assert.ok(path.basename(fixture).startsWith('pk-product-import-ui-'));
|
|
216
|
+
fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|