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
package/spec-cli/src/reviews.ts
DELETED
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
import { listSessions } from './sessions.js'
|
|
3
|
-
import { getBoard, getBoardForForgeRevision } from './graphCache.js'
|
|
4
|
-
import { type SessionEvalOrderRow, buildSessionEvals, type SessionEvals } from '../../spec-eval/src/sessioneval.js'
|
|
5
|
-
import { evalTimeline } from '../../spec-eval/src/evaltab.js'
|
|
6
|
-
import { issuesEnabled as issuesEnabledForReview } from './localIssues.js'
|
|
7
|
-
import { issueStores as issueStoresForReview } from './issues.js'
|
|
8
|
-
import { hasReviewSnapshot, readReviewSnapshot } from './reviewSnapshot.js'
|
|
9
|
-
import { residentForgeRevision, residentForgeState } from '../../spec-forge/src/resident.js'
|
|
10
|
-
// @ts-expect-error The dashboard module is deliberately plain JS so the browser and server execute the
|
|
11
|
-
// exact same tokenizer/matcher. It is shipped beside the built dashboard by the root package manifest.
|
|
12
|
-
import { EVAL_FILTER_KIND, evalFilterModel, evalReviewState, issueFilterModel, tokenFilterState } from '../../spec-dashboard/src/reviewFilters.js'
|
|
13
|
-
// @ts-expect-error See the shared-domain note above.
|
|
14
|
-
import { EVAL_QUERY_DEFAULT, ISSUE_QUERY_DEFAULT, readToken } from '../../spec-dashboard/src/reviewQuery.js'
|
|
15
|
-
|
|
16
|
-
export const REVIEW_PER_PAGE = 25
|
|
17
|
-
|
|
18
|
-
type ReviewItem = Record<string, unknown>
|
|
19
|
-
// A section count is one number, or — when the domain adapter splits that section — its named buckets,
|
|
20
|
-
// which re-add to the same whole population ([[review-filters]] owns the split; Evals splits its measured
|
|
21
|
-
// verdicts into {fresh,stale} so the remeasurement debt travels with the count instead of being recomputed
|
|
22
|
-
// per surface). The fold happens ONCE, here on the server, over the complete filtered population.
|
|
23
|
-
export type ReviewCount = number | Record<string, number>
|
|
24
|
-
type ReviewOption = { value: string; label?: string; count?: number }
|
|
25
|
-
type ReviewFacet = { key: string; label?: string; value: string; meaningful?: boolean; options: ReviewOption[] }
|
|
26
|
-
type EvalNeighbor = { node: string; scenario: string; state: string }
|
|
27
|
-
|
|
28
|
-
export type PagedReview<T extends ReviewItem = ReviewItem> = {
|
|
29
|
-
items: T[]
|
|
30
|
-
page: number
|
|
31
|
-
perPage: number
|
|
32
|
-
total: number
|
|
33
|
-
sourceTotal: number
|
|
34
|
-
pageCount: number
|
|
35
|
-
prev: number | null
|
|
36
|
-
next: number | null
|
|
37
|
-
revision: string
|
|
38
|
-
counts: Record<string, ReviewCount>
|
|
39
|
-
facets: Record<string, ReviewFacet>
|
|
40
|
-
section: { key: string; value: string; options: ReviewOption[] } | null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type EvalDetailReview = {
|
|
44
|
-
scope: string | null
|
|
45
|
-
selected: ReviewItem | null
|
|
46
|
-
history: ReviewItem[]
|
|
47
|
-
neighbors: {
|
|
48
|
-
prev: EvalNeighbor[]
|
|
49
|
-
next: EvalNeighbor[]
|
|
50
|
-
total: number
|
|
51
|
-
index: number | null
|
|
52
|
-
order: 'default'
|
|
53
|
-
}
|
|
54
|
-
revision: string
|
|
55
|
-
summary?: SessionEvals['summary']
|
|
56
|
-
evalRevision?: SessionEvals['evalRevision']
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const revisionOf = (value: unknown): string => createHash('sha256').update(JSON.stringify(value)).digest('hex')
|
|
60
|
-
|
|
61
|
-
export function reviewPageNumber(value: unknown): number {
|
|
62
|
-
const raw = typeof value === 'string' && /^\d+$/.test(value) ? Number(value) : Number.NaN
|
|
63
|
-
return Number.isSafeInteger(raw) && raw > 0 ? raw : 1
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function responseModel(model: any): Pick<PagedReview, 'counts' | 'facets' | 'section'> {
|
|
67
|
-
const facets = Object.fromEntries(Object.entries(model.facets ?? {}).map(([key, raw]) => {
|
|
68
|
-
const facet = raw as any
|
|
69
|
-
return [key, {
|
|
70
|
-
key,
|
|
71
|
-
...(facet.label != null ? { label: String(facet.label) } : {}),
|
|
72
|
-
value: String(facet.value ?? ''),
|
|
73
|
-
...(facet.meaningful != null ? { meaningful: !!facet.meaningful } : {}),
|
|
74
|
-
options: (facet.options ?? []).map((option: any) => ({
|
|
75
|
-
value: String(option.value ?? ''),
|
|
76
|
-
...(option.label != null ? { label: String(option.label) } : {}),
|
|
77
|
-
...(Number.isFinite(option.count) ? { count: Number(option.count) } : {}),
|
|
78
|
-
})),
|
|
79
|
-
}]
|
|
80
|
-
})) as Record<string, ReviewFacet>
|
|
81
|
-
const section = model.section ? {
|
|
82
|
-
key: String(model.section.key),
|
|
83
|
-
...(model.section.label != null ? { label: String(model.section.label) } : {}),
|
|
84
|
-
value: String(model.section.value ?? ''),
|
|
85
|
-
...(model.section.meaningful != null ? { meaningful: !!model.section.meaningful } : {}),
|
|
86
|
-
options: (model.section.options ?? []).map((option: any) => ({
|
|
87
|
-
value: String(option.value ?? ''),
|
|
88
|
-
...(option.label != null ? { label: String(option.label) } : {}),
|
|
89
|
-
...(Number.isFinite(option.count) ? { count: Number(option.count) } : {}),
|
|
90
|
-
})),
|
|
91
|
-
} : null
|
|
92
|
-
return { counts: { ...(model.sections ?? {}) }, facets, section }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function paginateReview<T extends ReviewItem>(
|
|
96
|
-
source: T[],
|
|
97
|
-
shown: T[],
|
|
98
|
-
model: any,
|
|
99
|
-
requestedPage: unknown,
|
|
100
|
-
revisionInputs: unknown,
|
|
101
|
-
): PagedReview<T> {
|
|
102
|
-
const page = reviewPageNumber(requestedPage)
|
|
103
|
-
const total = shown.length
|
|
104
|
-
const pageCount = Math.ceil(total / REVIEW_PER_PAGE)
|
|
105
|
-
const start = (page - 1) * REVIEW_PER_PAGE
|
|
106
|
-
const overflow = pageCount > 0 ? page > pageCount : page > 1
|
|
107
|
-
return {
|
|
108
|
-
items: shown.slice(start, start + REVIEW_PER_PAGE),
|
|
109
|
-
page,
|
|
110
|
-
perPage: REVIEW_PER_PAGE,
|
|
111
|
-
total,
|
|
112
|
-
sourceTotal: source.length,
|
|
113
|
-
pageCount,
|
|
114
|
-
prev: page > 1 ? page - 1 : null,
|
|
115
|
-
next: page < pageCount || overflow ? page + 1 : null,
|
|
116
|
-
revision: revisionOf(revisionInputs),
|
|
117
|
-
...responseModel(model),
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const issueOrder = (a: any, b: any): number => String(b.created ?? '').localeCompare(String(a.created ?? ''))
|
|
122
|
-
|| String(a.id ?? '').localeCompare(String(b.id ?? ''))
|
|
123
|
-
|
|
124
|
-
export async function issuesReview(query: string | undefined, requestedPage: unknown) {
|
|
125
|
-
// The first request must wait for the first atomic publication. Once one exists, a graph refresh may be
|
|
126
|
-
// rebuilding unrelated board/session state; the published review source remains a valid answer and its
|
|
127
|
-
// revision/poll path will deliver the next generation without making this page join that flight.
|
|
128
|
-
residentForgeState()
|
|
129
|
-
if (!hasReviewSnapshot()) await getBoard()
|
|
130
|
-
else if (readReviewSnapshot().forgeRevision < residentForgeRevision()) {
|
|
131
|
-
await getBoardForForgeRevision(residentForgeRevision())
|
|
132
|
-
}
|
|
133
|
-
const sessions = await listSessions()
|
|
134
|
-
const issues = readReviewSnapshot().issues.slice().sort(issueOrder)
|
|
135
|
-
const text = String(query ?? '').trim() || ISSUE_QUERY_DEFAULT
|
|
136
|
-
const model = issueFilterModel(issues, tokenFilterState(text, 'issue'), { sessions, defaultSection: '' })
|
|
137
|
-
return {
|
|
138
|
-
enabled: issuesEnabledForReview(),
|
|
139
|
-
stores: issueStoresForReview(),
|
|
140
|
-
...paginateReview(issues, model.shown, model, requestedPage, {
|
|
141
|
-
domain: 'issues', issues, sessions: sessions.map((session) => session.id),
|
|
142
|
-
}),
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const byNewest = (a: any, b: any): number => Number(b.filterKind === EVAL_FILTER_KIND.RESULT) - Number(a.filterKind === EVAL_FILTER_KIND.RESULT)
|
|
147
|
-
|| String(b.ts ?? '').localeCompare(String(a.ts ?? ''))
|
|
148
|
-
|| String(a.node ?? '').localeCompare(String(b.node ?? ''))
|
|
149
|
-
|| String(a.scenario ?? '').localeCompare(String(b.scenario ?? ''))
|
|
150
|
-
|
|
151
|
-
export function trunkEvalReviewItems(nodes: any[]): ReviewItem[] {
|
|
152
|
-
const items: any[] = []
|
|
153
|
-
for (const node of nodes ?? []) {
|
|
154
|
-
const latest = new Map<string, any>()
|
|
155
|
-
for (const reading of node.evals ?? []) if (!latest.has(reading.scenario)) latest.set(reading.scenario, reading)
|
|
156
|
-
for (const scenario of node.scenarios ?? []) {
|
|
157
|
-
const reading = latest.get(scenario.name)
|
|
158
|
-
if (!reading) {
|
|
159
|
-
items.push({
|
|
160
|
-
scenario: scenario.name,
|
|
161
|
-
expected: scenario.expected,
|
|
162
|
-
tags: scenario.tags,
|
|
163
|
-
node: node.id,
|
|
164
|
-
hue: node.hue,
|
|
165
|
-
filterKind: EVAL_FILTER_KIND.BLIND,
|
|
166
|
-
})
|
|
167
|
-
continue
|
|
168
|
-
}
|
|
169
|
-
items.push({
|
|
170
|
-
...reading,
|
|
171
|
-
expected: scenario.expected ?? reading.expected,
|
|
172
|
-
tags: scenario.tags,
|
|
173
|
-
state: evalReviewState(reading),
|
|
174
|
-
node: node.id,
|
|
175
|
-
hue: node.hue,
|
|
176
|
-
filterKind: EVAL_FILTER_KIND.RESULT,
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return items.sort(byNewest)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function scopedEvalReviewItems(model: SessionEvals): ReviewItem[] {
|
|
184
|
-
const items: any[] = []
|
|
185
|
-
for (const node of model.nodes ?? []) {
|
|
186
|
-
const latest = new Map<string, any>()
|
|
187
|
-
for (const reading of node.evals ?? []) if (!latest.has(reading.scenario)) latest.set(reading.scenario, reading)
|
|
188
|
-
for (const scenario of node.scenarios ?? []) {
|
|
189
|
-
const reading = latest.get(scenario.name)
|
|
190
|
-
if (!reading) {
|
|
191
|
-
items.push({
|
|
192
|
-
scenario: scenario.name,
|
|
193
|
-
expected: scenario.expected,
|
|
194
|
-
tags: scenario.tags,
|
|
195
|
-
impact: scenario.impact,
|
|
196
|
-
node: node.id,
|
|
197
|
-
hue: node.hue,
|
|
198
|
-
filterKind: EVAL_FILTER_KIND.BLIND,
|
|
199
|
-
})
|
|
200
|
-
continue
|
|
201
|
-
}
|
|
202
|
-
const item = {
|
|
203
|
-
...reading,
|
|
204
|
-
expected: scenario.expected ?? reading.expected,
|
|
205
|
-
tags: scenario.tags,
|
|
206
|
-
impact: scenario.impact,
|
|
207
|
-
state: evalReviewState(reading),
|
|
208
|
-
node: node.id,
|
|
209
|
-
hue: node.hue,
|
|
210
|
-
filterKind: EVAL_FILTER_KIND.RESULT,
|
|
211
|
-
}
|
|
212
|
-
items.push(item)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return items.sort(byNewest)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const evalItemKey = (item: any): string => `${String(item?.node ?? '')}\0${String(item?.scenario ?? '')}`
|
|
219
|
-
|
|
220
|
-
function evalNeighbor(item: any, stateOf: (row: { node: string; scenario: string }) => string): EvalNeighbor {
|
|
221
|
-
return {
|
|
222
|
-
node: String(item.node),
|
|
223
|
-
scenario: String(item.scenario),
|
|
224
|
-
state: stateOf(item),
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// `sequence` is the whole measured population in list order; `stateOf` answers only for rows whose freshness
|
|
229
|
-
// was actually computed. On a focused build those are two different sets — the sequence spans the scope, the
|
|
230
|
-
// states cover the selected row and its window — which is exactly why the state is looked up rather than
|
|
231
|
-
// carried: a row this response does not render contributes its POSITION and nothing else.
|
|
232
|
-
export function boundedEvalNeighbors(
|
|
233
|
-
sequence: { node: string; scenario: string }[],
|
|
234
|
-
node: string,
|
|
235
|
-
scenario: string,
|
|
236
|
-
stateOf: (row: { node: string; scenario: string }) => string,
|
|
237
|
-
want = 5,
|
|
238
|
-
) {
|
|
239
|
-
const items = sequence
|
|
240
|
-
const key = `${node}\0${scenario}`
|
|
241
|
-
const index = items.findIndex((item) => evalItemKey(item) === key)
|
|
242
|
-
if (index < 0) return { prev: [], next: [], total: items.length, index: null, order: 'default' as const }
|
|
243
|
-
const before = index
|
|
244
|
-
const after = items.length - index - 1
|
|
245
|
-
const take = Math.min(want, before + after)
|
|
246
|
-
const nextN = Math.min(after, Math.max(Math.ceil(take / 2), take - before))
|
|
247
|
-
const prevN = Math.min(before, take - nextN)
|
|
248
|
-
return {
|
|
249
|
-
prev: items.slice(index - prevN, index).reverse().map((item) => evalNeighbor(item, stateOf)),
|
|
250
|
-
next: items.slice(index + 1, index + 1 + nextN).map((item) => evalNeighbor(item, stateOf)),
|
|
251
|
-
total: items.length,
|
|
252
|
-
index,
|
|
253
|
-
order: 'default' as const,
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function projectEvalDetail(
|
|
258
|
-
items: ReviewItem[],
|
|
259
|
-
historySource: ReviewItem[],
|
|
260
|
-
node: string,
|
|
261
|
-
scenario: string,
|
|
262
|
-
metadata: {
|
|
263
|
-
scope?: string | null
|
|
264
|
-
summary?: SessionEvals['summary']
|
|
265
|
-
evalRevision?: SessionEvals['evalRevision']
|
|
266
|
-
// the scope's whole measured sequence, when `items` deliberately holds only the rendered window
|
|
267
|
-
sequence?: { node: string; scenario: string }[]
|
|
268
|
-
} = {},
|
|
269
|
-
): EvalDetailReview {
|
|
270
|
-
const results = items.filter((item: any) => item.filterKind === EVAL_FILTER_KIND.RESULT)
|
|
271
|
-
const selected = results.find((item) => evalItemKey(item) === `${node}\0${scenario}`) ?? null
|
|
272
|
-
const history = historySource.filter((reading: any) => String(reading.scenario) === scenario)
|
|
273
|
-
const stateByKey = new Map(results.map((item: any) => [evalItemKey(item), String(item.state ?? evalReviewState(item))]))
|
|
274
|
-
const sequence = metadata.sequence
|
|
275
|
-
?? results.map((item: any) => ({ node: String(item.node), scenario: String(item.scenario) }))
|
|
276
|
-
const neighbors = boundedEvalNeighbors(sequence, node, scenario,
|
|
277
|
-
(row) => stateByKey.get(evalItemKey(row)) ?? 'empty')
|
|
278
|
-
const scope = metadata.scope ?? null
|
|
279
|
-
return {
|
|
280
|
-
scope,
|
|
281
|
-
selected,
|
|
282
|
-
history,
|
|
283
|
-
neighbors,
|
|
284
|
-
revision: revisionOf({ scope, selected, history, neighbors, summary: metadata.summary, evalRevision: metadata.evalRevision }),
|
|
285
|
-
...(metadata.summary ? { summary: metadata.summary } : {}),
|
|
286
|
-
...(metadata.evalRevision ? { evalRevision: metadata.evalRevision } : {}),
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// the measured population in list order, from the freshness-free rows: a filed reading leads (newest first),
|
|
291
|
-
// and the tie-breaks are the identity ones — the SAME comparison `byNewest` applies, over the only fields it
|
|
292
|
-
// actually reads. Blind rows never enter, exactly as the detail's own `results` filter excludes them.
|
|
293
|
-
export function measuredSequence(order: SessionEvalOrderRow[]): { node: string; scenario: string }[] {
|
|
294
|
-
return order.filter((row) => row.ts)
|
|
295
|
-
.sort((a, b) => String(b.ts ?? '').localeCompare(String(a.ts ?? ''))
|
|
296
|
-
|| a.node.localeCompare(b.node) || a.scenario.localeCompare(b.scenario))
|
|
297
|
-
.map((row) => ({ node: row.node, scenario: row.scenario }))
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// the nodes whose verdicts the response will publish: the selected row's, plus a window wide enough to
|
|
301
|
-
// contain any neighbour boundedEvalNeighbors can choose (it takes at most five, split around the index).
|
|
302
|
-
export function focusNodes(order: SessionEvalOrderRow[], node: string, scenario: string): string[] {
|
|
303
|
-
const sequence = measuredSequence(order)
|
|
304
|
-
const index = sequence.findIndex((row) => row.node === node && row.scenario === scenario)
|
|
305
|
-
if (index < 0) return [node]
|
|
306
|
-
return [...new Set([node, ...sequence.slice(Math.max(0, index - 6), index + 7).map((row) => row.node)])]
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export async function evalDetailReview(node: string, scenario: string, scope?: string | null): Promise<EvalDetailReview | null> {
|
|
310
|
-
if (scope) {
|
|
311
|
-
// A detail renders ONE row plus at most five neighbours, but owes the whole population's index/total.
|
|
312
|
-
// So name the window from the freshness-free sequence and let only those nodes pay the freshness pass;
|
|
313
|
-
// a build that finds a full cached model ignores the pick and answers from it instead.
|
|
314
|
-
const model = await buildSessionEvals(scope, (order) => focusNodes(order, node, scenario))
|
|
315
|
-
if (!model) return null
|
|
316
|
-
const sourceNode = model.nodes.find((candidate) => candidate.id === node)
|
|
317
|
-
return projectEvalDetail(scopedEvalReviewItems(model), sourceNode?.evals ?? [], node, scenario, {
|
|
318
|
-
scope,
|
|
319
|
-
summary: model.summary,
|
|
320
|
-
evalRevision: model.evalRevision,
|
|
321
|
-
...(model.order ? { sequence: measuredSequence(model.order) } : {}),
|
|
322
|
-
})
|
|
323
|
-
}
|
|
324
|
-
await getBoard()
|
|
325
|
-
const snapshot = readReviewSnapshot()
|
|
326
|
-
const sourceNode = snapshot.evalNodes.find((candidate) => candidate.id === node)
|
|
327
|
-
return projectEvalDetail(trunkEvalReviewItems(snapshot.evalNodes), sourceNode?.readings ?? [], node, scenario)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
async function timelineEvalReview(text: string, requestedPage: unknown) {
|
|
331
|
-
const node = readToken(text, 'node')
|
|
332
|
-
if (!node) return null
|
|
333
|
-
const [timeline, sessions] = await Promise.all([evalTimeline(node), listSessions()])
|
|
334
|
-
const measured = new Set(timeline.readings.map((reading) => reading.scenario))
|
|
335
|
-
const items = [
|
|
336
|
-
...timeline.scenarios.filter((scenario) => !measured.has(scenario.name)).map((scenario) => ({
|
|
337
|
-
...scenario,
|
|
338
|
-
scenario: scenario.name,
|
|
339
|
-
node,
|
|
340
|
-
filterKind: EVAL_FILTER_KIND.UNMEASURED,
|
|
341
|
-
})),
|
|
342
|
-
...timeline.readings.map((reading, index) => ({
|
|
343
|
-
...reading,
|
|
344
|
-
state: evalReviewState(reading),
|
|
345
|
-
node,
|
|
346
|
-
filterKind: EVAL_FILTER_KIND.RESULT,
|
|
347
|
-
filterKey: `${EVAL_FILTER_KIND.RESULT}:${index}`,
|
|
348
|
-
})),
|
|
349
|
-
...(timeline.dangling ?? []).map((track) => ({
|
|
350
|
-
...track,
|
|
351
|
-
node,
|
|
352
|
-
filterKind: EVAL_FILTER_KIND.DANGLING,
|
|
353
|
-
filterKey: `${EVAL_FILTER_KIND.DANGLING}:${track.threadId}`,
|
|
354
|
-
})),
|
|
355
|
-
]
|
|
356
|
-
const filtered = evalFilterModel(items, tokenFilterState(text, 'eval'), { sessions, defaultKind: 'all', defaultSection: '' })
|
|
357
|
-
return {
|
|
358
|
-
scope: null,
|
|
359
|
-
view: 'timeline',
|
|
360
|
-
node,
|
|
361
|
-
hasEvalFile: timeline.hasEvalFile,
|
|
362
|
-
gates: [],
|
|
363
|
-
unknown: 0,
|
|
364
|
-
...paginateReview(items, filtered.shown, filtered, requestedPage, {
|
|
365
|
-
domain: 'evals', view: 'timeline', node, timeline, sessions: sessions.map((session) => session.id),
|
|
366
|
-
}),
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export async function evalsReview(query: string | undefined, requestedPage: unknown, options: { view?: string } = {}) {
|
|
371
|
-
const text = String(query ?? '').trim() || EVAL_QUERY_DEFAULT
|
|
372
|
-
if (options.view === 'timeline') return timelineEvalReview(text, requestedPage)
|
|
373
|
-
const scope = readToken(text, 'scope') || null
|
|
374
|
-
if (scope) {
|
|
375
|
-
const model = await buildSessionEvals(scope)
|
|
376
|
-
if (!model) return null
|
|
377
|
-
const items = scopedEvalReviewItems(model)
|
|
378
|
-
const sessions = await listSessions()
|
|
379
|
-
const filtered = evalFilterModel(items, tokenFilterState(text, 'eval'), { sessions, defaultKind: 'all', defaultSection: '' })
|
|
380
|
-
return {
|
|
381
|
-
scope,
|
|
382
|
-
gates: model.gates ?? [],
|
|
383
|
-
unknown: model.nodes.reduce((count, node) => count + (node.unknownCoverage?.length ?? 0), 0),
|
|
384
|
-
summary: model.summary,
|
|
385
|
-
evalRevision: model.evalRevision,
|
|
386
|
-
impact: model.impact,
|
|
387
|
-
...paginateReview(items, filtered.shown, filtered, requestedPage, {
|
|
388
|
-
domain: 'evals', scope, items, gates: model.gates ?? [], summary: model.summary,
|
|
389
|
-
evalRevision: model.evalRevision, impact: model.impact, sessions: sessions.map((session) => session.id),
|
|
390
|
-
}),
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
if (!hasReviewSnapshot()) await getBoard()
|
|
394
|
-
const sessions = await listSessions()
|
|
395
|
-
const items = trunkEvalReviewItems(readReviewSnapshot().evalNodes)
|
|
396
|
-
const filtered = evalFilterModel(items, tokenFilterState(text, 'eval'), { sessions, defaultKind: 'all', defaultSection: '' })
|
|
397
|
-
return {
|
|
398
|
-
scope: null,
|
|
399
|
-
gates: [],
|
|
400
|
-
unknown: 0,
|
|
401
|
-
...paginateReview(items, filtered.shown, filtered, requestedPage, {
|
|
402
|
-
domain: 'evals', items, sessions: sessions.map((session) => session.id),
|
|
403
|
-
}),
|
|
404
|
-
}
|
|
405
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
// tmux presence is the primitive we actually depend on, so probe THAT rather than assuming by platform:
|
|
4
|
-
// this also catches a bare POSIX box that simply hasn't installed tmux, not only native Windows.
|
|
5
|
-
export function hasTmux(): boolean {
|
|
6
|
-
try {
|
|
7
|
-
return spawnSync('tmux', ['-V'], { stdio: 'ignore' }).status === 0
|
|
8
|
-
} catch {
|
|
9
|
-
return false
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Pure so it is unit-testable without spawning or exiting: null = runtime OK; otherwise the stderr lines.
|
|
14
|
-
// The pointer branches on platform because the honest repair differs — WSL2 on Windows (no POSIX analog at
|
|
15
|
-
// all), install-tmux on a POSIX host that merely lacks it.
|
|
16
|
-
export function sessionRuntimeBlock(env: { hasTmux: boolean; platform: string }): string[] | null {
|
|
17
|
-
if (env.hasTmux) return null
|
|
18
|
-
const lines = ['spex: the session runtime needs a POSIX host (tmux, bash, and unix-domain sockets).']
|
|
19
|
-
if (env.platform === 'win32') {
|
|
20
|
-
lines.push('Native Windows has no analog for these — run SpexCode under WSL2 (a real Linux kernel):')
|
|
21
|
-
lines.push(' wsl --install # then, in the Ubuntu shell: nvm install 22 && npm i -g spexcode')
|
|
22
|
-
} else {
|
|
23
|
-
lines.push('tmux is not on PATH — install it and retry (e.g. `apt install tmux`, `brew install tmux`).')
|
|
24
|
-
}
|
|
25
|
-
return lines
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Called at the top of the session-launching command path (`spex serve`). Exits 69 (EX_UNAVAILABLE: a
|
|
29
|
-
// required support program does not exist) — a distinct, honest code, not a swallowed error or a stacktrace.
|
|
30
|
-
export function assertSessionRuntime(): void {
|
|
31
|
-
const block = sessionRuntimeBlock({ hasTmux: hasTmux(), platform: process.platform })
|
|
32
|
-
if (!block) return
|
|
33
|
-
for (const line of block) console.error(line)
|
|
34
|
-
process.exit(69)
|
|
35
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { spawn } from 'node:child_process'
|
|
3
|
-
import { closeSync, mkdirSync, openSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
-
import { dirname, join } from 'node:path'
|
|
5
|
-
import { repoRoot } from './git.js'
|
|
6
|
-
import { readJsonConfig, runtimeRoot } from './layout.js'
|
|
7
|
-
import { processStartToken, writeDetachedRuntimeReceipt, type ProcessIdentity } from './process-identity.js'
|
|
8
|
-
|
|
9
|
-
export type BackendInstanceRecord = {
|
|
10
|
-
version: 1
|
|
11
|
-
instanceId: string
|
|
12
|
-
pid: number
|
|
13
|
-
startToken: string
|
|
14
|
-
projectRoot: string
|
|
15
|
-
startedAt: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const backendInstancesDir = () => join(runtimeRoot(), 'backend-instances')
|
|
19
|
-
const backendInstancePath = (instanceId: string) => join(backendInstancesDir(), `${instanceId}.json`)
|
|
20
|
-
|
|
21
|
-
const atomicJson = (path: string, value: unknown) => {
|
|
22
|
-
mkdirSync(dirname(path), { recursive: true })
|
|
23
|
-
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`
|
|
24
|
-
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 })
|
|
25
|
-
renameSync(tmp, path)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function readBackendInstanceRecords(root = repoRoot()): BackendInstanceRecord[] {
|
|
29
|
-
let names: string[]
|
|
30
|
-
try { names = readdirSync(backendInstancesDir()) } catch { return [] }
|
|
31
|
-
return names.flatMap((name) => {
|
|
32
|
-
try {
|
|
33
|
-
const rec = readJsonConfig(join(backendInstancesDir(), name)) as BackendInstanceRecord
|
|
34
|
-
return rec?.version === 1 && rec.projectRoot === root ? [rec] : []
|
|
35
|
-
} catch { return [] }
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function registerBackendInstance(instanceId: string, pid = process.pid, root = repoRoot()): BackendInstanceRecord {
|
|
40
|
-
const startToken = processStartToken(pid)
|
|
41
|
-
if (!startToken) throw new Error(`cannot identify backend supervisor PID ${pid}`)
|
|
42
|
-
const record: BackendInstanceRecord = { version: 1, instanceId, pid, startToken, projectRoot: root, startedAt: new Date().toISOString() }
|
|
43
|
-
atomicJson(backendInstancePath(instanceId), record)
|
|
44
|
-
return record
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function unregisterBackendInstance(instanceId: string, pid = process.pid): void {
|
|
48
|
-
try {
|
|
49
|
-
const record = readJsonConfig(backendInstancePath(instanceId)) as BackendInstanceRecord
|
|
50
|
-
if (record.instanceId === instanceId && record.pid === pid && record.startToken === processStartToken(pid)) rmSync(backendInstancePath(instanceId), { force: true })
|
|
51
|
-
} catch { /* not ours / already removed */ }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function spawnDetachedRuntime(opts: {
|
|
55
|
-
cwd: string
|
|
56
|
-
logFile: string
|
|
57
|
-
pidFile: string
|
|
58
|
-
receiptFile: string
|
|
59
|
-
command: string
|
|
60
|
-
args: string[]
|
|
61
|
-
env?: NodeJS.ProcessEnv
|
|
62
|
-
}): ProcessIdentity {
|
|
63
|
-
mkdirSync(dirname(opts.logFile), { recursive: true })
|
|
64
|
-
mkdirSync(dirname(opts.pidFile), { recursive: true })
|
|
65
|
-
const log = openSync(opts.logFile, 'a', 0o600)
|
|
66
|
-
let child: ReturnType<typeof spawn>
|
|
67
|
-
try {
|
|
68
|
-
child = spawn(opts.command, opts.args, {
|
|
69
|
-
cwd: opts.cwd,
|
|
70
|
-
env: opts.env ?? process.env,
|
|
71
|
-
detached: true,
|
|
72
|
-
stdio: ['ignore', log, log],
|
|
73
|
-
})
|
|
74
|
-
} finally { closeSync(log) }
|
|
75
|
-
child.once('error', () => {})
|
|
76
|
-
if (!child.pid) throw new Error(`could not spawn detached shared runtime: ${opts.command}`)
|
|
77
|
-
child.unref()
|
|
78
|
-
try {
|
|
79
|
-
const identity = writeDetachedRuntimeReceipt(child.pid, opts.receiptFile)
|
|
80
|
-
writeFileSync(opts.pidFile, `${child.pid}\n`, { mode: 0o600 })
|
|
81
|
-
return { pid: identity.pid, startToken: identity.startToken }
|
|
82
|
-
} catch (error) {
|
|
83
|
-
try { child.kill('SIGTERM') } catch { /* exact just-spawned child already exited */ }
|
|
84
|
-
rmSync(opts.pidFile, { force: true })
|
|
85
|
-
rmSync(opts.receiptFile, { force: true })
|
|
86
|
-
throw error
|
|
87
|
-
}
|
|
88
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// throwaway benchmark harness for spec-search — drives the REAL `spex spec search --json` over the holdout
|
|
2
|
-
// cases and reports recall@1, recall@3, MRR. The cases live in the node's eval.md.
|
|
3
|
-
//
|
|
4
|
-
// Labels are node LEAF names, matched with the same de-collision rule the loader applies (specs.ts reId):
|
|
5
|
-
// a returned id matches a label if it IS the label or ends with `_<label>` — so a bare leaf keeps matching
|
|
6
|
-
// after collision-qualification renames it (e.g. a colliding `probe` leaf → `a_probe`). A label may
|
|
7
|
-
// also be written pre-qualified to pin one collision branch.
|
|
8
|
-
import { execFileSync } from 'node:child_process'
|
|
9
|
-
import { fileURLToPath } from 'node:url'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
|
|
12
|
-
const BIN = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'spex.mjs')
|
|
13
|
-
|
|
14
|
-
const CASES = [
|
|
15
|
-
['exit-cleanup', "does /exit remove the session's worktree and tmux, or just orphan them?", ['session-console']],
|
|
16
|
-
['owner-at-edit', 'how does an agent learn which spec governs a file it just edited?', ['spec-of-file']],
|
|
17
|
-
['main-block', 'what stops an agent from committing or merging straight into main?', ['main-guard']],
|
|
18
|
-
['main-escape', 'the escape hatch that lets seeding run on the main branch', ['main-guard']],
|
|
19
|
-
['inter-agent-msg', 'how do two running agent sessions send messages to each other?', ['agent-reply-channel', 'comms']],
|
|
20
|
-
['search-hidden-node', 'keyboard shortcut to find a node hidden inside a collapsed subtree', ['keyboard-nav']],
|
|
21
|
-
['session-order', 'how is the order of sessions in the session list decided?', ['session-console']],
|
|
22
|
-
['node-status', 'what makes a node show as pending vs active vs merged vs drift?', ['spec-node-states']],
|
|
23
|
-
['dashboard-backend', 'how does the dashboard reach the backend API and on which port?', ['api-endpoint']],
|
|
24
|
-
['loss-measured', "how is a node's loss measured and its scenarios scored?", ['eval-core']],
|
|
25
|
-
['launch-injection', "what context gets injected into a freshly launched agent's prompt?", ['injected-context']],
|
|
26
|
-
['read-before-code', 'the one-shot nudge that makes an agent read its spec before touching code', ['spec-first']],
|
|
27
|
-
['hot-reload', 'zero-downtime backend reload without dropping connections', ['supervisor']],
|
|
28
|
-
['many-owners', 'can several specs own the same code file, and what happens if too many do?', ['governed-related']],
|
|
29
|
-
['active-spec-search', 'an injected sub-agent that searches specs for the agent, the spec analog of Explore', ['spec-search']],
|
|
30
|
-
['declare-done', 'how does a worker declare it is done', ['state']],
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
// de-collision-aware label match: exact id, or the id's trailing `_`-suffix is the label.
|
|
34
|
-
const matches = (id, label) => id === label || id.endsWith('_' + label)
|
|
35
|
-
|
|
36
|
-
let r1 = 0, r3 = 0, mrr = 0
|
|
37
|
-
const rows = []
|
|
38
|
-
for (const [name, query, expect] of CASES) {
|
|
39
|
-
const out = execFileSync('node', [BIN, 'spec', 'search', query, '--json', '--limit', '10'], { encoding: 'utf8' })
|
|
40
|
-
const results = JSON.parse(out)
|
|
41
|
-
const ids = results.map((x) => x.id)
|
|
42
|
-
const rank = ids.findIndex((id) => expect.some((label) => matches(id, label))) + 1 // 1-based; 0 = not found
|
|
43
|
-
const hit1 = rank === 1
|
|
44
|
-
const hit3 = rank >= 1 && rank <= 3
|
|
45
|
-
if (hit1) r1++
|
|
46
|
-
if (hit3) r3++
|
|
47
|
-
if (rank >= 1) mrr += 1 / rank
|
|
48
|
-
rows.push({ name, rank: rank || '—', top: ids.slice(0, 3).join(', '), expect: expect.join('|') })
|
|
49
|
-
}
|
|
50
|
-
const n = CASES.length
|
|
51
|
-
for (const row of rows) {
|
|
52
|
-
const mark = row.rank === 1 ? '✓1' : (typeof row.rank === 'number' && row.rank <= 3 ? '·3' : (row.rank === '—' ? '✗ ' : `·${row.rank}`))
|
|
53
|
-
console.log(`${mark} ${row.name.padEnd(20)} want=${row.expect.padEnd(22)} rank=${String(row.rank).padStart(2)} top3: ${row.top}`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// CJK-positive: the corpus is mostly English but a few nodes carry Chinese prose — the root `spexcode`
|
|
57
|
-
// node's body repeats 节点 throughout. A per-CJK-character tokenizer must let a Chinese content word reach
|
|
58
|
-
// that node; `spex spec search "节点"` MUST return `spexcode` in results (the bug: the old tokenizer split on
|
|
59
|
-
// [^a-z0-9]+ and discarded all CJK, so every Chinese query dead-ended at zero results).
|
|
60
|
-
const cjkPos = JSON.parse(execFileSync('node', [BIN, 'spec', 'search', '节点', '--json', '--limit', '10'], { encoding: 'utf8' }))
|
|
61
|
-
const cjkPosRank = cjkPos.map((x) => x.id).findIndex((id) => matches(id, 'spexcode')) + 1
|
|
62
|
-
const cjkPosPass = cjkPosRank >= 1
|
|
63
|
-
console.log(`${cjkPosPass ? `✓${cjkPosRank}` : '✗ '} cjk-positive want=spexcode(节点) rank=${cjkPosRank || '—'} top3: ${cjkPos.slice(0, 3).map((x) => x.id).join(', ')}`)
|
|
64
|
-
|
|
65
|
-
// zero-result fail-loud regression: a CJK query whose characters appear NOWHERE in the corpus (会/话) returns
|
|
66
|
-
// nothing, and the zero-result message must still carry the corpus-is-English translate-and-retry fact
|
|
67
|
-
// (fail-loud) plus the browse-all next step — CJK support does not suppress the honest zero-result route.
|
|
68
|
-
// (No nearest titles: a CJK query has nothing to be lexically near.)
|
|
69
|
-
const cjk = execFileSync('node', [BIN, 'spec', 'search', '会话'], { encoding: 'utf8' })
|
|
70
|
-
const cjkPass = cjk.includes('corpus is English') && cjk.includes('spex graph')
|
|
71
|
-
console.log(`${cjkPass ? '✓ ' : '✗ '} cjk-zero-result want=corpus-is-English + spex-tree ${cjkPass ? 'both present' : 'MISSING: ' + cjk.trim()}`)
|
|
72
|
-
|
|
73
|
-
// zero-result typo routing: an English typo that matches nothing must surface the nearest node titles
|
|
74
|
-
// (per-word edit distance) plus the browse-all next step, so a typo routes forward instead of dead-ending.
|
|
75
|
-
const typo = execFileSync('node', [BIN, 'spec', 'search', 'kyeboard'], { encoding: 'utf8' })
|
|
76
|
-
const typoPass = typo.includes('nearest titles') && typo.includes('keyboard-nav') && typo.includes('spex graph')
|
|
77
|
-
console.log(`${typoPass ? '✓ ' : '✗ '} typo-zero-result want=nearest-titles(keyboard-nav) + spex-tree ${typoPass ? 'both present' : 'MISSING: ' + typo.trim()}`)
|
|
78
|
-
|
|
79
|
-
console.log('—'.repeat(72))
|
|
80
|
-
console.log(`recall@1 = ${r1}/${n} = ${(r1 / n).toFixed(3)} recall@3 = ${r3}/${n} = ${(r3 / n).toFixed(3)} MRR = ${(mrr / n).toFixed(3)} cjk-positive = ${cjkPosPass ? 'PASS' : 'FAIL'} cjk-hint = ${cjkPass ? 'PASS' : 'FAIL'} typo-route = ${typoPass ? 'PASS' : 'FAIL'}`)
|
package/spec-cli/src/search.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { loadSpecsLite } from './specs.js'
|
|
2
|
-
import { rankDocs, type RankInput } from './ranker.js'
|
|
3
|
-
|
|
4
|
-
export type SearchResult = { id: string; title: string; path: string; score: number; snippet: string }
|
|
5
|
-
export type SearchStats = { nodes: number; tokens: number; ms: number }
|
|
6
|
-
|
|
7
|
-
// zero-result fallback: nearest node titles by per-word edit distance over title+id — the lightweight
|
|
8
|
-
// string distance that routes a typo'd query to a next step. Each query word takes its best normalised
|
|
9
|
-
// Levenshtein similarity against any title/id word (≥0.5, so unrelated words don't accumulate), then the
|
|
10
|
-
// words sum. Same loadSpecsLite read as the ranker; deliberately NOT part of rankDocs scoring (this
|
|
11
|
-
// tolerates typos, the ranker must not).
|
|
12
|
-
export function nearestTitles(query: string, n = 3): { id: string; title: string }[] {
|
|
13
|
-
const words = (s: string) => s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1)
|
|
14
|
-
const lev = (a: string, b: string) => {
|
|
15
|
-
const dp = Array.from({ length: b.length + 1 }, (_, i) => i)
|
|
16
|
-
for (let i = 1; i <= a.length; i++) {
|
|
17
|
-
let prev = dp[0]
|
|
18
|
-
dp[0] = i
|
|
19
|
-
for (let j = 1; j <= b.length; j++) {
|
|
20
|
-
const cur = dp[j]
|
|
21
|
-
dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
22
|
-
prev = cur
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return dp[b.length]
|
|
26
|
-
}
|
|
27
|
-
const qws = words(query)
|
|
28
|
-
if (!qws.length) return [] // nothing to be "near" (e.g. a pure-CJK query) — the caller still points at spex graph
|
|
29
|
-
return loadSpecsLite()
|
|
30
|
-
.map((s) => {
|
|
31
|
-
const tws = [...new Set(words(`${s.title} ${s.id}`))]
|
|
32
|
-
let score = 0
|
|
33
|
-
for (const q of qws) {
|
|
34
|
-
let best = 0
|
|
35
|
-
for (const t of tws) best = Math.max(best, 1 - lev(q, t) / Math.max(q.length, t.length))
|
|
36
|
-
if (best >= 0.5) score += best
|
|
37
|
-
}
|
|
38
|
-
return { id: s.id, title: s.title, score }
|
|
39
|
-
})
|
|
40
|
-
.filter((s) => s.score > 0)
|
|
41
|
-
.sort((a, b) => b.score - a.score)
|
|
42
|
-
.slice(0, n)
|
|
43
|
-
.map(({ id, title }) => ({ id, title }))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function searchSpecs(query: string, opts: { limit?: number; onStats?: (s: SearchStats) => void } = {}): Promise<SearchResult[]> {
|
|
47
|
-
const t0 = performance.now()
|
|
48
|
-
// pre-sort by (shorter id, then id): rankDocs sorts stably, so for equal-scored nodes this IS the tiebreak.
|
|
49
|
-
const nodes = loadSpecsLite()
|
|
50
|
-
.slice()
|
|
51
|
-
.sort((a, b) => a.id.length - b.id.length || a.id.localeCompare(b.id))
|
|
52
|
-
const inputs: RankInput<(typeof nodes)[number]>[] = nodes.map((s) => ({
|
|
53
|
-
ref: s, name: `${s.title} ${s.id}`, desc: s.desc, body: s.body,
|
|
54
|
-
}))
|
|
55
|
-
const out = rankDocs(query, inputs, { limit: opts.limit }).map((r) => ({
|
|
56
|
-
id: r.ref.id, title: r.ref.title, path: r.ref.path, score: r.score, snippet: r.snippet,
|
|
57
|
-
}))
|
|
58
|
-
if (opts.onStats) {
|
|
59
|
-
const tokens = inputs.reduce((a, i) => a + `${i.name} ${i.desc} ${i.body}`.split(/[^a-z0-9]+/i).filter(Boolean).length, 0)
|
|
60
|
-
opts.onStats({ nodes: inputs.length, tokens, ms: performance.now() - t0 })
|
|
61
|
-
}
|
|
62
|
-
return out
|
|
63
|
-
}
|