@sanqianx/project-knowledge 5.0.24 → 5.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -20
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +1361 -1353
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/README.md +349 -349
- package/_modules/knowledge-engine/package.json +1 -1
- package/_modules/knowledge-engine/src/coordinator.js +48 -3
- package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -140
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +238 -238
- package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -26
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +193 -193
- package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +394 -394
- package/_modules/knowledge-engine/src/runtime/contracts.js +282 -282
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +389 -389
- package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -228
- package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -62
- package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +505 -505
- package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +400 -400
- package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +171 -171
- package/_modules/knowledge-engine/src/runtime/scanner.js +298 -298
- package/_modules/knowledge-engine/ui/app.js +23 -2
- package/_site/README.md +30 -30
- package/_site/_test/agent-capture-mcp-ui-test.js +68 -68
- package/_site/_test/agent-capture-ui-test.js +101 -101
- package/_site/_test/ai-profile-resolver-test.js +137 -137
- package/_site/_test/ai-profile-test.js +75 -75
- package/_site/_test/ai-vendor-presets-test.js +91 -91
- package/_site/_test/automatic-context-test.js +70 -70
- package/_site/_test/automation-queue-test.js +14 -14
- package/_site/_test/automation-ui-test.js +40 -40
- package/_site/_test/background-task-registry-test.js +43 -43
- package/_site/_test/baseline-schema-test.js +91 -91
- package/_site/_test/bin-cli-test.js +230 -230
- package/_site/_test/bridge-adapter-test.js +95 -95
- package/_site/_test/bridge-consumer-service-test.js +310 -310
- package/_site/_test/candidate-bin-content-test.js +35 -35
- package/_site/_test/candidate-product-assets-test.js +13 -13
- package/_site/_test/candidate-release-contract-test.js +14 -14
- package/_site/_test/capture-hook-dedup-test.js +158 -158
- package/_site/_test/cdp-browser-failure-test.js +103 -103
- package/_site/_test/chat-claudecodeui-match-test.js +92 -92
- package/_site/_test/claude-executable-discovery-test.js +90 -90
- package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
- package/_site/_test/claude-workbench-test.js +174 -174
- package/_site/_test/cli-instance-management-test.js +62 -62
- package/_site/_test/client-capture-restart-e2e-test.js +189 -189
- package/_site/_test/codex-conversation-projection-test.js +60 -60
- package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
- package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
- package/_site/_test/codex-notify-fanout-test.js +141 -134
- package/_site/_test/codex-notify-upgrade-test.js +253 -253
- package/_site/_test/commit-boundary-freeze-test.js +147 -147
- package/_site/_test/commit-conversation-binding-test.js +99 -99
- package/_site/_test/commit-evidence-test.js +110 -110
- package/_site/_test/configuration-generation-test.js +22 -22
- package/_site/_test/context-pack-test.js +200 -200
- package/_site/_test/conversation-api-test.js +119 -119
- package/_site/_test/conversation-origin-test.js +15 -15
- package/_site/_test/conversation-store-test.js +138 -138
- package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
- package/_site/_test/desktop-browser-compat-test.js +28 -28
- package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
- package/_site/_test/embedding-config-test.js +32 -32
- package/_site/_test/explicit-commit-processor-test.js +37 -37
- package/_site/_test/fixtures/fake-browser-exit.js +10 -10
- package/_site/_test/fixtures/fake-browser-hang.js +10 -10
- package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
- package/_site/_test/fixtures/make-git-repos.js +92 -92
- package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
- package/_site/_test/folder-picker-output-test.js +19 -19
- package/_site/_test/fresh-hook-reimport-test.js +53 -53
- package/_site/_test/fresh-product-data-test.js +56 -56
- package/_site/_test/full-integration-e2e-test.js +167 -167
- package/_site/_test/git-path-compatibility-test.js +80 -80
- package/_site/_test/git-validation-test.js +81 -81
- package/_site/_test/glm-ui-refresh-review-test.js +313 -404
- package/_site/_test/harness-isolation-test.js +138 -0
- package/_site/_test/helpers/cdp-browser.js +383 -0
- package/_site/_test/helpers/cdp-network-capture.js +66 -0
- package/_site/_test/helpers/find-chrome.js +34 -0
- package/_site/_test/helpers/free-port.js +8 -0
- package/_site/_test/helpers/logging-ui-fixture.js +131 -0
- package/_site/_test/helpers/module-fixture.js +282 -0
- package/_site/_test/helpers/parallel-engine-advanced-engine-child.js +45 -0
- package/_site/_test/helpers/parallel-engine-advanced-fault-writer.js +171 -0
- package/_site/_test/helpers/parallel-engine-advanced-harness.js +245 -0
- package/_site/_test/helpers/parallel-engine-advanced-standin-analyzer.js +80 -0
- package/_site/_test/helpers/parallel-engine-advanced-ui-host.js +177 -0
- package/_site/_test/helpers/parallel-engine-final-harness.js +314 -0
- package/_site/_test/helpers/parallel-engine-final-standin-analyzer.js +159 -0
- package/_site/_test/helpers/parallel-knowledge-fixture.js +255 -0
- package/_site/_test/helpers/parallel-logging-acceptance-fixture.js +111 -0
- package/_site/_test/helpers/parallel-logging-acceptance-proxy.js +106 -0
- package/_site/_test/helpers/parallel-logging-fix-r2-proxy.js +205 -0
- package/_site/_test/helpers/parallel-logging-integration-5025-env.js +432 -0
- package/_site/_test/helpers/parallel-memory-analyzer.js +81 -0
- package/_site/_test/helpers/parallel-memory-event-bridge.js +128 -0
- package/_site/_test/helpers/parallel-memory-shell.js +470 -0
- package/_site/_test/helpers/parallel-nav-isolation-env.js +389 -0
- package/_site/_test/helpers/parallel-nav-module-ui.js +186 -0
- package/_site/_test/helpers/parallel-nav-proxy.js +148 -0
- package/_site/_test/helpers/parallel-nav-shell.js +361 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-guard.js +206 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-isolate.js +257 -0
- package/_site/_test/helpers/parallel-retrieval-followup-corpus.js +293 -0
- package/_site/_test/helpers/parallel-retrieval-followup-next-session.js +70 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-shell.js +737 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-wb-host.js +82 -0
- package/_site/_test/helpers/parallel-retrieval-followup-shell.js +279 -0
- package/_site/_test/helpers/parallel-workbench-fixture.js +371 -0
- package/_site/_test/helpers/parallel-workbench-host.js +80 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-fixture.js +408 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-harness.js +593 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-host.js +70 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-launcher.js +240 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-runtime-harness.js +630 -0
- package/_site/_test/helpers/parallel-workbench-source-map.js +196 -0
- package/_site/_test/helpers/spawn-server.js +106 -0
- package/_site/_test/helpers/version-visibility-fixture.js +251 -0
- package/_site/_test/hook-runtime-endpoint-test.js +88 -88
- package/_site/_test/hook-status-repair-api-test.js +152 -152
- package/_site/_test/hook-trigger-test.js +85 -85
- package/_site/_test/hook-worktree-test.js +42 -42
- package/_site/_test/import-preflight-api-test.js +234 -234
- package/_site/_test/index-builder-test.js +41 -41
- package/_site/_test/index-error-display-contract-test.js +10 -10
- package/_site/_test/index-writer-concurrency-test.js +135 -135
- package/_site/_test/integration-adapters-test.js +262 -262
- package/_site/_test/integration-auto-repair-test.js +113 -113
- package/_site/_test/integration-capture-setup-test.js +118 -118
- package/_site/_test/integration-intent-test.js +81 -81
- package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
- package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
- package/_site/_test/integration-runtime-binding-test.js +473 -473
- package/_site/_test/integration-status-scan-test.js +29 -29
- package/_site/_test/integration-surface-coverage-test.js +164 -164
- package/_site/_test/kb-framework-test.js +57 -57
- package/_site/_test/kbpath-follow-test.js +63 -63
- package/_site/_test/knowledge-db-test.js +144 -144
- package/_site/_test/knowledge-engine-client-test.js +120 -109
- package/_site/_test/knowledge-language-control-test.js +170 -170
- package/_site/_test/knowledge-maintenance-test.js +40 -40
- package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
- package/_site/_test/knowledge-query-test.js +54 -54
- package/_site/_test/knowledge-retrieval-service-test.js +77 -77
- package/_site/_test/knowledge-scopes-test.js +37 -37
- package/_site/_test/knowledge-storage-location-test.js +28 -28
- package/_site/_test/knowledge-storage-startup-test.js +52 -52
- package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
- package/_site/_test/launcher-readiness-test.js +80 -80
- package/_site/_test/legacy-claude-session-alias-test.js +106 -106
- package/_site/_test/legacy-routes-removed-test.js +47 -47
- package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
- package/_site/_test/live-gateway-test.js +48 -48
- package/_site/_test/local-marketplace-test.js +16 -16
- package/_site/_test/log-redaction-test.js +58 -58
- package/_site/_test/logging-api-test.js +89 -89
- package/_site/_test/logging-sse-no-gap-test.js +88 -88
- package/_site/_test/logging-ui-test.js +107 -107
- package/_site/_test/markdown-delta-overlay-test.js +85 -85
- package/_site/_test/markdown-maintenance-api-test.js +75 -75
- package/_site/_test/markdown-maintenance-test.js +11 -11
- package/_site/_test/mcp-server-test.js +149 -149
- package/_site/_test/model-context-windows-test.js +89 -89
- package/_site/_test/module-agent-start-proxy-test.js +65 -65
- package/_site/_test/module-artifact-integrity-test.js +27 -27
- package/_site/_test/module-boundary-test.js +30 -30
- package/_site/_test/module-bridge-eventbridge-test.js +202 -202
- package/_site/_test/module-dynamic-port-test.js +118 -118
- package/_site/_test/module-index-proxy-test.js +33 -33
- package/_site/_test/module-model-configuration-test.js +44 -44
- package/_site/_test/module-process-lifecycle-test.js +35 -35
- package/_site/_test/module-stream-proxy-test.js +34 -34
- package/_site/_test/module-terminal-restart-test.js +63 -63
- package/_site/_test/module-wiring-unbind-race-test.js +59 -59
- package/_site/_test/native-context-integrations-test.js +86 -86
- package/_site/_test/non-release-ci-test.js +34 -34
- package/_site/_test/offline-boundary-isolation-test.js +32 -32
- package/_site/_test/orphaned-claim-recovery-test.js +80 -80
- package/_site/_test/package-boundary-test.js +43 -43
- package/_site/_test/package-startup-test.js +56 -56
- package/_site/_test/packaged-ui-smoke-test.js +88 -88
- package/_site/_test/parallel-ci-r9-posix-socket-budget-test.js +156 -0
- package/_site/_test/parallel-engine-advanced-eng005-test.js +136 -0
- package/_site/_test/parallel-engine-advanced-eng006-test.js +171 -0
- package/_site/_test/parallel-engine-advanced-eng007-reconcile-test.js +231 -0
- package/_site/_test/parallel-engine-advanced-eng009-test.js +208 -0
- package/_site/_test/parallel-engine-advanced-eng010-test.js +213 -0
- package/_site/_test/parallel-engine-advanced-eng011-ui-probe.js +459 -0
- package/_site/_test/parallel-engine-final-eng002-test.js +255 -0
- package/_site/_test/parallel-engine-final-eng007-test.js +269 -0
- package/_site/_test/parallel-engine-final-eng011-test.js +197 -0
- package/_site/_test/parallel-engine-final-eng012-test.js +354 -0
- package/_site/_test/parallel-knowledge-client-error-code-test.js +184 -0
- package/_site/_test/parallel-knowledge-engine-test.js +353 -0
- package/_site/_test/parallel-knowledge-retrieval-test.js +292 -0
- package/_site/_test/parallel-knowledge-vector-test.js +168 -0
- package/_site/_test/parallel-linux-ci-r7-log007-day-test.js +110 -0
- package/_site/_test/parallel-logging-acceptance-log003-test.js +224 -0
- package/_site/_test/parallel-logging-acceptance-log004-test.js +231 -0
- package/_site/_test/parallel-logging-acceptance-log005-test.js +208 -0
- package/_site/_test/parallel-logging-acceptance-log006-test.js +214 -0
- package/_site/_test/parallel-logging-acceptance-log007-test.js +223 -0
- package/_site/_test/parallel-logging-acceptance-log008-test.js +180 -0
- package/_site/_test/parallel-logging-fix-r2-crash-probe.js +271 -0
- package/_site/_test/parallel-logging-fix-r2-gap-test.js +294 -0
- package/_site/_test/parallel-logging-fix-r2-normalhook-test.js +330 -0
- package/_site/_test/parallel-logging-integration-5025-portability-test.js +554 -0
- package/_site/_test/parallel-memory-candidate-test.js +301 -0
- package/_site/_test/parallel-memory-manual-test.js +197 -0
- package/_site/_test/parallel-memory-scope-test.js +228 -0
- package/_site/_test/parallel-memory-source-test.js +227 -0
- package/_site/_test/parallel-memory-sync-test.js +238 -0
- package/_site/_test/parallel-memory-view-r8-reload-gate-test.js +123 -0
- package/_site/_test/parallel-nav-failure-test.js +119 -0
- package/_site/_test/parallel-nav-isolation-test.js +308 -0
- package/_site/_test/parallel-nav-isolation-vector-path-test.js +90 -0
- package/_site/_test/parallel-nav-offline-test.js +272 -0
- package/_site/_test/parallel-nav-operability-test.js +257 -0
- package/_site/_test/parallel-nav-switch-test.js +247 -0
- package/_site/_test/parallel-nav-theme-test.js +130 -0
- package/_site/_test/parallel-nav-viewport-test.js +310 -0
- package/_site/_test/parallel-qa-engine-endpoint-guard-test.js +273 -0
- package/_site/_test/parallel-retrieval-followup-citation-test.js +219 -0
- package/_site/_test/parallel-retrieval-followup-corpus-test.js +287 -0
- package/_site/_test/parallel-workbench-independent-host.js +262 -0
- package/_site/_test/parallel-workbench-integration-5025-abort-probe.js +340 -0
- package/_site/_test/parallel-workbench-integration-5025-drafts-probe.js +588 -0
- package/_site/_test/parallel-workbench-integration-5025-env-check.js +317 -0
- package/_site/_test/parallel-workbench-integration-5025-model-probe.js +342 -0
- package/_site/_test/parallel-workbench-integration-5025-ordering-probe.js +326 -0
- package/_site/_test/parallel-workbench-model-probe.js +336 -0
- package/_site/_test/parallel-workbench-offline-test.js +169 -0
- package/_site/_test/parallel-workbench-provenance-trace.js +134 -0
- package/_site/_test/parallel-workbench-source-match.js +206 -0
- package/_site/_test/parallel-workbench-standalone-turn.js +349 -0
- package/_site/_test/path-consistency-test.js +151 -151
- package/_site/_test/pending-sweep-test.js +9 -9
- package/_site/_test/post-commit-automation-test.js +87 -87
- package/_site/_test/pr-consumer-contract-test.js +55 -55
- package/_site/_test/product-brand-contract-test.js +25 -25
- package/_site/_test/product-diagnostics-ui-test.js +93 -93
- package/_site/_test/product-empty-state-test.js +148 -0
- package/_site/_test/product-import-ui-test.js +219 -104
- package/_site/_test/product-navigation-test.js +152 -0
- package/_site/_test/product-progress-status-test.js +84 -84
- package/_site/_test/project-delete-recovery-test.js +64 -64
- package/_site/_test/project-domain-create-test.js +91 -0
- package/_site/_test/project-domain-scope-retrieval-test.js +214 -0
- package/_site/_test/project-git-init-confirm-test.js +115 -0
- package/_site/_test/project-goal-editor-test.js +144 -144
- package/_site/_test/project-import-duplicate-alias-test.js +134 -0
- package/_site/_test/project-import-unicode-restart-test.js +140 -0
- package/_site/_test/project-lifecycle-transaction-test.js +106 -106
- package/_site/_test/project-module-partial-recovery-test.js +222 -0
- package/_site/_test/project-remove-running-guard-test.js +79 -79
- package/_site/_test/project-store-test.js +90 -90
- package/_site/_test/project-unbind-late-read-ui-test.js +250 -0
- package/_site/_test/project-unbind-writer-guard-test.js +231 -0
- package/_site/_test/prompt-settings-test.js +115 -115
- package/_site/_test/protected-architecture-gate-test.js +122 -122
- package/_site/_test/refactor-characterization-test.js +36 -36
- package/_site/_test/registry-events-test.js +38 -38
- package/_site/_test/release-version-sync-test.js +115 -115
- package/_site/_test/requirement-binding-test.js +113 -113
- package/_site/_test/requirement-recorder-test.js +173 -173
- package/_site/_test/run-all-tests.js +173 -161
- package/_site/_test/runtime-endpoint-test.js +64 -64
- package/_site/_test/scanner-test.js +83 -83
- package/_site/_test/server-security-test.js +96 -96
- package/_site/_test/sessions-stream-test.js +93 -93
- package/_site/_test/shared-contracts-test.js +58 -58
- package/_site/_test/shared-scope-root-binding-test.js +74 -67
- package/_site/_test/simple-import-test.js +98 -98
- package/_site/_test/startup-analysis-disabled-test.js +16 -16
- package/_site/_test/storage-foundation-test.js +78 -78
- package/_site/_test/structured-logger-test.js +101 -101
- package/_site/_test/t07b-cln-005-coverage-test.js +212 -0
- package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
- package/_site/_test/team-knowledge-store-test.js +132 -132
- package/_site/_test/test-server-cleanup-test.js +35 -35
- package/_site/_test/test-server-module-isolation-test.js +37 -37
- package/_site/_test/tracking-start-test.js +144 -144
- package/_site/_test/vector-storage-root-test.js +75 -75
- package/_site/_test/version-visibility-browser-probe.js +249 -0
- package/_site/_test/version-visibility-test.js +92 -0
- package/_site/_test/workbench-context-receipts-test.js +22 -22
- package/_site/_test/workbench-permission-test.js +117 -117
- package/_site/_test/zcode-capture-integration-test.js +112 -112
- package/_site/lib/ai-profile-resolver.js +78 -78
- package/_site/lib/ai-vendor-presets.js +83 -83
- package/_site/lib/ai-workspace.js +104 -104
- package/_site/lib/atomic-file.js +204 -204
- package/_site/lib/automation-config.js +140 -140
- package/_site/lib/bridge-adapter.js +260 -260
- package/_site/lib/bridge-consumer-service.js +402 -402
- package/_site/lib/capture-hook-config.js +108 -108
- package/_site/lib/claude-cli-runner.js +1599 -1599
- package/_site/lib/cli-instances.js +43 -43
- package/_site/lib/codex-context-status.js +17 -17
- package/_site/lib/codex-notify-config.js +193 -193
- package/_site/lib/commit-conversation-binder.js +247 -247
- package/_site/lib/commit-processing-ledger.js +27 -27
- package/_site/lib/commit-prompt.js +202 -202
- package/_site/lib/commit-reconciler.js +444 -444
- package/_site/lib/context-pack-builder.js +299 -299
- package/_site/lib/contracts.js +291 -291
- package/_site/lib/conversation-exclusions.js +204 -204
- package/_site/lib/conversation-query-service.js +161 -161
- package/_site/lib/conversation-store.js +387 -387
- package/_site/lib/data-dir.js +13 -13
- package/_site/lib/embedding-service.js +85 -85
- package/_site/lib/engine-connection.js +93 -93
- package/_site/lib/engine-knowledge-client.js +76 -72
- package/_site/lib/evidence-bundle.js +228 -228
- package/_site/lib/folder-picker-output.js +24 -24
- package/_site/lib/git-path.js +14 -14
- package/_site/lib/git-runner.js +62 -62
- package/_site/lib/github-team-store.js +1175 -1175
- package/_site/lib/hook-manager.js +203 -203
- package/_site/lib/index-builder.js +157 -157
- package/_site/lib/index-service.js +173 -173
- package/_site/lib/integration-auto-repair.js +77 -77
- package/_site/lib/integration-installer.js +1240 -1240
- package/_site/lib/integration-intent.js +88 -88
- package/_site/lib/integration-status-scan.js +99 -99
- package/_site/lib/integration-status-worker.js +14 -14
- package/_site/lib/kb-framework.js +191 -191
- package/_site/lib/kb-validator.js +124 -124
- package/_site/lib/knowledge-db.js +363 -363
- package/_site/lib/knowledge-promotion.js +435 -435
- package/_site/lib/knowledge-retrieval-service.js +401 -401
- package/_site/lib/knowledge-schema.js +121 -121
- package/_site/lib/knowledge-tool-runtime.js +366 -366
- package/_site/lib/live-gateway.js +78 -78
- package/_site/lib/llm-client.js +173 -173
- package/_site/lib/markdown-knowledge-indexer.js +230 -230
- package/_site/lib/model-context-windows.js +82 -82
- package/_site/lib/module-bridge.js +874 -874
- package/_site/lib/module-port-resolver.js +57 -57
- package/_site/lib/post-commit-automation.js +141 -141
- package/_site/lib/product-data.js +35 -35
- package/_site/lib/project-lifecycle-service.js +554 -554
- package/_site/lib/project-registry-store.js +129 -129
- package/_site/lib/project-store.js +268 -268
- package/_site/lib/prompt-registry.js +148 -148
- package/_site/lib/public-version-info.js +37 -0
- package/_site/lib/registry-events.js +16 -16
- package/_site/lib/requirement-adapters.js +10 -10
- package/_site/lib/requirement-binder.js +112 -112
- package/_site/lib/requirement-recorder.js +278 -278
- package/_site/lib/runtime-control.js +89 -89
- package/_site/lib/runtime-endpoint.js +160 -160
- package/_site/lib/scanner.js +298 -298
- package/_site/lib/server-app.js +1672 -1664
- package/_site/lib/settings-store.js +153 -153
- package/_site/lib/storage-layout.js +166 -166
- package/_site/lib/structured-logger.js +574 -574
- package/_site/lib/zcode-context-integration.js +60 -60
- package/_site/scripts/codex-notify-fanout.cjs +95 -95
- package/_site/scripts/folder-picker.ps1 +156 -156
- package/_site/scripts/hook-trigger.js +158 -158
- package/_site/scripts/install-module-candidates.js +69 -69
- package/_site/scripts/pack-module-candidates.js +89 -89
- package/_site/scripts/sync-product-content.js +28 -28
- package/_site/scripts/sync-release-version.js +164 -164
- package/_site/scripts/vendor-modules.js +49 -49
- package/_site/scripts/verify-module-runtime.js +50 -50
- package/_site/scripts/verify-product-runtime.js +36 -36
- package/_site/scripts/verify-retrieval-gold.js +83 -83
- package/_site/server.js +15 -15
- package/_site/start.bat +26 -26
- package/_site/stop.bat +11 -11
- package/bin/project-knowledge-integrations.js +254 -254
- package/bin/project-knowledge-kb.js +67 -67
- package/bin/project-knowledge-mcp.js +201 -201
- package/bin/project-knowledge.js +269 -170
- package/claude-prompts.json +21 -21
- package/docs/README.zh-CN.md +207 -205
- package/docs/assets/architecture.svg +181 -181
- package/docs/assets/logo.svg +49 -49
- package/docs/fresh-v5-start.md +44 -44
- package/docs/leader-glm-workbench-playbook.md +122 -122
- package/docs/project-registry-schema.md +33 -33
- package/docs/release-5.0.16-acceptance.md +176 -176
- package/docs/release-5.0.17-acceptance.md +117 -117
- package/docs/release-5.0.19-acceptance.md +39 -39
- package/docs/team-knowledge-mode-a-plan.md +73 -73
- package/docs/testing-strategy.md +33 -33
- package/module-runtime-manifest.json +66 -63
- package/package.json +113 -110
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/templates/change.md +30 -30
- package/templates/project-readme.md +52 -52
- package/ui/index.html +3 -2
- package/ui/version-info.css +44 -0
- package/ui/version-info.js +24 -0
- package/vendor-manifest.json +55 -55
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// PRJ-011: changing the domain of a project updates the retrieval scope.
|
|
3
|
+
//
|
|
4
|
+
// The steps of the case are "change the domain of the project, then run a
|
|
5
|
+
// project / domain retrieval". The expectations are:
|
|
6
|
+
// 1. the scope follows the registration update;
|
|
7
|
+
// 2. the absolute knowledge path does not move.
|
|
8
|
+
//
|
|
9
|
+
// The instance is an isolated product server with its own data directory and
|
|
10
|
+
// its own knowledge root. The knowledge engine is the real vendored module;
|
|
11
|
+
// the three other modules are local fixtures that answer 503, so only the
|
|
12
|
+
// engine participates.
|
|
13
|
+
//
|
|
14
|
+
// Boundary: the vector index is not part of this fixture, so the retrieval
|
|
15
|
+
// answers from the current knowledge files only. The scope expansion and the
|
|
16
|
+
// registration update are the behaviour under test; the ranking channel is
|
|
17
|
+
// explicitly the lexical one, which the answer reports itself.
|
|
18
|
+
|
|
19
|
+
const assert = require('node:assert/strict');
|
|
20
|
+
const crypto = require('node:crypto');
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const { spawnSync } = require('node:child_process');
|
|
24
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
25
|
+
const { createModuleFixture, createUpstreamProxy } = require('./helpers/module-fixture');
|
|
26
|
+
const freePort = require('./helpers/free-port');
|
|
27
|
+
const { applyDriverIsolation } = require('./helpers/parallel-qa-engine-endpoint-isolate');
|
|
28
|
+
const { guardedCreateEngineServer } = require('./helpers/parallel-qa-engine-endpoint-guard');
|
|
29
|
+
|
|
30
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
31
|
+
|
|
32
|
+
function git(cwd, args) {
|
|
33
|
+
const result = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
34
|
+
assert.equal(result.status, 0, `git ${args.join(' ')} failed: ${result.stderr}`);
|
|
35
|
+
return String(result.stdout || '').trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeRepo(root, name) {
|
|
39
|
+
const repo = path.join(root, name);
|
|
40
|
+
fs.mkdirSync(repo, { recursive: true });
|
|
41
|
+
git(repo, ['init', '-q', '-b', 'main']);
|
|
42
|
+
git(repo, ['config', 'user.email', 'domain@local.invalid']);
|
|
43
|
+
git(repo, ['config', 'user.name', 'Domain Regression']);
|
|
44
|
+
fs.writeFileSync(path.join(repo, 'README.md'), `# ${name}\n`);
|
|
45
|
+
git(repo, ['add', '.']); git(repo, ['commit', '-qm', 'baseline']);
|
|
46
|
+
return repo;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function waitForHealth(base, timeoutMs = 20000) {
|
|
50
|
+
const deadline = Date.now() + timeoutMs;
|
|
51
|
+
while (Date.now() < deadline) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(2000) });
|
|
54
|
+
if (response.ok) return;
|
|
55
|
+
} catch {}
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
57
|
+
}
|
|
58
|
+
throw new Error('Timed out waiting for the isolated server on ' + base);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
// Driver isolation FIRST: the environment is rebuilt from a system whitelist
|
|
63
|
+
// with every product surface pointed at a per-run layout, before any product
|
|
64
|
+
// module is required.
|
|
65
|
+
const applied = applyDriverIsolation({ prefix: 'pk-qa-prj-domain' });
|
|
66
|
+
const fixture = fs.mkdtempSync(path.join(applied.layout.temp, 'pk-prj-domain-'));
|
|
67
|
+
const dataDir = path.join(fixture, 'data');
|
|
68
|
+
const knowledgeRoot = path.join(fixture, 'knowledge');
|
|
69
|
+
const repoA = makeRepo(fixture, 'repo-domain-a');
|
|
70
|
+
const repoB = makeRepo(fixture, 'repo-domain-b');
|
|
71
|
+
const knowledgeA = path.join(knowledgeRoot, '领域项目');
|
|
72
|
+
const knowledgeB = path.join(knowledgeRoot, '另一领域项目');
|
|
73
|
+
fs.mkdirSync(knowledgeA, { recursive: true }); fs.mkdirSync(knowledgeB, { recursive: true });
|
|
74
|
+
|
|
75
|
+
const engineToken = crypto.randomBytes(24).toString('hex');
|
|
76
|
+
// OWN fixtures first (offline by design — the lexical channel is the
|
|
77
|
+
// behaviour under test), then the engine constructed against them: the
|
|
78
|
+
// coordinator ignores env and would otherwise fall back to LIVE 8787/8790.
|
|
79
|
+
const terminal = createModuleFixture({ kind: 'terminal' });
|
|
80
|
+
const vectorHub = createModuleFixture({ kind: 'vectorHub' });
|
|
81
|
+
const eventBridge = createModuleFixture({ kind: 'eventBridge' });
|
|
82
|
+
for (const fixtureService of [terminal, vectorHub, eventBridge]) {
|
|
83
|
+
await fixtureService.start();
|
|
84
|
+
fixtureService.setOnline(false);
|
|
85
|
+
}
|
|
86
|
+
const engine = guardedCreateEngineServer({ dataDir: path.join(fixture, 'engine'), runtimeToken: engineToken, debug: true, consume: false, vectorUrl: vectorHub.url, bridgeUrl: eventBridge.url });
|
|
87
|
+
await engine.ready;
|
|
88
|
+
await new Promise(resolve => engine.server.listen(0, '127.0.0.1', resolve));
|
|
89
|
+
const engineUrl = `http://127.0.0.1:${engine.server.address().port}`;
|
|
90
|
+
const engineProxy = createUpstreamProxy({ target: engineUrl, token: engineToken, tag: 'engine' });
|
|
91
|
+
await engineProxy.start();
|
|
92
|
+
|
|
93
|
+
const port = await freePort();
|
|
94
|
+
const base = `http://127.0.0.1:${port}`;
|
|
95
|
+
const server = spawnServer({
|
|
96
|
+
root: ROOT,
|
|
97
|
+
port,
|
|
98
|
+
dataDir,
|
|
99
|
+
tag: 'project-domain',
|
|
100
|
+
analysisOwner: 'engine',
|
|
101
|
+
extraEnv: {
|
|
102
|
+
KB_MODULES_REGISTER: '1',
|
|
103
|
+
KB_MODULES_AUTOSTART: '0',
|
|
104
|
+
KB_KNOWLEDGE_ENGINE_URL: engineProxy.url,
|
|
105
|
+
KB_TERMINAL_URL: terminal.url,
|
|
106
|
+
KB_VECTORHUB_URL: vectorHub.url,
|
|
107
|
+
KB_EVENTBRIDGE_URL: eventBridge.url,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
const api = async (method, route, body) => {
|
|
111
|
+
const response = await fetch(base + route, {
|
|
112
|
+
method,
|
|
113
|
+
headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
|
|
114
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
115
|
+
signal: AbortSignal.timeout(20000),
|
|
116
|
+
});
|
|
117
|
+
return { status: response.status, body: await response.json() };
|
|
118
|
+
};
|
|
119
|
+
const engineApi = async (method, route, body) => {
|
|
120
|
+
const response = await fetch(engineUrl + route, {
|
|
121
|
+
method,
|
|
122
|
+
headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
|
|
123
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
124
|
+
signal: AbortSignal.timeout(20000),
|
|
125
|
+
});
|
|
126
|
+
return { status: response.status, body: await response.json() };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await waitForHealth(base);
|
|
131
|
+
assert.equal((await api('PATCH', '/api/settings', { knowledge: { rootPath: knowledgeRoot } })).status, 200);
|
|
132
|
+
|
|
133
|
+
// Two real domains. The product registers them as shared scopes with the
|
|
134
|
+
// engine, so a domain retrieval has something to expand to.
|
|
135
|
+
const first = await api('POST', '/api/domains', { name: '领域一' });
|
|
136
|
+
assert.equal(first.status, 201, JSON.stringify(first.body));
|
|
137
|
+
const domainA = first.body.domain.id;
|
|
138
|
+
const second = await api('POST', '/api/domains', { name: '领域二' });
|
|
139
|
+
assert.equal(second.status, 201, JSON.stringify(second.body));
|
|
140
|
+
const domainB = second.body.domain.id;
|
|
141
|
+
assert.ok(domainA && domainB && domainA !== domainB);
|
|
142
|
+
|
|
143
|
+
const scopesBefore = (await engineApi('GET', '/api/snapshot?summary=1&limit=1')).body.sharedScopes;
|
|
144
|
+
assert.ok(scopesBefore.some(scope => scope.kind === 'domain' && scope.domainId === domainA), 'the engine knows the first domain scope');
|
|
145
|
+
assert.ok(scopesBefore.some(scope => scope.kind === 'domain' && scope.domainId === domainB), 'the engine knows the second domain scope');
|
|
146
|
+
const scopePathsBefore = Object.fromEntries(scopesBefore.map(scope => [scope.id, scope.knowledgePath]));
|
|
147
|
+
|
|
148
|
+
const importedA = await api('POST', '/api/projects/import', { localPath: repoA, knowledgePath: knowledgeA, displayName: '领域项目', domainId: domainA });
|
|
149
|
+
assert.equal(importedA.status, 201, JSON.stringify(importedA.body));
|
|
150
|
+
const projectA = importedA.body.projectId;
|
|
151
|
+
assert.equal(importedA.body.project.config.domainId, domainA, 'the first project starts in the first domain');
|
|
152
|
+
const importedB = await api('POST', '/api/projects/import', { localPath: repoB, knowledgePath: knowledgeB, displayName: '另一领域项目', domainId: domainB });
|
|
153
|
+
assert.equal(importedB.status, 201, JSON.stringify(importedB.body));
|
|
154
|
+
const projectB = importedB.body.projectId;
|
|
155
|
+
assert.equal(importedB.body.project.config.domainId, domainB, 'the second project starts in the second domain');
|
|
156
|
+
|
|
157
|
+
// The knowledge file is written after the import: a new project's
|
|
158
|
+
// knowledge directory has to be empty when the project is registered.
|
|
159
|
+
const knowledgeFile = path.join(knowledgeA, 'note.md');
|
|
160
|
+
fs.writeFileSync(knowledgeFile, '# 领域笔记\n\n这是一条属于项目的知识记录。\n');
|
|
161
|
+
const knowledgeBytes = fs.readFileSync(knowledgeFile);
|
|
162
|
+
|
|
163
|
+
// A real domain retrieval through the product, answered by the real engine.
|
|
164
|
+
const before = await api('POST', '/api/knowledge/search', { projectId: projectA, query: '领域笔记', scope: 'domain' });
|
|
165
|
+
assert.equal(before.status, 200, JSON.stringify(before.body));
|
|
166
|
+
// The answer reports the scopes it scanned, one entry per scope.
|
|
167
|
+
const scopeIds = result => (result.health?.scopes || []).map(scope => scope.projectId);
|
|
168
|
+
assert.ok(scopeIds(before.body).includes(domainA), 'the retrieval runs in the domain of the project');
|
|
169
|
+
assert.equal(scopeIds(before.body).includes(domainB), false, 'the retrieval does not reach the other domain');
|
|
170
|
+
assert.equal(scopeIds(before.body).includes(projectB), false, 'the retrieval does not reach the project of the other domain');
|
|
171
|
+
assert.ok(before.body.results.length >= 1, 'the retrieval answers from the current knowledge files');
|
|
172
|
+
assert.ok(before.body.results.some(row => row.scope_project_id === projectA && String(row.snippet || '').includes('领域笔记')), 'the answer points at the knowledge file of the project');
|
|
173
|
+
assert.deepEqual(before.body.retrievalModes, ['lexical'], 'the answer reports the lexical channel it used');
|
|
174
|
+
|
|
175
|
+
// Change the domain of the first project.
|
|
176
|
+
const changed = await api('PATCH', `/api/projects/${encodeURIComponent(projectA)}`, { domainId: domainB });
|
|
177
|
+
assert.equal(changed.status, 200, JSON.stringify(changed.body));
|
|
178
|
+
assert.equal(changed.body.config.domainId, domainB, 'the product records the new domain');
|
|
179
|
+
|
|
180
|
+
// The registration in the real engine follows the change.
|
|
181
|
+
const registration = (await engineApi('GET', '/api/projects')).body.projects.find(project => project.projectId === projectA);
|
|
182
|
+
assert.ok(registration, 'the engine keeps the project registration');
|
|
183
|
+
assert.equal(registration.domainId, domainB, 'the engine registration carries the new domain');
|
|
184
|
+
|
|
185
|
+
// 1. The scope follows the registration update.
|
|
186
|
+
const after = await api('POST', '/api/knowledge/search', { projectId: projectA, query: '领域笔记', scope: 'domain' });
|
|
187
|
+
assert.equal(after.status, 200, JSON.stringify(after.body));
|
|
188
|
+
assert.ok(scopeIds(after.body).includes(domainB), 'the retrieval now runs in the new domain');
|
|
189
|
+
assert.equal(scopeIds(after.body).includes(domainA), false, 'the retrieval no longer reaches the old domain');
|
|
190
|
+
assert.ok(scopeIds(after.body).includes(projectB), 'the retrieval now reaches the project of the new domain');
|
|
191
|
+
assert.ok(after.body.results.some(row => row.scope_project_id === projectA && String(row.snippet || '').includes('领域笔记')), 'the project knowledge is still reachable');
|
|
192
|
+
|
|
193
|
+
// 2. The absolute knowledge path does not move.
|
|
194
|
+
const projected = (await api('GET', '/api/projects/projection')).body.projects.find(project => project.projectId === projectA);
|
|
195
|
+
assert.equal(path.resolve(projected.knowledgePath), path.resolve(knowledgeA), 'the knowledge path stays the same absolute path');
|
|
196
|
+
assert.deepEqual(fs.readFileSync(knowledgeFile), knowledgeBytes, 'the knowledge file bytes do not change');
|
|
197
|
+
assert.ok(fs.existsSync(path.join(knowledgeA, 'note.md')), 'the knowledge file stays where it is');
|
|
198
|
+
const scopesAfter = (await engineApi('GET', '/api/snapshot?summary=1&limit=1')).body.sharedScopes;
|
|
199
|
+
for (const scope of scopesAfter) {
|
|
200
|
+
assert.equal(scope.knowledgePath, scopePathsBefore[scope.id], `the shared scope path of ${scope.id} does not move`);
|
|
201
|
+
}
|
|
202
|
+
assert.ok(fs.existsSync(path.join(knowledgeRoot, '_shared')), 'the shared knowledge root stays where it is');
|
|
203
|
+
|
|
204
|
+
console.log('project-domain-scope-retrieval-test PASS (the domain retrieval scope follows the registration update; the absolute knowledge path does not move)');
|
|
205
|
+
} finally {
|
|
206
|
+
await server.cleanup();
|
|
207
|
+
await engine.close().catch(() => {});
|
|
208
|
+
await engineProxy.close();
|
|
209
|
+
for (const fixtureService of [terminal, vectorHub, eventBridge]) await fixtureService.close();
|
|
210
|
+
fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// PRJ-008: Git initialization needs an explicit user confirmation.
|
|
3
|
+
//
|
|
4
|
+
// The steps of the case are "select a directory that is not a Git repository,
|
|
5
|
+
// then request the import with and without the initialization confirmation".
|
|
6
|
+
// Two expectations are covered:
|
|
7
|
+
// 1. without an explicit confirmation no `git init` may happen;
|
|
8
|
+
// 2. the initialization only happens inside the fixture the user selected.
|
|
9
|
+
//
|
|
10
|
+
// The instance is an isolated product server with its own data directory and
|
|
11
|
+
// its own knowledge root. Nothing outside the fixture directory is touched.
|
|
12
|
+
|
|
13
|
+
const assert = require('node:assert/strict');
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { spawnSync } = require('node:child_process');
|
|
18
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
19
|
+
const freePort = require('./helpers/free-port');
|
|
20
|
+
|
|
21
|
+
const ROOT = path.resolve(__dirname, '../..');
|
|
22
|
+
|
|
23
|
+
function git(cwd, args) {
|
|
24
|
+
const result = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
25
|
+
return { status: result.status, stdout: String(result.stdout || '').trim(), stderr: String(result.stderr || '').trim() };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function waitForHealth(base, timeoutMs = 20000) {
|
|
29
|
+
const deadline = Date.now() + timeoutMs;
|
|
30
|
+
while (Date.now() < deadline) {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(2000) });
|
|
33
|
+
if (response.ok) return;
|
|
34
|
+
} catch {}
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
36
|
+
}
|
|
37
|
+
throw new Error('Timed out waiting for the isolated server on ' + base);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findGitDirectories(root, depth = 0) {
|
|
41
|
+
const found = [];
|
|
42
|
+
if (depth > 3) return found;
|
|
43
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
44
|
+
if (!entry.isDirectory()) continue;
|
|
45
|
+
const absolute = path.join(root, entry.name);
|
|
46
|
+
if (entry.name === '.git') { found.push(absolute); continue; }
|
|
47
|
+
found.push(...findGitDirectories(absolute, depth + 1));
|
|
48
|
+
}
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-prj-gitinit-'));
|
|
54
|
+
const dataDir = path.join(fixture, 'data');
|
|
55
|
+
const knowledgeRoot = path.join(fixture, 'knowledge');
|
|
56
|
+
const selected = path.join(fixture, 'selected-project');
|
|
57
|
+
const neighbour = path.join(fixture, 'neighbour-project');
|
|
58
|
+
fs.mkdirSync(selected); fs.mkdirSync(neighbour);
|
|
59
|
+
fs.writeFileSync(path.join(selected, 'README.md'), '# selected\n');
|
|
60
|
+
fs.writeFileSync(path.join(neighbour, 'README.md'), '# neighbour\n');
|
|
61
|
+
|
|
62
|
+
const port = await freePort();
|
|
63
|
+
const base = `http://127.0.0.1:${port}`;
|
|
64
|
+
const server = spawnServer({ root: ROOT, port, dataDir, tag: 'project-git-init' });
|
|
65
|
+
const api = async (method, route, body) => {
|
|
66
|
+
const response = await fetch(base + route, {
|
|
67
|
+
method,
|
|
68
|
+
headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
|
|
69
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
70
|
+
signal: AbortSignal.timeout(15000),
|
|
71
|
+
});
|
|
72
|
+
return { status: response.status, body: await response.json() };
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
await waitForHealth(base);
|
|
76
|
+
assert.equal((await api('PATCH', '/api/settings', { knowledge: { rootPath: knowledgeRoot } })).status, 200);
|
|
77
|
+
|
|
78
|
+
// The preflight reports that a Git initialization is planned. The
|
|
79
|
+
// directory itself stays untouched: the preflight never mutates state.
|
|
80
|
+
const preflight = await api('POST', '/api/projects/preflight-import', { localPath: selected, knowledgePath: path.join(knowledgeRoot, 'selected'), displayName: '选定目录' });
|
|
81
|
+
assert.equal(preflight.status, 200, JSON.stringify(preflight.body));
|
|
82
|
+
assert.equal(preflight.body.plannedGitInit, true, 'the preflight must report the planned Git initialization');
|
|
83
|
+
assert.equal(preflight.body.checks.git.status, 'non-git', 'the preflight must report the directory as a non-Git directory');
|
|
84
|
+
assert.equal(fs.existsSync(path.join(selected, '.git')), false, 'the preflight must not initialize the repository');
|
|
85
|
+
|
|
86
|
+
// Without the explicit confirmation the import is refused and no Git
|
|
87
|
+
// repository is created anywhere.
|
|
88
|
+
const refused = await api('POST', '/api/projects/import', { localPath: selected, knowledgePath: path.join(knowledgeRoot, 'selected'), displayName: '选定目录' });
|
|
89
|
+
assert.equal(refused.status, 400, JSON.stringify(refused.body));
|
|
90
|
+
assert.equal(refused.body.error.code, 'INVALID_ARGUMENT');
|
|
91
|
+
assert.match(refused.body.error.message, /初始化/, 'the refusal must name the missing Git initialization confirmation');
|
|
92
|
+
assert.equal(fs.existsSync(path.join(selected, '.git')), false, 'a refused import must not run git init');
|
|
93
|
+
assert.deepEqual(findGitDirectories(fixture), [], 'a refused import must not create a Git repository anywhere in the fixture');
|
|
94
|
+
assert.equal((await api('GET', '/api/projects/aggregated')).body.projects.length, 0, 'a refused import must not register a project');
|
|
95
|
+
|
|
96
|
+
// With the explicit confirmation the initialization happens inside the
|
|
97
|
+
// selected fixture only.
|
|
98
|
+
const imported = await api('POST', '/api/projects/import', { localPath: selected, knowledgePath: path.join(knowledgeRoot, 'selected'), displayName: '选定目录', confirmGitInit: true });
|
|
99
|
+
assert.equal(imported.status, 201, JSON.stringify(imported.body));
|
|
100
|
+
assert.equal(fs.existsSync(path.join(selected, '.git')), true, 'the confirmed import must initialize the selected directory');
|
|
101
|
+
assert.equal(fs.existsSync(path.join(neighbour, '.git')), false, 'the neighbouring directory must stay untouched');
|
|
102
|
+
assert.deepEqual(findGitDirectories(fixture), [path.join(selected, '.git')], 'exactly the selected fixture may hold a Git repository');
|
|
103
|
+
const inside = git(selected, ['rev-parse', '--is-inside-work-tree']);
|
|
104
|
+
assert.equal(inside.status, 0, inside.stderr);
|
|
105
|
+
assert.equal(inside.stdout, 'true');
|
|
106
|
+
assert.equal(path.resolve(imported.body.project.config.repoPath), path.resolve(selected), 'the registered workspace path is the selected fixture');
|
|
107
|
+
assert.equal((await api('GET', '/api/projects/aggregated')).body.projects.length, 1, 'the confirmed import registers exactly one project');
|
|
108
|
+
console.log('project-git-init-confirm-test PASS (no git init without confirmation; initialization confined to the selected fixture)');
|
|
109
|
+
} finally {
|
|
110
|
+
await server.cleanup();
|
|
111
|
+
fs.rmSync(fixture, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
// _site/_test/project-goal-editor-test.js
|
|
2
|
-
//
|
|
3
|
-
// T13: Project Goal editor.
|
|
4
|
-
// GET /api/projects/:id/goal -> read GOAL.md from project knowledge path
|
|
5
|
-
// PUT /api/projects/:id/goal -> atomic write GOAL.md
|
|
6
|
-
// Verifies:
|
|
7
|
-
// - missing GOAL.md returns exists=false;
|
|
8
|
-
// - PUT creates the file atomically;
|
|
9
|
-
// - subsequent GET returns the persisted content;
|
|
10
|
-
// - empty content allowed (no auto-template);
|
|
11
|
-
// - the file is scoped to the project's own knowledgePath (no
|
|
12
|
-
// cross-project writes);
|
|
13
|
-
// - 413 when content exceeds 256KB.
|
|
14
|
-
|
|
15
|
-
const assert = require('assert');
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const os = require('os');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const { spawnSync } = require('child_process');
|
|
20
|
-
const { spawnServer } = require('./helpers/spawn-server');
|
|
21
|
-
|
|
22
|
-
const ROOT = path.resolve(__dirname, '..', '..');
|
|
23
|
-
const sitePort = 8000 + (process.pid % 200);
|
|
24
|
-
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-' + process.pid + '-'));
|
|
25
|
-
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-repo-' + process.pid + '-'));
|
|
26
|
-
|
|
27
|
-
function git(args) {
|
|
28
|
-
const result = spawnSync('git', ['-C', repo, ...args], {
|
|
29
|
-
encoding: 'utf8', windowsHide: true,
|
|
30
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
31
|
-
});
|
|
32
|
-
if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed: ${result.status}`);
|
|
33
|
-
return String(result.stdout || '').trim();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function serverFetch(url, init, timeoutMs = 10000) {
|
|
37
|
-
const controller = new AbortController();
|
|
38
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
39
|
-
try {
|
|
40
|
-
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
41
|
-
const text = await response.text();
|
|
42
|
-
return { status: response.status, body: text ? JSON.parse(text) : {} };
|
|
43
|
-
} finally {
|
|
44
|
-
clearTimeout(timer);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
(async () => {
|
|
49
|
-
git(['init', '-q', '-b', 'main']);
|
|
50
|
-
git(['config', 'user.email', 'goal@example.local']);
|
|
51
|
-
git(['config', 'user.name', 'Goal']);
|
|
52
|
-
fs.writeFileSync(path.join(repo, 'README.md'), '# goal\n');
|
|
53
|
-
git(['add', 'README.md']);
|
|
54
|
-
git(['commit', '-q', '-m', 'baseline']);
|
|
55
|
-
const knowledgeRoot = path.join(dataDir, 'knowledge');
|
|
56
|
-
fs.mkdirSync(knowledgeRoot, { recursive: true });
|
|
57
|
-
const server = spawnServer({
|
|
58
|
-
root: ROOT, port: sitePort, dataDir, tag: 'goal',
|
|
59
|
-
extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1', KB_EMBEDDING_FAKE: '1', KB_SKIP_MIGRATION: '1' },
|
|
60
|
-
});
|
|
61
|
-
try {
|
|
62
|
-
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
63
|
-
try { const r = await serverFetch(`http://127.0.0.1:${sitePort}/api/health`); if (r.body.ok) break; } catch {}
|
|
64
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
65
|
-
}
|
|
66
|
-
await serverFetch(`http://127.0.0.1:${sitePort}/api/settings`, {
|
|
67
|
-
method: 'PATCH', headers: { 'content-type': 'application/json' },
|
|
68
|
-
body: JSON.stringify({ knowledge: { rootPath: knowledgeRoot } }),
|
|
69
|
-
});
|
|
70
|
-
await serverFetch(`http://127.0.0.1:${sitePort}/api/ai-profiles`, {
|
|
71
|
-
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
schema: 'ai-profiles/v1', defaultProfileId: 'fake',
|
|
74
|
-
profiles: [{ id: 'fake', name: 'Fake', enabled: true, vendor: 'anthropic', model: 'fake', apiKeyUpdate: { mode: 'replace', value: 'k' } }],
|
|
75
|
-
}),
|
|
76
|
-
});
|
|
77
|
-
const importR = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/import`, {
|
|
78
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
79
|
-
body: JSON.stringify({ localPath: repo, aiProfileId: 'fake' }),
|
|
80
|
-
});
|
|
81
|
-
assert.strictEqual(importR.status, 201, JSON.stringify(importR.body));
|
|
82
|
-
const projectId = importR.body.projectId;
|
|
83
|
-
const knowledgePath = importR.body.config.knowledgePath;
|
|
84
|
-
|
|
85
|
-
// Case 1: GET on missing GOAL.md returns exists=false
|
|
86
|
-
let r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
|
|
87
|
-
assert.strictEqual(r.status, 200);
|
|
88
|
-
assert.strictEqual(r.body.exists, false);
|
|
89
|
-
assert.strictEqual(r.body.content, '');
|
|
90
|
-
|
|
91
|
-
// Case 2: PUT creates the file atomically with the supplied content
|
|
92
|
-
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
93
|
-
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
94
|
-
body: JSON.stringify({ content: '# Project Goal\n\nBuild a knowledge base for v4.1.22 -> main recovery.\n' }),
|
|
95
|
-
});
|
|
96
|
-
assert.strictEqual(r.status, 200, JSON.stringify(r.body));
|
|
97
|
-
const goalPath = path.join(knowledgePath, 'GOAL.md');
|
|
98
|
-
assert(fs.existsSync(goalPath), 'GOAL.md must be created');
|
|
99
|
-
assert(fs.readFileSync(goalPath, 'utf8').includes('v4.1.22'), 'goal content must include the user-supplied text');
|
|
100
|
-
|
|
101
|
-
// Case 3: GET now returns the persisted content
|
|
102
|
-
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
|
|
103
|
-
assert.strictEqual(r.status, 200);
|
|
104
|
-
assert.strictEqual(r.body.exists, true);
|
|
105
|
-
assert(r.body.content.includes('v4.1.22'));
|
|
106
|
-
|
|
107
|
-
// Case 4: empty content is allowed (no auto-template)
|
|
108
|
-
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
109
|
-
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
110
|
-
body: JSON.stringify({ content: '' }),
|
|
111
|
-
});
|
|
112
|
-
assert.strictEqual(r.status, 200);
|
|
113
|
-
assert.strictEqual(fs.readFileSync(goalPath, 'utf8'), '', 'empty content must clear the file');
|
|
114
|
-
|
|
115
|
-
// Case 5: writes are scoped to the project's own knowledgePath
|
|
116
|
-
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
117
|
-
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
118
|
-
body: JSON.stringify({ content: 'real content\n' }),
|
|
119
|
-
});
|
|
120
|
-
assert.strictEqual(r.status, 200);
|
|
121
|
-
const otherFiles = fs.readdirSync(knowledgePath).filter(name => name !== 'GOAL.md');
|
|
122
|
-
assert.deepStrictEqual(otherFiles, [], 'only GOAL.md must be created in the project knowledge path');
|
|
123
|
-
|
|
124
|
-
// Case 6: 413 when content exceeds 256KB
|
|
125
|
-
const huge = 'x'.repeat(257 * 1024);
|
|
126
|
-
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
127
|
-
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
128
|
-
body: JSON.stringify({ content: huge }),
|
|
129
|
-
});
|
|
130
|
-
assert.strictEqual(r.status, 413);
|
|
131
|
-
|
|
132
|
-
// Case 7: GET on a non-existent project returns 404
|
|
133
|
-
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/does-not-exist/goal`, undefined);
|
|
134
|
-
assert.strictEqual(r.status, 404);
|
|
135
|
-
} finally {
|
|
136
|
-
server.child.kill();
|
|
137
|
-
await new Promise(resolve => server.child.once('exit', resolve));
|
|
138
|
-
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
139
|
-
fs.rmSync(repo, { recursive: true, force: true });
|
|
140
|
-
}
|
|
141
|
-
console.log('project-goal-editor-test PASS');
|
|
142
|
-
})().catch(error => {
|
|
143
|
-
console.error(error.stack || error.message);
|
|
144
|
-
process.exit(1);
|
|
1
|
+
// _site/_test/project-goal-editor-test.js
|
|
2
|
+
//
|
|
3
|
+
// T13: Project Goal editor.
|
|
4
|
+
// GET /api/projects/:id/goal -> read GOAL.md from project knowledge path
|
|
5
|
+
// PUT /api/projects/:id/goal -> atomic write GOAL.md
|
|
6
|
+
// Verifies:
|
|
7
|
+
// - missing GOAL.md returns exists=false;
|
|
8
|
+
// - PUT creates the file atomically;
|
|
9
|
+
// - subsequent GET returns the persisted content;
|
|
10
|
+
// - empty content allowed (no auto-template);
|
|
11
|
+
// - the file is scoped to the project's own knowledgePath (no
|
|
12
|
+
// cross-project writes);
|
|
13
|
+
// - 413 when content exceeds 256KB.
|
|
14
|
+
|
|
15
|
+
const assert = require('assert');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { spawnSync } = require('child_process');
|
|
20
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
23
|
+
const sitePort = 8000 + (process.pid % 200);
|
|
24
|
+
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-' + process.pid + '-'));
|
|
25
|
+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-goal-repo-' + process.pid + '-'));
|
|
26
|
+
|
|
27
|
+
function git(args) {
|
|
28
|
+
const result = spawnSync('git', ['-C', repo, ...args], {
|
|
29
|
+
encoding: 'utf8', windowsHide: true,
|
|
30
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
31
|
+
});
|
|
32
|
+
if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed: ${result.status}`);
|
|
33
|
+
return String(result.stdout || '').trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function serverFetch(url, init, timeoutMs = 10000) {
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
return { status: response.status, body: text ? JSON.parse(text) : {} };
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
(async () => {
|
|
49
|
+
git(['init', '-q', '-b', 'main']);
|
|
50
|
+
git(['config', 'user.email', 'goal@example.local']);
|
|
51
|
+
git(['config', 'user.name', 'Goal']);
|
|
52
|
+
fs.writeFileSync(path.join(repo, 'README.md'), '# goal\n');
|
|
53
|
+
git(['add', 'README.md']);
|
|
54
|
+
git(['commit', '-q', '-m', 'baseline']);
|
|
55
|
+
const knowledgeRoot = path.join(dataDir, 'knowledge');
|
|
56
|
+
fs.mkdirSync(knowledgeRoot, { recursive: true });
|
|
57
|
+
const server = spawnServer({
|
|
58
|
+
root: ROOT, port: sitePort, dataDir, tag: 'goal',
|
|
59
|
+
extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1', KB_EMBEDDING_FAKE: '1', KB_SKIP_MIGRATION: '1' },
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
63
|
+
try { const r = await serverFetch(`http://127.0.0.1:${sitePort}/api/health`); if (r.body.ok) break; } catch {}
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
65
|
+
}
|
|
66
|
+
await serverFetch(`http://127.0.0.1:${sitePort}/api/settings`, {
|
|
67
|
+
method: 'PATCH', headers: { 'content-type': 'application/json' },
|
|
68
|
+
body: JSON.stringify({ knowledge: { rootPath: knowledgeRoot } }),
|
|
69
|
+
});
|
|
70
|
+
await serverFetch(`http://127.0.0.1:${sitePort}/api/ai-profiles`, {
|
|
71
|
+
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
schema: 'ai-profiles/v1', defaultProfileId: 'fake',
|
|
74
|
+
profiles: [{ id: 'fake', name: 'Fake', enabled: true, vendor: 'anthropic', model: 'fake', apiKeyUpdate: { mode: 'replace', value: 'k' } }],
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
const importR = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/import`, {
|
|
78
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({ localPath: repo, aiProfileId: 'fake' }),
|
|
80
|
+
});
|
|
81
|
+
assert.strictEqual(importR.status, 201, JSON.stringify(importR.body));
|
|
82
|
+
const projectId = importR.body.projectId;
|
|
83
|
+
const knowledgePath = importR.body.config.knowledgePath;
|
|
84
|
+
|
|
85
|
+
// Case 1: GET on missing GOAL.md returns exists=false
|
|
86
|
+
let r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
|
|
87
|
+
assert.strictEqual(r.status, 200);
|
|
88
|
+
assert.strictEqual(r.body.exists, false);
|
|
89
|
+
assert.strictEqual(r.body.content, '');
|
|
90
|
+
|
|
91
|
+
// Case 2: PUT creates the file atomically with the supplied content
|
|
92
|
+
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
93
|
+
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
94
|
+
body: JSON.stringify({ content: '# Project Goal\n\nBuild a knowledge base for v4.1.22 -> main recovery.\n' }),
|
|
95
|
+
});
|
|
96
|
+
assert.strictEqual(r.status, 200, JSON.stringify(r.body));
|
|
97
|
+
const goalPath = path.join(knowledgePath, 'GOAL.md');
|
|
98
|
+
assert(fs.existsSync(goalPath), 'GOAL.md must be created');
|
|
99
|
+
assert(fs.readFileSync(goalPath, 'utf8').includes('v4.1.22'), 'goal content must include the user-supplied text');
|
|
100
|
+
|
|
101
|
+
// Case 3: GET now returns the persisted content
|
|
102
|
+
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, undefined);
|
|
103
|
+
assert.strictEqual(r.status, 200);
|
|
104
|
+
assert.strictEqual(r.body.exists, true);
|
|
105
|
+
assert(r.body.content.includes('v4.1.22'));
|
|
106
|
+
|
|
107
|
+
// Case 4: empty content is allowed (no auto-template)
|
|
108
|
+
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
109
|
+
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({ content: '' }),
|
|
111
|
+
});
|
|
112
|
+
assert.strictEqual(r.status, 200);
|
|
113
|
+
assert.strictEqual(fs.readFileSync(goalPath, 'utf8'), '', 'empty content must clear the file');
|
|
114
|
+
|
|
115
|
+
// Case 5: writes are scoped to the project's own knowledgePath
|
|
116
|
+
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
117
|
+
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
118
|
+
body: JSON.stringify({ content: 'real content\n' }),
|
|
119
|
+
});
|
|
120
|
+
assert.strictEqual(r.status, 200);
|
|
121
|
+
const otherFiles = fs.readdirSync(knowledgePath).filter(name => name !== 'GOAL.md');
|
|
122
|
+
assert.deepStrictEqual(otherFiles, [], 'only GOAL.md must be created in the project knowledge path');
|
|
123
|
+
|
|
124
|
+
// Case 6: 413 when content exceeds 256KB
|
|
125
|
+
const huge = 'x'.repeat(257 * 1024);
|
|
126
|
+
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/${projectId}/goal`, {
|
|
127
|
+
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
128
|
+
body: JSON.stringify({ content: huge }),
|
|
129
|
+
});
|
|
130
|
+
assert.strictEqual(r.status, 413);
|
|
131
|
+
|
|
132
|
+
// Case 7: GET on a non-existent project returns 404
|
|
133
|
+
r = await serverFetch(`http://127.0.0.1:${sitePort}/api/projects/does-not-exist/goal`, undefined);
|
|
134
|
+
assert.strictEqual(r.status, 404);
|
|
135
|
+
} finally {
|
|
136
|
+
server.child.kill();
|
|
137
|
+
await new Promise(resolve => server.child.once('exit', resolve));
|
|
138
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
139
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
console.log('project-goal-editor-test PASS');
|
|
142
|
+
})().catch(error => {
|
|
143
|
+
console.error(error.stack || error.message);
|
|
144
|
+
process.exit(1);
|
|
145
145
|
});
|