@sanqianx/project-knowledge 5.0.24 → 5.0.26
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 +68 -65
- 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,272 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// NAV-008 — 断网恢复。
|
|
3
|
+
//
|
|
4
|
+
// When the browser loses the network the shell must close the old stream, and
|
|
5
|
+
// when the network comes back it must restore the snapshot and open a new
|
|
6
|
+
// stream — exactly one subscription per stream, and no content that pretends
|
|
7
|
+
// to be live while it is not.
|
|
8
|
+
//
|
|
9
|
+
// The regression drives the real product UI in a real browser against an
|
|
10
|
+
// isolated instance (own port, own data dir, own knowledge dir, own Bridge
|
|
11
|
+
// journal, own browser profile, own vendored knowledge engine). The network
|
|
12
|
+
// outage is a real browser outage: CDP Network.emulateNetworkConditions puts
|
|
13
|
+
// the browser offline, so navigator.onLine really turns false, the browser
|
|
14
|
+
// really cannot reach the product, and the real 'offline' and 'online' events
|
|
15
|
+
// really fire.
|
|
16
|
+
//
|
|
17
|
+
// The evidence is taken from three independent places:
|
|
18
|
+
//
|
|
19
|
+
// * the page itself: every WebSocket the shell opened and every
|
|
20
|
+
// subscription message it sent, recorded before the page's own scripts
|
|
21
|
+
// run;
|
|
22
|
+
// * the product proxy: the reads the browser really made;
|
|
23
|
+
// * the engine proxy: the streams the product really opened on the engine.
|
|
24
|
+
//
|
|
25
|
+
// Screenshots are written to PK_TEST_EVIDENCE_DIR when the batch sets it, so
|
|
26
|
+
// this tracked file stays free of machine-specific paths.
|
|
27
|
+
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
const assert = require('node:assert/strict');
|
|
30
|
+
const { spawnSync } = require('node:child_process');
|
|
31
|
+
|
|
32
|
+
const { startHarness, makeRepo } = require('./helpers/parallel-nav-shell');
|
|
33
|
+
|
|
34
|
+
const EVIDENCE = process.env.PK_TEST_EVIDENCE_DIR ? path.resolve(process.env.PK_TEST_EVIDENCE_DIR) : null;
|
|
35
|
+
|
|
36
|
+
const SNAPSHOT = '/api/engine/snapshot';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Record every WebSocket the page opens, every subscription message it
|
|
40
|
+
* sends and every close the browser really performed. The hook is installed
|
|
41
|
+
* before the page's own scripts run, so the first stream of the page is
|
|
42
|
+
* recorded too.
|
|
43
|
+
*
|
|
44
|
+
* The close event is the evidence that the browser really closed the old
|
|
45
|
+
* stream: the shell drops its own bookkeeping as soon as it asks the socket
|
|
46
|
+
* to close, while the browser reaches the closed state only when the
|
|
47
|
+
* connection is really gone.
|
|
48
|
+
*/
|
|
49
|
+
const SOCKET_HOOK = `(() => {
|
|
50
|
+
window.__sockets = [];
|
|
51
|
+
window.__sent = [];
|
|
52
|
+
window.__closed = [];
|
|
53
|
+
const Native = window.WebSocket;
|
|
54
|
+
const Wrapped = function (...args) {
|
|
55
|
+
const ws = new Native(...args);
|
|
56
|
+
const generation = window.__sockets.length;
|
|
57
|
+
window.__sockets.push(ws);
|
|
58
|
+
ws.addEventListener('close', event => {
|
|
59
|
+
window.__closed.push({ generation, code: event.code, wasClean: event.wasClean, readyState: ws.readyState, at: Date.now() });
|
|
60
|
+
});
|
|
61
|
+
const send = ws.send.bind(ws);
|
|
62
|
+
ws.send = function (data) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(data);
|
|
65
|
+
window.__sent.push({ generation, type: parsed.type || '', id: parsed.id || '', route: parsed.route || '' });
|
|
66
|
+
} catch {}
|
|
67
|
+
return send(data);
|
|
68
|
+
};
|
|
69
|
+
return ws;
|
|
70
|
+
};
|
|
71
|
+
Wrapped.prototype = Native.prototype;
|
|
72
|
+
Object.setPrototypeOf(Wrapped, Native);
|
|
73
|
+
window.WebSocket = Wrapped;
|
|
74
|
+
})()`;
|
|
75
|
+
|
|
76
|
+
const socketStates = harness => harness.run('window.__sockets.map(socket => socket.readyState)');
|
|
77
|
+
const sentMessages = harness => harness.run('window.__sent || []');
|
|
78
|
+
const closeEvents = harness => harness.run('window.__closed || []');
|
|
79
|
+
const liveStats = harness => harness.run('window.KnowledgeLive.stats()');
|
|
80
|
+
const connectionLabel = harness => harness.run("document.getElementById('activityConnection').textContent");
|
|
81
|
+
const homeText = harness => harness.run("document.getElementById('homeContent').textContent");
|
|
82
|
+
|
|
83
|
+
/** The subscriptions that are still live on one socket generation. */
|
|
84
|
+
function activeSubscriptions(messages, generation) {
|
|
85
|
+
const active = new Map();
|
|
86
|
+
for (const message of messages) {
|
|
87
|
+
if (message.generation !== generation) continue;
|
|
88
|
+
if (message.type === 'subscribe') active.set(message.id, message.route);
|
|
89
|
+
else if (message.type === 'unsubscribe') active.delete(message.id);
|
|
90
|
+
}
|
|
91
|
+
return active;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function gitCommit(repo, fileName, content) {
|
|
95
|
+
const fs = require('node:fs');
|
|
96
|
+
fs.writeFileSync(path.join(repo, fileName), content);
|
|
97
|
+
const run = args => {
|
|
98
|
+
const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true });
|
|
99
|
+
assert.equal(result.status, 0, `git ${args.join(' ')} failed: ${result.stderr}`);
|
|
100
|
+
return String(result.stdout || '').trim();
|
|
101
|
+
};
|
|
102
|
+
run(['add', '.']);
|
|
103
|
+
run(['commit', '-qm', `second ${fileName}`]);
|
|
104
|
+
return run(['rev-parse', 'HEAD']);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
const harness = await startHarness({ tag: 'nav008', evidenceDir: EVIDENCE || undefined });
|
|
109
|
+
try {
|
|
110
|
+
const repo = makeRepo(harness.fixture, 'nav008-a');
|
|
111
|
+
const project = await harness.importProject(repo, 'NAV008 甲');
|
|
112
|
+
const shortFirst = repo.headCommit.slice(0, 8);
|
|
113
|
+
// Real engine state: the first run of the project.
|
|
114
|
+
harness.engine.store.addRun({ projectId: project.projectId, commitSha: repo.headCommit, trigger: 'manual' });
|
|
115
|
+
|
|
116
|
+
// The page is reloaded with the socket hook in place, so the very first
|
|
117
|
+
// stream of the page is recorded as well.
|
|
118
|
+
await harness.browser.send('Page.addScriptToEvaluateOnNewDocument', { source: SOCKET_HOOK });
|
|
119
|
+
await harness.browser.send('Page.reload', { ignoreCache: true });
|
|
120
|
+
await harness.waitFor(() => harness.run("document.readyState === 'complete'"), 'the reloaded page');
|
|
121
|
+
await harness.waitFor(
|
|
122
|
+
async () => (await homeText(harness)).includes(shortFirst),
|
|
123
|
+
'the reloaded page shows the run of the project',
|
|
124
|
+
20000,
|
|
125
|
+
);
|
|
126
|
+
await harness.waitFor(
|
|
127
|
+
async () => {
|
|
128
|
+
const stats = await liveStats(harness);
|
|
129
|
+
return stats.connections === 1 && stats.subscriptions >= 2;
|
|
130
|
+
},
|
|
131
|
+
'the live channel is connected',
|
|
132
|
+
20000,
|
|
133
|
+
);
|
|
134
|
+
await harness.waitFor(
|
|
135
|
+
async () => (await connectionLabel(harness)).includes('已连接实时更新'),
|
|
136
|
+
'the shell reports the live connection',
|
|
137
|
+
20000,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// The baseline of the healthy state.
|
|
141
|
+
const baselineStats = await liveStats(harness);
|
|
142
|
+
const baselineSockets = await socketStates(harness);
|
|
143
|
+
const baselineSent = await sentMessages(harness);
|
|
144
|
+
assert.equal(baselineSockets.length, 1, `the page opened one live socket: ${JSON.stringify(baselineSockets)}`);
|
|
145
|
+
assert.equal(baselineSockets[0], 1, 'the live socket is open');
|
|
146
|
+
assert.equal(baselineStats.subscriptions, 2, `the shell holds two subscriptions: ${JSON.stringify(baselineStats)}`);
|
|
147
|
+
const baselineActive = activeSubscriptions(baselineSent, 0);
|
|
148
|
+
assert.equal(baselineActive.size, 2, `the shell subscribed two channels: ${JSON.stringify([...baselineActive])}`);
|
|
149
|
+
assert.equal(new Set(baselineActive.values()).size, 2, `the two baseline subscriptions are for two different streams: ${JSON.stringify([...baselineActive])}`);
|
|
150
|
+
const baselineProgressReads = harness.engineProxy.requests.filter(entry => entry.path.startsWith('/api/progress')).length;
|
|
151
|
+
assert.ok(baselineProgressReads >= 1, `the engine really holds a progress stream: ${baselineProgressReads}`);
|
|
152
|
+
|
|
153
|
+
// The second commit of the fixture repository gives the second run a
|
|
154
|
+
// real commit of its own.
|
|
155
|
+
const secondSha = gitCommit(repo.path, 'SECOND.md', '# second\n');
|
|
156
|
+
const shortSecond = secondSha.slice(0, 8);
|
|
157
|
+
|
|
158
|
+
// ---- the network goes away ----
|
|
159
|
+
await harness.setOffline(true);
|
|
160
|
+
await harness.waitFor(() => harness.run('navigator.onLine === false'), 'the browser really goes offline');
|
|
161
|
+
await harness.waitFor(
|
|
162
|
+
async () => {
|
|
163
|
+
const stats = await liveStats(harness);
|
|
164
|
+
return stats.connections === 0 && stats.subscriptions === 0;
|
|
165
|
+
},
|
|
166
|
+
'the shell closes the old stream',
|
|
167
|
+
20000,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const offlineSockets = await socketStates(harness);
|
|
171
|
+
assert.equal(offlineSockets.length, 1, `the page still holds one socket object: ${JSON.stringify(offlineSockets)}`);
|
|
172
|
+
// The browser closes the stream on its own schedule: the shell drops its
|
|
173
|
+
// bookkeeping as soon as it asks the socket to close, so the closed
|
|
174
|
+
// state has to be waited for instead of being sampled right away.
|
|
175
|
+
const closedOld = await harness.waitFor(
|
|
176
|
+
async () => {
|
|
177
|
+
const events = await closeEvents(harness);
|
|
178
|
+
const close = events.find(event => event.generation === 0);
|
|
179
|
+
if (!close) return null;
|
|
180
|
+
const states = await socketStates(harness);
|
|
181
|
+
return { close, states };
|
|
182
|
+
},
|
|
183
|
+
'the browser really closes the old stream',
|
|
184
|
+
20000,
|
|
185
|
+
);
|
|
186
|
+
assert.equal(closedOld.states[0], 3, `the old stream is really closed by the browser: ${JSON.stringify(closedOld)}`);
|
|
187
|
+
assert.equal(closedOld.close.readyState, 3, `the browser closed the old stream in the closed state: ${JSON.stringify(closedOld.close)}`);
|
|
188
|
+
|
|
189
|
+
// NAV-008 expected 3: the content does not pretend to be live. The shell
|
|
190
|
+
// says the connection is broken, and it does not show the update list as
|
|
191
|
+
// if it were live information.
|
|
192
|
+
const offlineLabel = await connectionLabel(harness);
|
|
193
|
+
assert.equal(offlineLabel.includes('已连接实时更新'), false, `the shell does not claim a live connection while it is offline: ${offlineLabel}`);
|
|
194
|
+
assert.ok(offlineLabel.includes('连接中断'), `the shell reports the broken connection: ${offlineLabel}`);
|
|
195
|
+
const offlineHome = await homeText(harness);
|
|
196
|
+
assert.ok(offlineHome.includes('知识引擎暂时不可用'), `the home does not show stale updates as live information: ${offlineHome}`);
|
|
197
|
+
await harness.browser.screenshot(path.join(harness.evidence, 'nav008-offline.png'));
|
|
198
|
+
|
|
199
|
+
// No read of the snapshot reached the product while the browser was
|
|
200
|
+
// offline: the outage is real, not a silent retry.
|
|
201
|
+
const requestsBeforeOffline = harness.productProxy.requests.length;
|
|
202
|
+
await harness.wait(1200);
|
|
203
|
+
const requestsDuringOffline = harness.productProxy.requests.slice(requestsBeforeOffline);
|
|
204
|
+
const snapshotDuringOffline = requestsDuringOffline.filter(entry => entry.path.startsWith(SNAPSHOT));
|
|
205
|
+
assert.deepEqual(snapshotDuringOffline, [], `no snapshot read reached the product while the browser was offline: ${JSON.stringify(snapshotDuringOffline)}`);
|
|
206
|
+
|
|
207
|
+
// Real engine state that the browser cannot know while it is offline.
|
|
208
|
+
harness.engine.store.addRun({ projectId: project.projectId, commitSha: secondSha, trigger: 'manual' });
|
|
209
|
+
|
|
210
|
+
// ---- the network comes back ----
|
|
211
|
+
await harness.setOffline(false);
|
|
212
|
+
await harness.waitFor(() => harness.run('navigator.onLine === true'), 'the browser really comes online');
|
|
213
|
+
await harness.waitFor(
|
|
214
|
+
async () => {
|
|
215
|
+
const stats = await liveStats(harness);
|
|
216
|
+
return stats.connections === 1 && stats.subscriptions === 2;
|
|
217
|
+
},
|
|
218
|
+
'the shell opens the new stream',
|
|
219
|
+
20000,
|
|
220
|
+
);
|
|
221
|
+
await harness.waitFor(
|
|
222
|
+
async () => (await connectionLabel(harness)).includes('已连接实时更新'),
|
|
223
|
+
'the shell reports the new live connection',
|
|
224
|
+
20000,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const recoveredStats = await liveStats(harness);
|
|
228
|
+
const recoveredSockets = await socketStates(harness);
|
|
229
|
+
const recoveredSent = await sentMessages(harness);
|
|
230
|
+
const recoveredClosed = await closeEvents(harness);
|
|
231
|
+
assert.equal(recoveredSockets.length, 2, `the page opened a second socket after the recovery: ${JSON.stringify(recoveredSockets)}`);
|
|
232
|
+
assert.equal(recoveredSockets[0], 3, `the old socket stays closed: ${JSON.stringify(recoveredSockets)} ${JSON.stringify(recoveredClosed)}`);
|
|
233
|
+
assert.equal(recoveredSockets[1], 1, 'the new socket is open');
|
|
234
|
+
assert.equal(recoveredClosed.filter(event => event.generation === 1).length, 0, `the new stream was not closed by the outage: ${JSON.stringify(recoveredClosed)}`);
|
|
235
|
+
|
|
236
|
+
// NAV-008 expected 2: no duplicated subscription. The subscriptions of
|
|
237
|
+
// the new socket are exactly the two streams of the shell, each of them
|
|
238
|
+
// once, and the count of the shell matches them.
|
|
239
|
+
const recoveredActive = activeSubscriptions(recoveredSent, 1);
|
|
240
|
+
assert.equal(recoveredActive.size, 2, `the new socket carries exactly two subscriptions: ${JSON.stringify([...recoveredActive])}`);
|
|
241
|
+
assert.equal(new Set(recoveredActive.values()).size, 2, `the two recovered subscriptions are for two different streams: ${JSON.stringify([...recoveredActive])}`);
|
|
242
|
+
assert.equal(recoveredStats.subscriptions, recoveredActive.size, `the shell counts the live subscriptions exactly: ${JSON.stringify(recoveredStats)} ${JSON.stringify([...recoveredActive])}`);
|
|
243
|
+
assert.equal(recoveredStats.connections, 1, 'the shell holds exactly one live connection');
|
|
244
|
+
const progressReads = harness.engineProxy.requests.filter(entry => entry.path.startsWith('/api/progress')).length;
|
|
245
|
+
assert.equal(progressReads, baselineProgressReads + 1, `the engine holds one progress stream more, not more: ${progressReads}`);
|
|
246
|
+
|
|
247
|
+
// NAV-008 expected 1: the snapshot is restored. The run that appeared
|
|
248
|
+
// while the browser was offline is on the page now, and the page was
|
|
249
|
+
// updated by a real read of the snapshot after the recovery.
|
|
250
|
+
const snapshotReads = harness.productProxy.requests.filter(entry => entry.path.startsWith(SNAPSHOT));
|
|
251
|
+
assert.ok(snapshotReads.length >= 1, 'the shell read the snapshot after the recovery');
|
|
252
|
+
const lastSnapshot = snapshotReads[snapshotReads.length - 1];
|
|
253
|
+
assert.equal(lastSnapshot.status, 200, `the last snapshot read really succeeded: ${JSON.stringify(lastSnapshot)}`);
|
|
254
|
+
await harness.waitFor(
|
|
255
|
+
async () => (await homeText(harness)).includes(shortSecond),
|
|
256
|
+
'the run that appeared while the browser was offline is on the page now',
|
|
257
|
+
20000,
|
|
258
|
+
);
|
|
259
|
+
const recoveredHome = await homeText(harness);
|
|
260
|
+
assert.ok(recoveredHome.includes(shortFirst), 'the run of before the outage is still on the page');
|
|
261
|
+
assert.equal(recoveredHome.includes('知识引擎暂时不可用'), false, 'the page shows the real records again');
|
|
262
|
+
await harness.browser.screenshot(path.join(harness.evidence, 'nav008-recovered.png'));
|
|
263
|
+
|
|
264
|
+
assert.equal(harness.browser.exceptions.length, 0, `no uncaught product JavaScript exceptions: ${JSON.stringify(harness.browser.exceptions)}`);
|
|
265
|
+
|
|
266
|
+
console.log('parallel-nav-offline-test PASS (the old stream is closed, the snapshot and a new stream are restored, no duplicated subscription, nothing pretends to be live)');
|
|
267
|
+
} finally {
|
|
268
|
+
await harness.cleanup();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// NAV-006 — 可操作性。
|
|
3
|
+
//
|
|
4
|
+
// The shell must be operable by a real pointer and by a real keyboard: a
|
|
5
|
+
// native pointer click must reach the control, the focus must be visible, and
|
|
6
|
+
// nothing transparent may intercept the click.
|
|
7
|
+
//
|
|
8
|
+
// The regression drives the real product UI in a real browser against an
|
|
9
|
+
// isolated instance (own port, own data dir, own knowledge dir, own Bridge
|
|
10
|
+
// journal, own browser profile). Every pointer action is a CDP
|
|
11
|
+
// Input.dispatchMouseEvent on the real coordinates of the real control, and
|
|
12
|
+
// the hit test (document.elementFromPoint) must resolve to that control, so
|
|
13
|
+
// no transparent layer can stand in for a click. Every key action is a CDP
|
|
14
|
+
// Input.dispatchKeyEvent. element.click() is never used.
|
|
15
|
+
//
|
|
16
|
+
// The proof that the pointer really was the browser's is not the harness but
|
|
17
|
+
// the page: the shell's own listener records the event, and the recorded
|
|
18
|
+
// event carries isTrusted === true, which a synthesized DOM click can never
|
|
19
|
+
// produce.
|
|
20
|
+
//
|
|
21
|
+
// Screenshots are written to PK_TEST_EVIDENCE_DIR when the batch sets it, so
|
|
22
|
+
// this tracked file stays free of machine-specific paths.
|
|
23
|
+
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
const assert = require('node:assert/strict');
|
|
26
|
+
|
|
27
|
+
const { startHarness, makeRepo } = require('./helpers/parallel-nav-shell');
|
|
28
|
+
|
|
29
|
+
const EVIDENCE = process.env.PK_TEST_EVIDENCE_DIR ? path.resolve(process.env.PK_TEST_EVIDENCE_DIR) : null;
|
|
30
|
+
|
|
31
|
+
/** Record every click the page really receives, trusted or not. */
|
|
32
|
+
async function installClickRecorder(harness) {
|
|
33
|
+
await harness.run(`(() => {
|
|
34
|
+
window.__navClicks = [];
|
|
35
|
+
if (!window.__navClickHooked) {
|
|
36
|
+
window.__navClickHooked = true;
|
|
37
|
+
document.addEventListener('click', event => {
|
|
38
|
+
const target = event.target;
|
|
39
|
+
const nav = target.closest ? target.closest('[data-view]') : null;
|
|
40
|
+
window.__navClicks.push({
|
|
41
|
+
trusted: event.isTrusted === true,
|
|
42
|
+
tag: target.tagName,
|
|
43
|
+
id: target.id || '',
|
|
44
|
+
view: nav ? nav.getAttribute('data-view') : '',
|
|
45
|
+
label: (target.textContent || '').trim().slice(0, 30),
|
|
46
|
+
});
|
|
47
|
+
}, true);
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
})()`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const clicks = harness => harness.run('window.__navClicks || []');
|
|
54
|
+
|
|
55
|
+
/** The focus ring the browser draws for the keyboard-focused element. */
|
|
56
|
+
const focusState = harness => harness.run(`(() => {
|
|
57
|
+
const el = document.activeElement;
|
|
58
|
+
if (!el || el === document.body) return { none: true };
|
|
59
|
+
const style = getComputedStyle(el);
|
|
60
|
+
const rect = el.getBoundingClientRect();
|
|
61
|
+
return {
|
|
62
|
+
none: false,
|
|
63
|
+
tag: el.tagName,
|
|
64
|
+
id: el.id || '',
|
|
65
|
+
view: el.getAttribute ? (el.getAttribute('data-view') || '') : '',
|
|
66
|
+
label: (el.textContent || '').trim().slice(0, 24),
|
|
67
|
+
outlineStyle: style.outlineStyle,
|
|
68
|
+
outlineWidth: style.outlineWidth,
|
|
69
|
+
outlineColor: style.outlineColor,
|
|
70
|
+
boxShadow: style.boxShadow,
|
|
71
|
+
width: rect.width,
|
|
72
|
+
height: rect.height,
|
|
73
|
+
visible: rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none',
|
|
74
|
+
inViewport: rect.left >= 0 && rect.top >= 0 && rect.right <= innerWidth + 1 && rect.bottom <= innerHeight + 1,
|
|
75
|
+
};
|
|
76
|
+
})()`);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Every element that could swallow a pointer across the whole viewport.
|
|
80
|
+
* A transparent full-screen layer with pointer-events enabled is exactly the
|
|
81
|
+
* defect this case looks for.
|
|
82
|
+
*/
|
|
83
|
+
const overlayScan = harness => harness.run(`(() => {
|
|
84
|
+
const full = [];
|
|
85
|
+
const covers = (rect) => rect.width >= innerWidth - 2 && rect.height >= innerHeight - 2;
|
|
86
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
87
|
+
const style = getComputedStyle(el);
|
|
88
|
+
if (style.display === 'none' || style.visibility === 'hidden') continue;
|
|
89
|
+
if (style.pointerEvents === 'none') continue;
|
|
90
|
+
// Only a positioned element can float over the page; the layout
|
|
91
|
+
// containers are static and hold the controls themselves.
|
|
92
|
+
if (!['fixed', 'absolute', 'sticky'].includes(style.position)) continue;
|
|
93
|
+
const rect = el.getBoundingClientRect();
|
|
94
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
95
|
+
if (!covers(rect)) continue;
|
|
96
|
+
const opacity = Number(style.opacity);
|
|
97
|
+
full.push({
|
|
98
|
+
tag: el.tagName,
|
|
99
|
+
id: el.id || '',
|
|
100
|
+
className: typeof el.className === 'string' ? el.className : '',
|
|
101
|
+
opacity: Number.isFinite(opacity) ? opacity : 1,
|
|
102
|
+
zIndex: style.zIndex,
|
|
103
|
+
background: style.backgroundColor,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return full;
|
|
107
|
+
})()`);
|
|
108
|
+
|
|
109
|
+
async function main() {
|
|
110
|
+
const harness = await startHarness({ tag: 'nav006', evidenceDir: EVIDENCE || undefined });
|
|
111
|
+
try {
|
|
112
|
+
const repo = makeRepo(harness.fixture, 'nav006-a');
|
|
113
|
+
await harness.importProject(repo, 'NAV006 甲');
|
|
114
|
+
await harness.requestRegistryRefresh();
|
|
115
|
+
await harness.waitFor(
|
|
116
|
+
() => harness.run("document.getElementById('homeContent').textContent.includes('NAV006 甲')"),
|
|
117
|
+
'the imported project is listed',
|
|
118
|
+
20000,
|
|
119
|
+
);
|
|
120
|
+
await installClickRecorder(harness);
|
|
121
|
+
|
|
122
|
+
// NAV-006 expected 4: the pointer is the browser's own, not a
|
|
123
|
+
// synthesized DOM event. The page records what it really received.
|
|
124
|
+
await harness.nativeClick('nav [data-view="search"]');
|
|
125
|
+
const afterNavClick = await clicks(harness);
|
|
126
|
+
assert.ok(afterNavClick.length >= 1, 'the page really received the click');
|
|
127
|
+
const navClick = afterNavClick[afterNavClick.length - 1];
|
|
128
|
+
assert.equal(navClick.trusted, true, `the click the page received is a trusted browser event: ${JSON.stringify(navClick)}`);
|
|
129
|
+
assert.equal(navClick.view, 'search', `the click reached the navigation control: ${JSON.stringify(navClick)}`);
|
|
130
|
+
await harness.waitFor(
|
|
131
|
+
() => harness.run("document.getElementById('view-search').classList.contains('active')"),
|
|
132
|
+
'the search view is open',
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// NAV-006 expected 4 (continued): the hit test of the very same control
|
|
136
|
+
// resolves to the control, so nothing transparent stands in front.
|
|
137
|
+
const hit = await harness.nativeClick('nav [data-view="memory"]');
|
|
138
|
+
assert.equal(hit.hitIsTarget, true, `the control is the hit target: ${JSON.stringify(hit)}`);
|
|
139
|
+
await harness.waitFor(
|
|
140
|
+
() => harness.run("document.getElementById('view-memory').classList.contains('active')"),
|
|
141
|
+
'the memory view is open',
|
|
142
|
+
);
|
|
143
|
+
const memoryClick = (await clicks(harness)).pop();
|
|
144
|
+
assert.equal(memoryClick.trusted, true, 'the memory navigation click is trusted too');
|
|
145
|
+
assert.equal(memoryClick.view, 'memory', 'the memory navigation click reached the control');
|
|
146
|
+
|
|
147
|
+
// NAV-006 expected 1: no transparent layer intercepts the page.
|
|
148
|
+
const overlays = await overlayScan(harness);
|
|
149
|
+
assert.deepEqual(overlays, [], `no transparent full-viewport layer covers the page: ${JSON.stringify(overlays)}`);
|
|
150
|
+
|
|
151
|
+
// NAV-006 expected 2: the input focus is visible.
|
|
152
|
+
//
|
|
153
|
+
// The keyboard walk starts at the top of the document. Every Tab stop is
|
|
154
|
+
// recorded, and every stop must be visible on the screen and must carry
|
|
155
|
+
// a visible focus ring.
|
|
156
|
+
await harness.run("document.querySelector('nav [data-view=\"home\"]').focus()");
|
|
157
|
+
const stops = [];
|
|
158
|
+
for (let index = 0; index < 22; index += 1) {
|
|
159
|
+
await harness.pressTab();
|
|
160
|
+
const state = await focusState(harness);
|
|
161
|
+
if (!state.none) stops.push(state);
|
|
162
|
+
}
|
|
163
|
+
assert.ok(stops.length >= 8, `the keyboard walk reaches the main controls: ${stops.length} stops`);
|
|
164
|
+
const distinct = new Set(stops.map(stop => `${stop.tag}#${stop.id}.${stop.view}:${stop.label}`));
|
|
165
|
+
assert.ok(distinct.size >= 6, `the keyboard focus really moves between controls: ${[...distinct].join(' | ')}`);
|
|
166
|
+
for (const stop of stops) {
|
|
167
|
+
assert.equal(stop.visible, true, `the focused control is visible: ${JSON.stringify(stop)}`);
|
|
168
|
+
assert.equal(stop.inViewport, true, `the focused control is on the screen: ${JSON.stringify(stop)}`);
|
|
169
|
+
const hasRing = stop.outlineStyle !== 'none' && parseFloat(stop.outlineWidth) > 0;
|
|
170
|
+
assert.equal(hasRing, true, `the focused control shows a focus ring: ${JSON.stringify(stop)}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// NAV-006 expected 2 (continued): the focus ring is really painted, not
|
|
174
|
+
// only declared. The computed style of the focused element is compared
|
|
175
|
+
// with the computed style of the same element when it is not focused.
|
|
176
|
+
await harness.run("document.querySelector('nav [data-view=\"home\"]').focus()");
|
|
177
|
+
const focused = await harness.run(`(() => {
|
|
178
|
+
const el = document.querySelector('nav [data-view="home"]');
|
|
179
|
+
const style = getComputedStyle(el);
|
|
180
|
+
return { outlineStyle: style.outlineStyle, outlineWidth: style.outlineWidth, outlineColor: style.outlineColor };
|
|
181
|
+
})()`);
|
|
182
|
+
assert.equal(focused.outlineStyle, 'solid', `the focused control has a solid focus ring: ${JSON.stringify(focused)}`);
|
|
183
|
+
assert.ok(parseFloat(focused.outlineWidth) >= 2, `the focus ring is thick enough to see: ${JSON.stringify(focused)}`);
|
|
184
|
+
await harness.browser.screenshot(path.join(harness.evidence, 'nav006-focus-ring.png'));
|
|
185
|
+
|
|
186
|
+
// NAV-006 expected 1 and 2: the keyboard alone can open a page and
|
|
187
|
+
// reach the fields of a form.
|
|
188
|
+
//
|
|
189
|
+
// The navigation control is reached with Tab only, then Enter opens the
|
|
190
|
+
// view. The click the keyboard produces is trusted as well, because the
|
|
191
|
+
// browser itself synthesises it from the key press.
|
|
192
|
+
await harness.run("document.body.focus(); if (document.activeElement) document.activeElement.blur();");
|
|
193
|
+
let reachedSearch = false;
|
|
194
|
+
for (let index = 0; index < 12 && !reachedSearch; index += 1) {
|
|
195
|
+
await harness.pressTab();
|
|
196
|
+
const state = await focusState(harness);
|
|
197
|
+
if (state.view === 'search') reachedSearch = true;
|
|
198
|
+
}
|
|
199
|
+
assert.equal(reachedSearch, true, 'the keyboard reaches the search control');
|
|
200
|
+
const before = (await clicks(harness)).length;
|
|
201
|
+
await harness.pressEnter();
|
|
202
|
+
await harness.waitFor(
|
|
203
|
+
() => harness.run("document.getElementById('view-search').classList.contains('active')"),
|
|
204
|
+
'the search view is open by keyboard alone',
|
|
205
|
+
);
|
|
206
|
+
const after = await clicks(harness);
|
|
207
|
+
assert.ok(after.length > before, 'the key press really produced a click');
|
|
208
|
+
const keyboardClick = after[after.length - 1];
|
|
209
|
+
assert.equal(keyboardClick.trusted, true, `the keyboard click is a trusted browser event: ${JSON.stringify(keyboardClick)}`);
|
|
210
|
+
assert.equal(keyboardClick.view, 'search', `the keyboard click reached the search control: ${JSON.stringify(keyboardClick)}`);
|
|
211
|
+
|
|
212
|
+
// The form of the import dialog is reachable and operable by keyboard.
|
|
213
|
+
await harness.nativeClick('nav [data-view="projects"]');
|
|
214
|
+
await harness.waitFor(
|
|
215
|
+
() => harness.run("document.getElementById('view-projects').classList.contains('active')"),
|
|
216
|
+
'the projects view is open',
|
|
217
|
+
);
|
|
218
|
+
await harness.nativeClick('#createDomain');
|
|
219
|
+
await harness.waitFor(
|
|
220
|
+
() => harness.run("document.getElementById('domainDialog').open === true"),
|
|
221
|
+
'the domain dialog is open',
|
|
222
|
+
);
|
|
223
|
+
await harness.run("document.querySelector('#domainDialog input[name=\"name\"]').focus()");
|
|
224
|
+
const fieldStop = await focusState(harness);
|
|
225
|
+
assert.equal(fieldStop.visible, true, `the form field is visible: ${JSON.stringify(fieldStop)}`);
|
|
226
|
+
assert.equal(fieldStop.inViewport, true, `the form field is on the screen: ${JSON.stringify(fieldStop)}`);
|
|
227
|
+
assert.equal(fieldStop.outlineStyle !== 'none' && parseFloat(fieldStop.outlineWidth) > 0, true, `the form field shows a focus ring: ${JSON.stringify(fieldStop)}`);
|
|
228
|
+
await harness.browser.screenshot(path.join(harness.evidence, 'nav006-form-focus.png'));
|
|
229
|
+
|
|
230
|
+
// The keyboard can type into the focused field, and the value really
|
|
231
|
+
// lands in the product's own input.
|
|
232
|
+
await harness.pressKey('a', { code: 'KeyA', keyCode: 65, text: 'a' });
|
|
233
|
+
await harness.pressKey('b', { code: 'KeyB', keyCode: 66, text: 'b' });
|
|
234
|
+
const typed = await harness.run("document.querySelector('#domainDialog input[name=\"name\"]').value");
|
|
235
|
+
assert.equal(typed, 'ab', `the keyboard typed into the focused field: ${JSON.stringify(typed)}`);
|
|
236
|
+
|
|
237
|
+
// Escape closes the dialog again, so the keyboard owns the whole flow.
|
|
238
|
+
await harness.pressKey('Escape', { code: 'Escape', keyCode: 27 });
|
|
239
|
+
await harness.waitFor(
|
|
240
|
+
() => harness.run("document.getElementById('domainDialog').open === false"),
|
|
241
|
+
'the dialog closes by keyboard',
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Every click the page received during the case was a browser click.
|
|
245
|
+
const recorded = await clicks(harness);
|
|
246
|
+
assert.ok(recorded.length >= 5, `the page received the clicks of the case: ${recorded.length}`);
|
|
247
|
+
assert.equal(recorded.every(entry => entry.trusted === true), true, `no synthesized click reached the page: ${JSON.stringify(recorded)}`);
|
|
248
|
+
|
|
249
|
+
assert.equal(harness.browser.exceptions.length, 0, `no uncaught product JavaScript exceptions: ${JSON.stringify(harness.browser.exceptions)}`);
|
|
250
|
+
|
|
251
|
+
console.log('parallel-nav-operability-test PASS (native pointer and keyboard reach the controls, the focus is visible, nothing transparent intercepts)');
|
|
252
|
+
} finally {
|
|
253
|
+
await harness.cleanup();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
main().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
|