spexcode 0.6.1 → 0.6.5
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/README.md +99 -149
- package/bin/spex.mjs +4 -0
- package/node_modules/@spexcode/spec-cli/bin/spex.mjs +76 -0
- package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +33 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +307 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.js +1859 -0
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +111 -0
- package/node_modules/@spexcode/spec-cli/dist/client.js +476 -0
- package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
- package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +53 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +686 -0
- package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
- package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
- package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-queue.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-queue.js +179 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +547 -0
- package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
- package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
- package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
- package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +361 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +37 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +503 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +716 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +35 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
- package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
- package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
- package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
- package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +115 -22
- package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +325 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.js +3026 -0
- package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
- package/node_modules/@spexcode/spec-cli/dist/help.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/help.js +574 -0
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
- package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
- package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
- package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
- package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
- package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
- package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
- package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
- package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
- package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
- package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
- package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
- package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
- package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
- package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +7 -0
- package/{spec-cli/src/listen.ts → node_modules/@spexcode/spec-cli/dist/listen.js} +13 -17
- package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
- package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
- package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
- package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
- package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
- package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
- package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
- package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
- package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +4 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +208 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
- package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
- package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
- package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
- package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
- package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
- package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
- package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
- package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
- package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
- package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
- package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
- package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
- package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
- package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
- package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
- package/node_modules/@spexcode/spec-cli/dist/session-cursors.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/session-cursors.js +82 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
- package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
- package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
- package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
- package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +51 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +233 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +412 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +4239 -0
- package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
- package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
- package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
- package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
- package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
- package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
- package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
- package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
- package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
- package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
- package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
- package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
- package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
- package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
- package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +27 -7
- package/node_modules/@spexcode/spec-cli/package.json +47 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +9 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +22 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +15 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +10 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +13 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +12 -0
- package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
- package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
- package/node_modules/@spexcode/spec-core/dist/git.d.ts +166 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +2736 -0
- package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
- package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
- package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
- package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
- package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
- package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
- package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
- package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
- package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
- package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
- package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
- package/node_modules/@spexcode/spec-core/dist/layout.d.ts +183 -0
- package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
- package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
- package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
- package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
- package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
- package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
- package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
- package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
- package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
- package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
- package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
- package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
- package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
- package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
- package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
- package/node_modules/@spexcode/spec-core/package.json +28 -0
- package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
- package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
- package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
- package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
- package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
- package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
- package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
- package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
- package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
- package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
- package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
- package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
- package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
- package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
- package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
- package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
- package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
- package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
- package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
- package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
- package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
- package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
- package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
- package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
- package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
- package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
- package/node_modules/@spexcode/spec-eval/package.json +36 -0
- package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
- package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
- package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
- package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
- package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
- package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
- package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
- package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
- package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
- package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
- package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
- package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
- package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
- package/node_modules/@spexcode/spec-forge/package.json +32 -0
- package/package.json +33 -19
- package/spec-cli/bin/spex.mjs +0 -69
- package/spec-cli/src/anchors.ts +0 -706
- package/spec-cli/src/attach.ts +0 -43
- package/spec-cli/src/claude-headless.ts +0 -321
- package/spec-cli/src/cli.ts +0 -1115
- package/spec-cli/src/client.ts +0 -315
- package/spec-cli/src/cockpit.ts +0 -43
- package/spec-cli/src/codex-runtime-generations.ts +0 -646
- package/spec-cli/src/commit-surgery.ts +0 -68
- package/spec-cli/src/contract-filter.ts +0 -202
- package/spec-cli/src/delivery-queue.ts +0 -107
- package/spec-cli/src/doctor.ts +0 -516
- package/spec-cli/src/file-write.ts +0 -22
- package/spec-cli/src/gateway-auth.ts +0 -181
- package/spec-cli/src/gateway-hub.ts +0 -356
- package/spec-cli/src/gateway.ts +0 -368
- package/spec-cli/src/git.ts +0 -2180
- package/spec-cli/src/graph.ts +0 -221
- package/spec-cli/src/graphCache.ts +0 -641
- package/spec-cli/src/graphDelta.ts +0 -77
- package/spec-cli/src/graphStream.ts +0 -1019
- package/spec-cli/src/harness-select.ts +0 -77
- package/spec-cli/src/harness.ts +0 -2747
- package/spec-cli/src/headless-controller.ts +0 -63
- package/spec-cli/src/help.ts +0 -480
- package/spec-cli/src/host-resources.ts +0 -707
- package/spec-cli/src/host.ts +0 -611
- package/spec-cli/src/identity-presets.d.ts +0 -13
- package/spec-cli/src/identity-presets.js +0 -128
- package/spec-cli/src/index.ts +0 -730
- package/spec-cli/src/init.ts +0 -258
- package/spec-cli/src/issues-cli.ts +0 -276
- package/spec-cli/src/issues.ts +0 -237
- package/spec-cli/src/layout.ts +0 -557
- package/spec-cli/src/lint.ts +0 -458
- package/spec-cli/src/localIssues.ts +0 -538
- package/spec-cli/src/materialize.ts +0 -412
- package/spec-cli/src/mentions.ts +0 -81
- package/spec-cli/src/opencode-headless.ts +0 -205
- package/spec-cli/src/pi-headless.ts +0 -153
- package/spec-cli/src/plugin-harness.ts +0 -157
- package/spec-cli/src/process-identity.ts +0 -207
- package/spec-cli/src/project-identity.ts +0 -73
- package/spec-cli/src/project-store.ts +0 -17
- package/spec-cli/src/pty-bridge.ts +0 -396
- package/spec-cli/src/pty-helper.mjs +0 -96
- package/spec-cli/src/pty-native-helper.mjs +0 -22
- package/spec-cli/src/ranker.ts +0 -191
- package/spec-cli/src/reaper.ts +0 -125
- package/spec-cli/src/resilience.ts +0 -41
- package/spec-cli/src/reviewSnapshot.ts +0 -28
- package/spec-cli/src/reviews.ts +0 -405
- package/spec-cli/src/runtime-guard.ts +0 -35
- package/spec-cli/src/runtime-ownership.ts +0 -88
- package/spec-cli/src/search.bench.mjs +0 -80
- package/spec-cli/src/search.ts +0 -63
- package/spec-cli/src/session-cursors.ts +0 -91
- package/spec-cli/src/session-follow.ts +0 -182
- package/spec-cli/src/session-timeline.ts +0 -98
- package/spec-cli/src/sessions.ts +0 -3403
- package/spec-cli/src/sh.ts +0 -3
- package/spec-cli/src/slash-commands.ts +0 -318
- package/spec-cli/src/source-files.ts +0 -104
- package/spec-cli/src/specs.ts +0 -498
- package/spec-cli/src/supervise.ts +0 -245
- package/spec-cli/src/tree.ts +0 -118
- package/spec-cli/src/tsx-bin.ts +0 -18
- package/spec-cli/src/uninstall.ts +0 -171
- package/spec-cli/src/uploads.ts +0 -253
- package/spec-cli/src/worktree-sources.ts +0 -42
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
- package/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -28
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +0 -23
- package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -32
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -9
- package/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -15
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -18
- package/spec-dashboard/dist/assets/App-F9uaAVcH.js +0 -2
- package/spec-dashboard/dist/assets/Dashboard-Ba_jhxp1.js +0 -27
- package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
- package/spec-dashboard/dist/assets/EvalsPage-FixoOg_n.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-CuKLFhH3.js +0 -1
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/spec-dashboard/dist/assets/MobileApp-CHgEHORJ.js +0 -2
- package/spec-dashboard/dist/assets/Modal-CQgYymmr.js +0 -1
- package/spec-dashboard/dist/assets/PageScroll-hT7UTLvD.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-CtXxakF9.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
- package/spec-dashboard/dist/assets/SessionInterface-Bpie-9fs.js +0 -39
- package/spec-dashboard/dist/assets/SessionWindow-CixDi4PI.js +0 -1
- package/spec-dashboard/dist/assets/Settings-C2MsucfE.js +0 -1
- package/spec-dashboard/dist/assets/Thread-C6Go8HRh.js +0 -13
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
- package/spec-dashboard/dist/assets/data-B-RQmit6.js +0 -1
- package/spec-dashboard/dist/assets/index-CsI8DElI.css +0 -1
- package/spec-dashboard/dist/assets/index-DrVao0Ep.js +0 -41
- package/spec-dashboard/dist/assets/launch-BBH02b1v.js +0 -276
- package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
- package/spec-dashboard/dist/assets/terminalFont-C6oGNKrf.js +0 -1
- package/spec-dashboard/dist/index.html +0 -29
- package/spec-dashboard/src/reviewFilters.js +0 -322
- package/spec-dashboard/src/reviewQuery.js +0 -174
- package/spec-dashboard/src/session.js +0 -199
- package/spec-eval/src/cache.ts +0 -57
- package/spec-eval/src/cli.ts +0 -889
- package/spec-eval/src/evaltab.ts +0 -262
- package/spec-eval/src/filing.ts +0 -39
- package/spec-eval/src/freshness.ts +0 -557
- package/spec-eval/src/humanok.ts +0 -28
- package/spec-eval/src/scenariofresh.ts +0 -244
- package/spec-eval/src/scenarios.ts +0 -672
- package/spec-eval/src/sessioneval.ts +0 -2226
- package/spec-eval/src/sidecar.ts +0 -93
- package/spec-eval/src/timeline.ts +0 -54
- package/spec-forge/src/__fixtures__/github-forge.json +0 -63
- package/spec-forge/src/cache.ts +0 -66
- package/spec-forge/src/cli.ts +0 -99
- package/spec-forge/src/drivers/github.ts +0 -132
- package/spec-forge/src/drivers/gitlab.ts +0 -153
- package/spec-forge/src/drivers.ts +0 -73
- package/spec-forge/src/links.ts +0 -72
- package/spec-forge/src/needs-eval.ts +0 -29
- package/spec-forge/src/port.ts +0 -36
- package/spec-forge/src/resident.ts +0 -46
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { repoRoot, git, sourceIndexes, rowsFor, treeFilePaths, treeFileText, withEventLedgerBuild } from '@spexcode/spec-core';
|
|
4
|
+
import { loadSpecs, parseFrontmatter } from '@spexcode/spec-core';
|
|
5
|
+
import { readJsonConfig } from '@spexcode/spec-core';
|
|
6
|
+
import { extractors, extractorFor, extOf, parseCodeEntry, relationClaimsPath, resolveSelectors, windowEvents, anchorHitQueries } from '@spexcode/spec-core';
|
|
7
|
+
import { DEFAULT_TEST_GLOBS, sourcePolicyDescription, trackedSourceFiles } from './source-files.js';
|
|
8
|
+
export const SPEC_LINT_REPORT_PROJECTION = 'spex.spec-lint.report';
|
|
9
|
+
export const SPEC_LINT_REPORT_SCHEMA_VERSION = 1;
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
governedRoots: ['spec-dashboard/src', 'spec-cli/src'],
|
|
12
|
+
sourceIncludeGlobs: null,
|
|
13
|
+
sourceExcludeGlobs: [],
|
|
14
|
+
sourceExtensions: null,
|
|
15
|
+
testGlobs: DEFAULT_TEST_GLOBS,
|
|
16
|
+
maxOwners: 3,
|
|
17
|
+
scenarioTags: ['frontend-e2e', 'backend-api', 'cli', 'desktop', 'mobile'],
|
|
18
|
+
scopedCodeMiss: 'warn',
|
|
19
|
+
};
|
|
20
|
+
export function loadConfig(root, pendingSource) {
|
|
21
|
+
// Absent spexcode.json → tuned defaults; a MALFORMED one throws LOUD (readJsonConfig) rather than
|
|
22
|
+
// silently reverting the author's budgets to defaults and green-washing the very warnings they tuned.
|
|
23
|
+
let parsed;
|
|
24
|
+
if (pendingSource === undefined)
|
|
25
|
+
parsed = readJsonConfig(join(root, 'spexcode.json'));
|
|
26
|
+
else if (pendingSource === null)
|
|
27
|
+
parsed = {};
|
|
28
|
+
else {
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(pendingSource);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(`invalid JSON in candidate spexcode.json: ${e?.message ?? e}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const c = parsed?.lint ?? {};
|
|
37
|
+
const merged = { ...DEFAULT_CONFIG, ...c };
|
|
38
|
+
return normalizeConfig(merged);
|
|
39
|
+
}
|
|
40
|
+
// Compile every author-facing source selector into one include-minus-exclude/test policy. In particular,
|
|
41
|
+
// sourceExtensions is compatibility syntax only: it contributes include globs and never reaches discovery.
|
|
42
|
+
export function normalizeConfig(cfg) {
|
|
43
|
+
// a mistyped enum silently reverting to the default would green-wash (or over-warn) exactly the
|
|
44
|
+
// advisory the author meant to tune — same fail-loud rule as a malformed spexcode.json.
|
|
45
|
+
if (cfg.scopedCodeMiss !== 'warn' && cfg.scopedCodeMiss !== 'ignore')
|
|
46
|
+
throw new Error(`spexcode.json lint.scopedCodeMiss must be "warn" or "ignore", got ${JSON.stringify(cfg.scopedCodeMiss)}`);
|
|
47
|
+
const dedot = (xs) => xs.map((x) => x.replace(/^\.+/, ''));
|
|
48
|
+
const anyDepth = (xs) => xs.map((g) => (g.includes('/') ? g : `**/${g}`));
|
|
49
|
+
const extensions = cfg.sourceExtensions === null ? null : dedot(cfg.sourceExtensions);
|
|
50
|
+
const includes = cfg.sourceIncludeGlobs === null && extensions === null
|
|
51
|
+
? null
|
|
52
|
+
: [...new Set([...anyDepth(cfg.sourceIncludeGlobs ?? []), ...(extensions ?? []).map((ext) => `**/*.${ext}`)])];
|
|
53
|
+
return {
|
|
54
|
+
...cfg,
|
|
55
|
+
sourceIncludeGlobs: includes,
|
|
56
|
+
sourceExcludeGlobs: anyDepth(cfg.sourceExcludeGlobs),
|
|
57
|
+
sourceExtensions: extensions,
|
|
58
|
+
testGlobs: anyDepth(cfg.testGlobs),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function untrackedAdoptionFiles(root) {
|
|
62
|
+
if (git(['-C', root, 'rev-parse', '--is-inside-work-tree']).trim() !== 'true')
|
|
63
|
+
return [];
|
|
64
|
+
const status = git([
|
|
65
|
+
'-C', root,
|
|
66
|
+
'-c', 'core.quotePath=false',
|
|
67
|
+
'status', '--porcelain=v1', '-z', '--untracked-files=all',
|
|
68
|
+
'--', '.spec', 'spexcode.json',
|
|
69
|
+
]);
|
|
70
|
+
return status.split('\0')
|
|
71
|
+
.filter((entry) => entry.startsWith('?? '))
|
|
72
|
+
.map((entry) => entry.slice(3))
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
function pendingChangedPaths(root, tip) {
|
|
76
|
+
try {
|
|
77
|
+
git(['-C', root, 'rev-parse', `${tip}^{commit}`]);
|
|
78
|
+
// `-m` compares a merge with every parent. An `ours` merge can leave the result tree
|
|
79
|
+
// identical to its first parent while still making an unacknowledged side-branch commit
|
|
80
|
+
// reachable; first-parent-only paths would filter that debt out of the pending anchor window.
|
|
81
|
+
const fields = git(['-C', root, '-c', 'core.quotePath=false', 'diff-tree', '--no-commit-id', '--name-status', '-z', '-r', '-m', '-M', tip])
|
|
82
|
+
.split('\0').filter(Boolean);
|
|
83
|
+
const changed = [];
|
|
84
|
+
for (let i = 0; i < fields.length;) {
|
|
85
|
+
const status = fields[i++];
|
|
86
|
+
if (/^[RC]/.test(status)) {
|
|
87
|
+
changed.push(fields[i++], fields[i++]);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
changed.push(fields[i++]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return [...new Set(changed.filter(Boolean))];
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Cheap pending classification for the reference hook. It reads only the candidate tree and claims; it
|
|
100
|
+
// never constructs either history index. A normal lint call deliberately does not use this
|
|
101
|
+
// shortcut so its full findings/oracle contract remains unchanged.
|
|
102
|
+
export async function pendingTouchesGoverned(root, tip) {
|
|
103
|
+
// A merge can introduce reachable side-branch debt without changing the result tree. The first-parent
|
|
104
|
+
// diff is insufficient for a scope proof, so all multi-parent candidates stay on the full lint path.
|
|
105
|
+
const parentCount = git(['-C', root, 'rev-list', '--parents', '-n1', tip]).trim().split(/\s+/).length - 1;
|
|
106
|
+
if (parentCount > 1)
|
|
107
|
+
return true;
|
|
108
|
+
const changed = pendingChangedPaths(root, tip);
|
|
109
|
+
if (!changed.length)
|
|
110
|
+
return true;
|
|
111
|
+
// `governedRoots` is source discovery policy, not the set of actual code claims: a spec may deliberately
|
|
112
|
+
// govern a path outside those roots. Read only the candidate spec tree (no history/drift indexes) so the
|
|
113
|
+
// scope proof follows the same code:/related: declarations that lint later enforces.
|
|
114
|
+
const specs = await loadSpecs(root, { tip, history: null, drift: null });
|
|
115
|
+
const claims = specs.flatMap((spec) => [...spec.code, ...spec.related]);
|
|
116
|
+
return changed.some((path) => claims.some((claim) => relationClaimsPath(claim, path))
|
|
117
|
+
|| path === 'spexcode.json' || path === 'spexcode.local.json'
|
|
118
|
+
|| (path.startsWith('.spec/') && !path.startsWith('.spec/.issues/'))
|
|
119
|
+
|| path === '.spec');
|
|
120
|
+
}
|
|
121
|
+
export async function specLint(root = repoRoot(), regs = extractors(root), options = {}) {
|
|
122
|
+
return (await specLintRun(root, regs, options)).findings;
|
|
123
|
+
}
|
|
124
|
+
export async function specLintReport(root = repoRoot(), regs = extractors(root), options = {}) {
|
|
125
|
+
const run = await specLintRun(root, regs, options);
|
|
126
|
+
return {
|
|
127
|
+
projection: SPEC_LINT_REPORT_PROJECTION,
|
|
128
|
+
schemaVersion: SPEC_LINT_REPORT_SCHEMA_VERSION,
|
|
129
|
+
sourceFiles: run.sourceFiles,
|
|
130
|
+
findings: run.findings,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function specLintRun(root, regs, options) {
|
|
134
|
+
return withEventLedgerBuild(root, () => specLintInLedger(root, regs, options));
|
|
135
|
+
}
|
|
136
|
+
async function specLintInLedger(root, regs, options) {
|
|
137
|
+
const tip = options.tip ?? 'HEAD';
|
|
138
|
+
const pending = tip !== 'HEAD';
|
|
139
|
+
const changed = pending ? pendingChangedPaths(root, tip) : [];
|
|
140
|
+
const files = pending ? treeFilePaths(root, tip) : null;
|
|
141
|
+
const directories = new Set();
|
|
142
|
+
for (const file of files ?? []) {
|
|
143
|
+
const parts = file.split('/');
|
|
144
|
+
for (let i = 1; i < parts.length; i++)
|
|
145
|
+
directories.add(parts.slice(0, i).join('/'));
|
|
146
|
+
}
|
|
147
|
+
const existsAtTip = (path) => files ? files.has(path) || directories.has(path.replace(/\/+$/, '')) : existsSync(join(root, path));
|
|
148
|
+
const isDirectoryAtTip = (path) => files
|
|
149
|
+
? !files.has(path) && directories.has(path.replace(/\/+$/, ''))
|
|
150
|
+
: statSync(join(root, path)).isDirectory();
|
|
151
|
+
const textAtTip = (path) => pending ? treeFileText(root, tip, path) : readFileSync(join(root, path), 'utf8');
|
|
152
|
+
const cfg = loadConfig(root, pending ? treeFileText(root, tip, 'spexcode.json') : undefined);
|
|
153
|
+
const untracked = untrackedAdoptionFiles(root);
|
|
154
|
+
if (untracked.length) {
|
|
155
|
+
const shown = untracked.slice(0, 6);
|
|
156
|
+
const suffix = untracked.length > shown.length ? ` (+${untracked.length - shown.length} more)` : '';
|
|
157
|
+
return {
|
|
158
|
+
sourceFiles: [],
|
|
159
|
+
findings: [{
|
|
160
|
+
level: 'error',
|
|
161
|
+
rule: 'integrity',
|
|
162
|
+
msg: `project source of truth is untracked: ${shown.join(', ')}${suffix} — add it with \`git add .spec spexcode.json\` and commit it; generated harness files such as .codex/, .claude/, and AGENTS.md are machine-local`,
|
|
163
|
+
}],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const governed = trackedSourceFiles(root, cfg.governedRoots, cfg, tip);
|
|
167
|
+
const [hidx, didx] = await sourceIndexes(root, tip);
|
|
168
|
+
const specs = await loadSpecs(root, { tip, history: hidx, drift: didx });
|
|
169
|
+
const out = [];
|
|
170
|
+
// integrity + build the file -> owners map. A relation's STRUCTURAL problems (a duplicate entry,
|
|
171
|
+
// bare/scoped mixing on one base path, a selector on a glob — all from parseRelation,
|
|
172
|
+
// [[code-anchor]]) are integrity errors: malformed edges block like broken ones.
|
|
173
|
+
const owners = new Map();
|
|
174
|
+
const claimed = new Set();
|
|
175
|
+
const governedClaims = new Set();
|
|
176
|
+
for (const s of specs) {
|
|
177
|
+
for (const p of s.relationProblems)
|
|
178
|
+
out.push({ level: 'error', rule: 'integrity', spec: s.id, msg: `'${s.id}' ${p}` });
|
|
179
|
+
const scopedPaths = new Set(s.codeScoped.map((e) => e.path));
|
|
180
|
+
for (const f of s.code) {
|
|
181
|
+
if (!existsAtTip(f))
|
|
182
|
+
out.push({ level: 'error', rule: 'integrity', spec: s.id, file: f, msg: `spec '${s.id}' lists a missing file: ${f}` });
|
|
183
|
+
claimed.add(f);
|
|
184
|
+
governedClaims.add(f);
|
|
185
|
+
// a selector-SCOPED entry claims named units, not the whole file, so it stays out of the owners
|
|
186
|
+
// bound below ([[code-anchor]]) — `spex spec owner` still displays it as a (scoped) governor.
|
|
187
|
+
if (!scopedPaths.has(f))
|
|
188
|
+
owners.set(f, [...(owners.get(f) ?? []), s.id]);
|
|
189
|
+
}
|
|
190
|
+
// one-govern: a node is source of truth for at most ONE file — DISTINCT base paths; several
|
|
191
|
+
// `path#symbol` selectors on the same file are one subject — so drift/eval/ack have a single
|
|
192
|
+
// unambiguous subject (see [[governed-related]]). >1 is a defect — pick the true subject, demote the
|
|
193
|
+
// rest to related. ERROR (the node-side twin of too-many-owners' file-side bound). 0 is fine.
|
|
194
|
+
if (s.code.length > 1)
|
|
195
|
+
out.push({ level: 'error', rule: 'one-govern', spec: s.id, msg: `'${s.id}' governs ${s.code.length} files [${s.code.join(', ')}] — a node is source of truth for at most ONE. Keep the true subject in code:, move the rest to related:` });
|
|
196
|
+
}
|
|
197
|
+
// a file is COVERED if any node GOVERNS (code:) or merely REFERENCES (related:) it; integrity covers both.
|
|
198
|
+
// `related:` is the coverage net: govern is a sharp ideally-one-file pointer, so most files are reached by
|
|
199
|
+
// related, not govern (see [[governed-related]]). It carries coverage but never drift, never eval freshness.
|
|
200
|
+
for (const s of specs)
|
|
201
|
+
for (const f of s.related) {
|
|
202
|
+
if (!existsAtTip(f))
|
|
203
|
+
out.push({ level: 'error', rule: 'integrity', spec: s.id, file: f, msg: `spec '${s.id}' lists a missing related file: ${f}` });
|
|
204
|
+
claimed.add(f);
|
|
205
|
+
}
|
|
206
|
+
// A candidate may retire a node together with the implementation it owned, or transfer that path to a
|
|
207
|
+
// new node. It may not delete the governor while leaving the governed subject behind: ordinary coverage
|
|
208
|
+
// is advisory, so without this transition check a candidate could erase the very contract that would
|
|
209
|
+
// reject its code change. Read only deleted spec blobs from HEAD; no second history index is needed.
|
|
210
|
+
if (pending) {
|
|
211
|
+
let deleted = [];
|
|
212
|
+
try {
|
|
213
|
+
deleted = git(['-C', root, '-c', 'core.quotePath=false', 'diff', '--diff-filter=D', '--name-only', 'HEAD', tip, '--', '.spec'])
|
|
214
|
+
.split('\n').map((path) => path.trim()).filter((path) => path.endsWith('/spec.md'));
|
|
215
|
+
}
|
|
216
|
+
catch { /* an unreadable comparison is already surfaced by the candidate history reads */ }
|
|
217
|
+
for (const specPath of deleted) {
|
|
218
|
+
const oldSource = treeFileText(root, 'HEAD', specPath);
|
|
219
|
+
if (oldSource === null)
|
|
220
|
+
continue;
|
|
221
|
+
const value = parseFrontmatter(oldSource).fm.code;
|
|
222
|
+
const rows = Array.isArray(value) ? value : value ? [value] : [];
|
|
223
|
+
for (const row of rows) {
|
|
224
|
+
const path = parseCodeEntry(row).path;
|
|
225
|
+
if (existsAtTip(path) && !governedClaims.has(path))
|
|
226
|
+
out.push({ level: 'error', rule: 'integrity', file: path, msg: `candidate deletes governor '${specPath}' but leaves its governed subject '${path}' without a code: owner — delete the retired implementation too, or transfer it to a real node in this commit` });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// id-format: a node id (its leaf dir basename) passes an EXACT per-character whitelist — an ascii char
|
|
231
|
+
// must be [a-z0-9-]; a non-ascii char must be a unicode letter/number (judged on NFC, the mint's
|
|
232
|
+
// canonical form) — and is UNIQUE tree-wide (ERROR). This is THE id vocabulary, defined once (the
|
|
233
|
+
// spec-lint node's rule table) and referenced by [[mentions]] / [[id-url-safe]]: CJK and every other
|
|
234
|
+
// letter script is first-class, exactly what the script-agnostic resolve machinery already accepts.
|
|
235
|
+
// Everything else is forbidden by construction, no heuristics — space, '/', uppercase Latin (lowercase
|
|
236
|
+
// is the Latin norm), control chars, and '_' (reserved as the mint's parent-qualification join; a '_'
|
|
237
|
+
// inside a basename would make that join ambiguous). One optional leading dot is allowed — the
|
|
238
|
+
// reflexive plugin root `.plugins` is dot-prefixed by design. Uniqueness is what keeps the leaf THE id:
|
|
239
|
+
// on a collision the mint must parent-qualify with `_`, so every surface suddenly speaks a longer id
|
|
240
|
+
// than the dir name — legal to the machinery, illegible to people.
|
|
241
|
+
const ID_RE = /^\.?(?:[a-z0-9-]|(?![\x00-\x7F])[\p{L}\p{N}])+$/u;
|
|
242
|
+
const leafOf = (p) => { const segs = p.split('/'); return segs[segs.length - 2]; };
|
|
243
|
+
const byLeaf = new Map();
|
|
244
|
+
for (const s of specs) {
|
|
245
|
+
const leaf = leafOf(s.path);
|
|
246
|
+
byLeaf.set(leaf, [...(byLeaf.get(leaf) ?? []), s.path]);
|
|
247
|
+
if (!ID_RE.test(leaf.normalize('NFC')))
|
|
248
|
+
out.push({ level: 'error', rule: 'id-format', spec: s.id, msg: `node dir '${leaf}' is not a valid id — each char is ascii [a-z0-9-] or a non-ascii unicode letter/number (one optional leading dot); space, '/', '_', uppercase Latin and control chars are forbidden; rename the directory` });
|
|
249
|
+
}
|
|
250
|
+
for (const [leaf, paths] of byLeaf) {
|
|
251
|
+
if (paths.length > 1)
|
|
252
|
+
out.push({ level: 'error', rule: 'id-format', msg: `leaf id '${leaf}' names ${paths.length} nodes [${paths.map((p) => p.replace(/\/spec\.md$/, '')).join(', ')}] — a leaf id is unique tree-wide; rename all but one` });
|
|
253
|
+
}
|
|
254
|
+
// confusable-id: two leaf ids one edit apart read as the same word (WARN — a typo in either reaches a
|
|
255
|
+
// real, wrong node). Deliberately conservative: distance exactly 1, so hierarchy naming like
|
|
256
|
+
// graph/graph-delivery (a whole suffix apart) and verb pairs like evidence-put/evidence-get (distance 2)
|
|
257
|
+
// never warn — better to miss a borderline pair than to nag legitimate siblings. Distance is measured
|
|
258
|
+
// in CODE POINTS, script-agnostic: a CJK pair one character apart (节点/结点 — a classic homophone
|
|
259
|
+
// IME slip) warns like an ascii pair, an astral char counts as one edit (not two surrogate units), and
|
|
260
|
+
// a pure-CJK id can never sit one edit from a pure-ascii one.
|
|
261
|
+
const lev1 = (a, b) => {
|
|
262
|
+
const A = [...a], B = [...b];
|
|
263
|
+
if (Math.abs(A.length - B.length) > 1 || a === b)
|
|
264
|
+
return false;
|
|
265
|
+
let i = 0;
|
|
266
|
+
while (i < A.length && i < B.length && A[i] === B[i])
|
|
267
|
+
i++;
|
|
268
|
+
const rest = (x, k) => x.slice(k).join('');
|
|
269
|
+
return rest(A, i + 1) === rest(B, i + 1) || rest(A, i + 1) === rest(B, i) || rest(A, i) === rest(B, i + 1);
|
|
270
|
+
};
|
|
271
|
+
const leaves = [...byLeaf.keys()];
|
|
272
|
+
for (let i = 0; i < leaves.length; i++)
|
|
273
|
+
for (let j = i + 1; j < leaves.length; j++) {
|
|
274
|
+
if (lev1(leaves[i], leaves[j]))
|
|
275
|
+
out.push({ level: 'warn', rule: 'confusable-id', msg: `leaf ids '${leaves[i]}' and '${leaves[j]}' are one edit apart — easily confused; if they are distinct concepts, rename one to read apart` });
|
|
276
|
+
}
|
|
277
|
+
// mention: a `[[id]]` in a body must name a real node (ERROR — a dangling mention is a broken edge in
|
|
278
|
+
// the very graph the tree exists to keep honest). Checked against the SAME minted ids every other
|
|
279
|
+
// surface resolves ([[id-url-safe]]). Prose only: a fenced block or inline `code span` is sample text
|
|
280
|
+
// (`[[node]]`, `[[<id>]]` placeholders live there), not a reference.
|
|
281
|
+
const idSet = new Set(specs.map((s) => s.id));
|
|
282
|
+
const MENTION_RE = /\[\[(\.?[\p{L}\p{N}_-]+)\]\]/gu;
|
|
283
|
+
for (const s of specs) {
|
|
284
|
+
let inFence = false;
|
|
285
|
+
for (const rawLine of s.body.split('\n')) {
|
|
286
|
+
if (/^\s*```/.test(rawLine)) {
|
|
287
|
+
inFence = !inFence;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (inFence)
|
|
291
|
+
continue;
|
|
292
|
+
const line = rawLine.replace(/`[^`]*`/g, '');
|
|
293
|
+
for (const m of line.matchAll(MENTION_RE)) {
|
|
294
|
+
if (!idSet.has(m[1]))
|
|
295
|
+
out.push({ level: 'error', rule: 'mention', spec: s.id, msg: `'${s.id}' mentions [[${m[1]}]] — no such node; retarget or drop it (backtick it if it is sample text)` });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// living: a spec body describes the node's CURRENT intent — it is not a changelog. Version history
|
|
300
|
+
// (every content commit, its reason/session/line-diff) is read from git and shown in the dashboard's
|
|
301
|
+
// recent/history tabs, so a `## vN`-style heading in the body is duplicated, drift-prone state.
|
|
302
|
+
// Reject it. Fence-aware — a `## v2` inside a ``` block is sample text, not a heading.
|
|
303
|
+
const VER_HEADING = /^#{1,6}\s+v\d+\b/;
|
|
304
|
+
for (const s of specs) {
|
|
305
|
+
let inFence = false;
|
|
306
|
+
for (const line of s.body.split('\n')) {
|
|
307
|
+
if (/^\s*```/.test(line)) {
|
|
308
|
+
inFence = !inFence;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (!inFence && VER_HEADING.test(line))
|
|
312
|
+
out.push({ level: 'error', rule: 'living', spec: s.id, msg: `'${s.id}' has a changelog heading "${line.trim()}" — keep the body current-state; version history lives in git (recent/history tabs)` });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// coverage: every governed source file must be claimed by at least one spec.
|
|
316
|
+
if (governed.length === 0)
|
|
317
|
+
out.push({ level: 'warn', rule: 'coverage', msg: `governing NOTHING — 0 source candidates under governedRoots [${cfg.governedRoots.join(', ')}]; ${sourcePolicyDescription(cfg)}. Repair these knobs under the "lint" key in spexcode.json (top-level keys are ignored): governedRoots, sourceIncludeGlobs, sourceExcludeGlobs, testGlobs; sourceExtensions remains compatibility shorthand for include globs.` });
|
|
318
|
+
for (const f of governed)
|
|
319
|
+
if (!claimed.has(f))
|
|
320
|
+
out.push({ level: 'warn', rule: 'coverage', file: f, msg: `no spec governs: ${f}` });
|
|
321
|
+
// too-many-owners: many nodes governing one file is ordinary composition (drift fans to each — correct,
|
|
322
|
+
// every owner has a stake), so this is NOT flagged. Only an OVER-owned file is a smell — governed by more
|
|
323
|
+
// than maxOwners nodes, it has accreted more independently-specified functionality than one file should
|
|
324
|
+
// hold (see [[governed-related]]). ONE summary line (not one per file — its own wall of noise): the count,
|
|
325
|
+
// the worst offenders, and the remedy, which blames the FILE not the ownership — SPLIT it so each governor
|
|
326
|
+
// reclaims its own module; or merge the nodes if they're one concern; or give it a single foundation owner.
|
|
327
|
+
const over = [...owners].filter(([, ids]) => ids.length > cfg.maxOwners).sort((a, b) => b[1].length - a[1].length);
|
|
328
|
+
if (over.length) {
|
|
329
|
+
const top = over.slice(0, 5).map(([f, ids]) => `${f.split('/').pop()}(${ids.length})`).join(', ');
|
|
330
|
+
out.push({ level: 'warn', rule: 'owners', msg: `${over.length} file(s) are governed by > ${cfg.maxOwners} nodes — each holds more separately-specified functionality than one file should. Worst: ${top}. SPLIT the file so each governor owns its own module (or merge the nodes, or give it a single foundation owner + related:).` });
|
|
331
|
+
}
|
|
332
|
+
const readyWarned = new Set();
|
|
333
|
+
const anchorSteps = [];
|
|
334
|
+
for (const s of specs) {
|
|
335
|
+
for (const { relation, entries } of [{ relation: 'code', entries: s.codeScoped }, { relation: 'related', entries: s.relatedScoped }]) {
|
|
336
|
+
for (const { path, selectors } of entries) {
|
|
337
|
+
if (pending && !changed.some((file) => relationClaimsPath(path, file)))
|
|
338
|
+
continue;
|
|
339
|
+
const x = extractorFor(regs, extOf(path));
|
|
340
|
+
if (!x) {
|
|
341
|
+
anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `'${s.id}' anchors ${path}#${selectors.join(', #')} (${relation}:), but no extractor is designated for '.${extOf(path)}' files — anchor validation was skipped and remains unverified; add a LangSpec row (anchors.ts) or drop the selector(s)` } });
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const ready = x.ready();
|
|
345
|
+
if (ready !== true) {
|
|
346
|
+
// once per (extractor, reason), even across several anchored nodes — one repair, one message.
|
|
347
|
+
if (!readyWarned.has(x.id + ready)) {
|
|
348
|
+
readyWarned.add(x.id + ready);
|
|
349
|
+
anchorSteps.push({ finding: { level: 'error', rule: 'integrity', msg: `anchor extractor '${x.id}' cannot run: ${ready}` } });
|
|
350
|
+
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (!existsAtTip(path))
|
|
354
|
+
continue; // the missing FILE already errored above
|
|
355
|
+
if (isDirectoryAtTip(path)) {
|
|
356
|
+
anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `'${s.id}' puts a selector on a directory (${relation}: ${path}#${selectors[0]}) — a selector scopes ONE real file` } });
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
let units;
|
|
360
|
+
try {
|
|
361
|
+
const source = textAtTip(path);
|
|
362
|
+
if (source === null)
|
|
363
|
+
throw new Error(`candidate tree has no file '${path}'`);
|
|
364
|
+
units = x.extract(source, path);
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `anchor ${path}#${selectors.join(', #')} ('${s.id}') is unverifiable — the current file does not parse: ${e?.message ?? e}` } });
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
// each selector resolves (or errors) on its own; only the live ones feed the window engine.
|
|
371
|
+
// The dead/ambiguous verdict itself comes from the ONE shared classifier ([[code-anchor]]); only the
|
|
372
|
+
// wording of the gate's findings lives here.
|
|
373
|
+
const live = [];
|
|
374
|
+
for (const res of resolveSelectors(units, selectors)) {
|
|
375
|
+
const sym = res.selector;
|
|
376
|
+
if ('dead' in res) {
|
|
377
|
+
anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `dead anchor: ${path}#${sym} ('${s.id}') names no unit on the current tree — the unit was deleted or renamed; update the spec's ${relation}: entry to follow it` } });
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if ('ambiguous' in res) {
|
|
381
|
+
anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `ambiguous anchor: ${path}#${sym} ('${s.id}') names ${res.ambiguous} same-named units in one file — an anchor must be unique; rename one unit` } });
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (res.ok.typeOnly)
|
|
385
|
+
anchorSteps.push({ finding: { level: 'warn', rule: 'anchor', spec: s.id, file: path, msg: `${path}#${sym} anchors a ${res.ok.kind} — anchoring a type is usually wrong (types reshape with every refactor); anchor the behaviour-bearing unit instead` } });
|
|
386
|
+
live.push(sym);
|
|
387
|
+
}
|
|
388
|
+
if (!live.length)
|
|
389
|
+
continue;
|
|
390
|
+
const since = rowsFor(hidx, s.path)[0]?.hash || '';
|
|
391
|
+
const win = windowEvents(didx, since, path, s.id);
|
|
392
|
+
if (!win.length)
|
|
393
|
+
continue;
|
|
394
|
+
anchorSteps.push({ query: { id: s.id, version: s.version, relation, path, symbols: live, win } });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const anchorHits = await anchorHitQueries(root, anchorSteps.flatMap((step) => 'query' in step ? [{ win: step.query.win, symbols: step.query.symbols }] : []), regs);
|
|
399
|
+
let hitIndex = 0;
|
|
400
|
+
for (const step of anchorSteps) {
|
|
401
|
+
if ('finding' in step) {
|
|
402
|
+
out.push(step.finding);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const { id, version, relation, path } = step.query;
|
|
406
|
+
const hits = anchorHits[hitIndex++];
|
|
407
|
+
if (!hits.length)
|
|
408
|
+
continue;
|
|
409
|
+
const hitSyms = [...new Set(hits.flatMap((h) => h.selectors))];
|
|
410
|
+
const shas = hits.map((h) => h.commit.slice(0, 8)).join(', ');
|
|
411
|
+
const unparseable = hits.filter((h) => h.unparseable);
|
|
412
|
+
const parseNote = unparseable.length ? ` (${unparseable.length} of these could not be parsed at that commit — counted as hits conservatively)` : '';
|
|
413
|
+
if (relation === 'code') {
|
|
414
|
+
const current = pending && hits.some((hit) => hit.commit === tip);
|
|
415
|
+
const older = pending && hits.some((hit) => hit.commit !== tip);
|
|
416
|
+
const remedy = !pending
|
|
417
|
+
? `update the spec, or 'spex spec ack ${id} --reason "…"' if the contract still holds`
|
|
418
|
+
: current && older
|
|
419
|
+
? `update the spec in this commit; its own hit can be declared by retrying with 'git commit --trailer "Spec-OK: ${id}" …', but the listed older debt must be cleared first (an in-commit declaration never pardons ancestors)`
|
|
420
|
+
: current
|
|
421
|
+
? `update the spec in this commit, or retry with 'git commit --trailer "Spec-OK: ${id}" …'; a later empty ack cannot pre-author this candidate`
|
|
422
|
+
: `update the spec in this commit, or clear this older debt with 'spex spec ack ${id} --reason "…"' before retrying the candidate`;
|
|
423
|
+
out.push({ level: 'error', rule: 'anchor-drift', spec: id, file: path, msg: `${path}#${hitSyms.join(', #')} was changed by ${hits.length} commit(s) since spec '${id}' v${version} [${shas}]${parseNote} — the anchored contract's code moved: ${remedy}` });
|
|
424
|
+
}
|
|
425
|
+
else
|
|
426
|
+
out.push({ level: 'warn', rule: 'related-drift', spec: id, file: path, msg: `related ${path}#${hitSyms.join(', #')} ('${id}') was changed by ${hits.length} commit(s) since v${version} [${shas}]${parseNote} — a scoped dependency shifted, worth a glance (SOFT: never blocks, no ack, no eval staleness)` });
|
|
427
|
+
}
|
|
428
|
+
// drift: a governed file has commits NOT yet reflected in its spec. Judged by true git ancestry —
|
|
429
|
+
// loadSpecs computes `driftFiles` via driftFor() over the one cached driftIndex walk (git.ts): a
|
|
430
|
+
// commit to the file counts iff it is NOT reachable from the spec's latest version (in-memory
|
|
431
|
+
// parent-edge reachability, the equivalent of `rev-list <version>..HEAD -- <file>`), never a
|
|
432
|
+
// log-position or timestamp guess.
|
|
433
|
+
// A selector-SCOPED code file keeps this file-level advisory by default (a miss still nudges); the
|
|
434
|
+
// committed `lint.scopedCodeMiss: "ignore"` silences ONLY this advisory for scoped entries — the
|
|
435
|
+
// anchor engine's verdicts above (hit = block, resolution failures = integrity) are untouched.
|
|
436
|
+
for (const s of specs) {
|
|
437
|
+
const scopedPaths = new Set(s.codeScoped.map((e) => e.path));
|
|
438
|
+
for (const d of s.driftFiles) {
|
|
439
|
+
if (cfg.scopedCodeMiss === 'ignore' && scopedPaths.has(d.file))
|
|
440
|
+
continue;
|
|
441
|
+
out.push({ level: 'warn', rule: 'drift', spec: s.id, file: d.file, msg: `${d.file} is ${d.behind} commit(s) ahead of spec '${s.id}' (v${s.version}) — may be stale` });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// related drift: the SOFT tier ([[governed-related]]). A referenced file moved ahead of the node's
|
|
445
|
+
// version — a nudge that a dependency shifted. Same ancestry basis as govern drift, but WARN-only,
|
|
446
|
+
// never reaching the commit gate (driftGate reads govern) or eval freshness. It is COMMON (shared substrate and
|
|
447
|
+
// faces change often without re-versioning every referrer), so per-file it is a wall; like
|
|
448
|
+
// too-many-owners it collapses to ONE summary line, with the per-file detail riding the board
|
|
449
|
+
// (relatedDriftFiles). It stays a soft edge, never a per-file interruption.
|
|
450
|
+
const rd = specs.flatMap((s) => s.relatedDriftFiles.map((d) => ({ id: s.id, behind: d.behind })));
|
|
451
|
+
if (rd.length) {
|
|
452
|
+
const byNode = new Map();
|
|
453
|
+
for (const d of rd)
|
|
454
|
+
byNode.set(d.id, (byNode.get(d.id) ?? 0) + 1);
|
|
455
|
+
const worst = [...byNode].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([id, n]) => `${id}(${n})`).join(', ');
|
|
456
|
+
out.push({ level: 'warn', rule: 'related-drift', msg: `${rd.length} related file(s) across ${byNode.size} node(s) drifted ahead of their spec (SOFT — a dependency shifted, worth a glance; never blocks, no ack, no eval staleness). Most: ${worst}` });
|
|
457
|
+
}
|
|
458
|
+
return { sourceFiles: governed.slice().sort(), findings: out };
|
|
459
|
+
}
|
|
460
|
+
export const DRIFT_GUIDANCE = `DRIFT — a governed file has moved ahead of its spec. A CHECKPOINT, not a chore: find WHERE the truth
|
|
461
|
+
broke along raw intent → expanded spec → code: link → code structure → implementation, fix THAT layer.
|
|
462
|
+
|
|
463
|
+
Inspect: spex spec lint which files, against which spec
|
|
464
|
+
the node's spec.md its raw source + expanded spec
|
|
465
|
+
git diff $(git log -1 --format=%H -- <spec.md>)..HEAD -- <file> the code delta since the spec
|
|
466
|
+
|
|
467
|
+
Diagnose, then apply the one honest remedy:
|
|
468
|
+
• contract changed → rewrite the spec body to the new intent, commit it (re-versions the node)
|
|
469
|
+
• only mechanics changed → spex spec ack <node> "checked — spec still valid" (give a real reason)
|
|
470
|
+
• implementation is WRONG → the spec is right; fix the CODE back toward it, then ack
|
|
471
|
+
• wrong code: link → the node shouldn't own this file (or owns it too broadly); fix frontmatter
|
|
472
|
+
• expanded spec ≠ raw → the spec drifted from human intent; fix the expanded spec to serve the raw
|
|
473
|
+
• structural mismatch → one file owned by many specs / a feature with no home: refactor so a file
|
|
474
|
+
maps to a node, or file an issue and link it (defer honestly)
|
|
475
|
+
|
|
476
|
+
Never patch. A reasoned ack or a real fix are recorded and re-judged at review; a blind ack is a lie.`;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Server } from 'node:net'
|
|
2
|
-
|
|
3
1
|
// @@@ listenOrExit - the shared "I own this port; if I cannot bind it, I have failed" contract for the two
|
|
4
2
|
// public-port listeners: the supervisor's raw-TCP proxy (supervise.ts) and the dashboard/public gateway
|
|
5
3
|
// (gateway.ts). A bind failure is the ONE thing neither may survive — it is the opposite of the keep-serving
|
|
@@ -10,19 +8,17 @@ import type { Server } from 'node:net'
|
|
|
10
8
|
// reap any child booted for this bind so none is orphaned, and exit non-zero.
|
|
11
9
|
//
|
|
12
10
|
// http.Server / https.Server both extend net.Server, so this one signature covers every caller.
|
|
13
|
-
export function listenOrExit(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (opts.host) server.listen(port, opts.host, opts.onListen)
|
|
27
|
-
else server.listen(port, opts.onListen)
|
|
11
|
+
export function listenOrExit(server, port, opts) {
|
|
12
|
+
server.once('error', (err) => {
|
|
13
|
+
opts.cleanup?.();
|
|
14
|
+
const why = err.code === 'EADDRINUSE' ? `port ${port} is already in use`
|
|
15
|
+
: err.code === 'EACCES' ? `permission denied binding port ${port}`
|
|
16
|
+
: err.code ?? err.message;
|
|
17
|
+
console.error(`spec-cli: ${opts.label} cannot bind — ${why}. Free :${port} (e.g. lsof -i :${port}) or pick another port, then retry.`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
20
|
+
if (opts.host)
|
|
21
|
+
server.listen(port, opts.host, opts.onListen);
|
|
22
|
+
else
|
|
23
|
+
server.listen(port, opts.onListen);
|
|
28
24
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type DispatchOutcome } from './mentions.js';
|
|
2
|
+
import type { Issue } from './issues.js';
|
|
3
|
+
export declare const issuesEnabled: () => boolean;
|
|
4
|
+
export declare const currentSession: () => string;
|
|
5
|
+
export declare function loadLocalIssues(): Issue[];
|
|
6
|
+
export declare function loadOne(id: string): Issue;
|
|
7
|
+
export declare function commitTrunkData(relPath: string, message: string): 'committed' | 'no-op' | 'not-primary';
|
|
8
|
+
export declare function openIssue(concern: string, opts?: {
|
|
9
|
+
nodes?: string[];
|
|
10
|
+
body?: string;
|
|
11
|
+
evidence?: string[];
|
|
12
|
+
author?: string;
|
|
13
|
+
}): Issue;
|
|
14
|
+
export declare function reply(id: string, body: string, author?: string, evidence?: string[], remark?: {
|
|
15
|
+
targetCodeSha: string;
|
|
16
|
+
}): Issue;
|
|
17
|
+
export declare function replyLocalIssue(id: string, body: string, author: string, evidence?: string[], remark?: {
|
|
18
|
+
targetCodeSha: string;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
thread: Issue;
|
|
21
|
+
outcomes: DispatchOutcome[];
|
|
22
|
+
}>;
|
|
23
|
+
export declare function postLocalIssue(concern: string, opts: {
|
|
24
|
+
nodes?: string[];
|
|
25
|
+
body?: string;
|
|
26
|
+
evidence?: string[];
|
|
27
|
+
author: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
thread: Issue;
|
|
30
|
+
outcomes: DispatchOutcome[];
|
|
31
|
+
}>;
|
|
32
|
+
export declare function closeLocalIssue(id: string): {
|
|
33
|
+
status: 'landed';
|
|
34
|
+
already: boolean;
|
|
35
|
+
};
|
|
36
|
+
export declare const parseEvalConcern: (concern: string) => {
|
|
37
|
+
node: string;
|
|
38
|
+
scenario: string;
|
|
39
|
+
} | null;
|
|
40
|
+
export declare function remarkOnHost(host: {
|
|
41
|
+
issue?: string;
|
|
42
|
+
node?: string;
|
|
43
|
+
scenario?: string;
|
|
44
|
+
}, body: string, opts?: {
|
|
45
|
+
codeSha?: string;
|
|
46
|
+
author?: string;
|
|
47
|
+
evidence?: string[];
|
|
48
|
+
}): Promise<{
|
|
49
|
+
ref: string;
|
|
50
|
+
rid: string;
|
|
51
|
+
codeSha: string;
|
|
52
|
+
thread: Issue;
|
|
53
|
+
author: string;
|
|
54
|
+
outcomes: DispatchOutcome[];
|
|
55
|
+
}>;
|
|
56
|
+
export declare function resolveRemark(ref: string, by: string): {
|
|
57
|
+
thread: Issue;
|
|
58
|
+
rid: string;
|
|
59
|
+
};
|
|
60
|
+
export declare function retractRemark(ref: string, by: string): {
|
|
61
|
+
thread: Issue;
|
|
62
|
+
rid: string;
|
|
63
|
+
};
|
|
64
|
+
export declare function nudge(node: string): string;
|
|
65
|
+
export declare function closeoutNudge(sessionId: string | null | undefined): string;
|