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,308 @@
|
|
|
1
|
+
// SO_REUSEPORT is ENOTSUP on macOS/Node, so the supervisor owns the public port as a raw-TCP proxy and
|
|
2
|
+
// runs the Hono server as a child on a private port (raw piping carries WS upgrades too).
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import http from 'node:http';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { stat, readdir } from 'node:fs/promises';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { installProcessGuards } from '@spexcode/spec-core';
|
|
12
|
+
import { listenOrExit } from './listen.js';
|
|
13
|
+
import { resolvePublicConfig, startGateway, resolveDistDir } from './gateway.js';
|
|
14
|
+
import { publishEndpoint, dropOwnEndpoint } from './host.js';
|
|
15
|
+
import { repoRoot as servedRepoRoot } from '@spexcode/spec-core';
|
|
16
|
+
import { resolveProjectIdentity } from '@spexcode/spec-core';
|
|
17
|
+
import { startResourceMonitor } from './host-resources.js';
|
|
18
|
+
import { registerBackendInstance, unregisterBackendInstance } from './runtime-ownership.js';
|
|
19
|
+
import { sessionIdentityEnvVars } from './harness.js';
|
|
20
|
+
import { serverEntrypointArgs } from './tsx-bin.js';
|
|
21
|
+
// the supervisor OWNS the public port, so it must outlive any transient throw: an uncaught error here is
|
|
22
|
+
// logged and survived, never an exit that closes the port (and the tmux session) and takes the frontend down.
|
|
23
|
+
installProcessGuards();
|
|
24
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const packageRoot = join(here, '..');
|
|
26
|
+
const sourceRoot = join(packageRoot, 'src');
|
|
27
|
+
const workspaceRoot = existsSync(sourceRoot) ? join(packageRoot, '..') : null;
|
|
28
|
+
const entryArgs = serverEntrypointArgs(packageRoot, here);
|
|
29
|
+
const publicPort = Number(process.env.PORT || 8787);
|
|
30
|
+
const projectRoot = servedRepoRoot(); // the actual git tree whose source/spec/config the child serves
|
|
31
|
+
// @@@ public mode ([[public-mode]]) - with `spex serve --public`, the supervisor is NOT the internet face:
|
|
32
|
+
// the gateway is. The raw-TCP proxy retreats to a loopback internal port (the trusted boundary local agents
|
|
33
|
+
// reach) and the password-gated TLS gateway takes the public port, proxying /api + WS back to that loopback
|
|
34
|
+
// port. Off → unchanged: the proxy itself owns the public port and SPEXCODE_API_URL points there.
|
|
35
|
+
const publicCfg = resolvePublicConfig(projectRoot);
|
|
36
|
+
// the port the raw-TCP proxy actually binds: a private loopback port in public mode, the public port otherwise.
|
|
37
|
+
const proxyPort = publicCfg ? await freePort() : publicPort;
|
|
38
|
+
// what launched agents inherit as their `spex` endpoint — ALWAYS the loopback proxy, so local agents never
|
|
39
|
+
// meet the password gate (loopback is the trust boundary). Equals the public port when public mode is off.
|
|
40
|
+
const childApiBase = `http://127.0.0.1:${proxyPort}`;
|
|
41
|
+
// An installed package only has compiled JavaScript. In a source workspace keep the development loop honest:
|
|
42
|
+
// observe the source closure, compile it, then make the supervisor's normal zero-downtime swap. A source edit
|
|
43
|
+
// must never cause a restart that still runs the old dist.
|
|
44
|
+
const watchRoots = workspaceRoot
|
|
45
|
+
? [sourceRoot, join(workspaceRoot, 'spec-forge', 'src'), join(workspaceRoot, 'spec-eval', 'src'), join(workspaceRoot, 'packages', 'spec-core', 'src')]
|
|
46
|
+
: [here];
|
|
47
|
+
function buildWorkspace() {
|
|
48
|
+
if (!workspaceRoot)
|
|
49
|
+
return true;
|
|
50
|
+
console.log('[supervisor] building workspace artifacts');
|
|
51
|
+
const result = spawnSync('npm', ['run', 'build'], { cwd: workspaceRoot, stdio: 'inherit' });
|
|
52
|
+
if (result.status === 0)
|
|
53
|
+
return true;
|
|
54
|
+
console.error('[supervisor] workspace build failed — keeping current backend');
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// @@@ instance identity - one id for this serve's whole lifetime, minted at supervisor start and handed to
|
|
58
|
+
// every child via env, so the endpoint record and the live backend answer with the SAME identity across
|
|
59
|
+
// zero-downtime reloads. The host gateway ([[host-gateway]]) validates a record by comparing this id (and the
|
|
60
|
+
// served root) against the live /api/instance answer — a recycled port serving a DIFFERENT project or a
|
|
61
|
+
// different serve generation fails the match and is treated as offline, never proxied to.
|
|
62
|
+
const instanceId = randomUUID();
|
|
63
|
+
// A backend is a project control plane, never the session that happened to invoke `spex serve`. Strip every
|
|
64
|
+
// adapter identity before any hot child is spawned; otherwise all backend work is falsely charged to that
|
|
65
|
+
// caller long after its session ends.
|
|
66
|
+
for (const key of sessionIdentityEnvVars())
|
|
67
|
+
delete process.env[key];
|
|
68
|
+
process.env.SPEXCODE_PROJECT_ROOT = projectRoot;
|
|
69
|
+
process.env.SPEXCODE_INSTANCE_ID = instanceId;
|
|
70
|
+
registerBackendInstance(instanceId, process.pid, projectRoot);
|
|
71
|
+
let current = null; // which internal port new proxy connections forward to
|
|
72
|
+
let reloading = false; // single-flight guard for reload()
|
|
73
|
+
let pending = false; // a code change arrived mid-reload → reload again when done
|
|
74
|
+
// grab an ephemeral port by binding :0, then release it for the child to claim (negligible rebind race).
|
|
75
|
+
function freePort() {
|
|
76
|
+
return new Promise((res, rej) => {
|
|
77
|
+
const s = net.createServer();
|
|
78
|
+
s.once('error', rej);
|
|
79
|
+
s.listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => res(p)); });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// poll GET /health until 200; ~15s budget covers a cold Node + Hono start. False → keep old.
|
|
83
|
+
function waitHealthy(port, tries = 150) {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const retry = (left) => { if (left <= 1)
|
|
86
|
+
resolve(false);
|
|
87
|
+
else
|
|
88
|
+
setTimeout(() => attempt(left - 1), 100); };
|
|
89
|
+
const attempt = (left) => {
|
|
90
|
+
const req = http.get({ host: '127.0.0.1', port, path: '/health', timeout: 1000 }, (r) => {
|
|
91
|
+
r.resume();
|
|
92
|
+
if (r.statusCode === 200)
|
|
93
|
+
resolve(true);
|
|
94
|
+
else
|
|
95
|
+
retry(left);
|
|
96
|
+
});
|
|
97
|
+
req.on('error', () => retry(left));
|
|
98
|
+
req.on('timeout', () => { req.destroy(); retry(left); });
|
|
99
|
+
};
|
|
100
|
+
attempt(tries);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// boot a fresh backend child on a free internal port and wait until it's serving.
|
|
104
|
+
async function boot() {
|
|
105
|
+
const port = await freePort();
|
|
106
|
+
// PORT pins the child's PRIVATE bind port; SPEXCODE_API_URL pins everything the child SPAWNS (launched
|
|
107
|
+
// sessions + their hooks) at the PUBLIC port, so a launched agent's own `spex` reaches the stable proxy
|
|
108
|
+
// and never inherits this ephemeral, soon-retired port. ALWAYS childApiBase, never the ambient
|
|
109
|
+
// process.env.SPEXCODE_API_URL: the env this serve itself inherited may carry ANOTHER project's backend
|
|
110
|
+
// (the exact misroute [[remote-client]]'s ladder exists to kill), and a worker's env is its routing
|
|
111
|
+
// LIFELINE — it must be a deterministic backend-injected fact, not an inheritance gamble.
|
|
112
|
+
const child = spawn(process.execPath, entryArgs, { stdio: 'inherit', env: { ...process.env, PORT: String(port), SPEXCODE_API_URL: childApiBase, SPEXCODE_INSTANCE_ID: instanceId } });
|
|
113
|
+
// if the ACTIVE backend dies unexpectedly (crash, OOM), restart it so the public port keeps serving.
|
|
114
|
+
// Planned retirement sets current to the NEW child first, so the old child's exit fails this identity
|
|
115
|
+
// check and is ignored. boot()'s ~5s health budget rate-limits any crash loop.
|
|
116
|
+
child.on('exit', (code, sig) => {
|
|
117
|
+
if (current?.child === child) {
|
|
118
|
+
console.error(`[supervisor] active backend exited (${code ?? sig}) — restarting`);
|
|
119
|
+
current = null;
|
|
120
|
+
void reload('crash');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (await waitHealthy(port))
|
|
124
|
+
return { port, child };
|
|
125
|
+
try {
|
|
126
|
+
child.kill('SIGKILL');
|
|
127
|
+
}
|
|
128
|
+
catch { /* already gone */ }
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// boot → health-gate → atomic flip → drain old. The flip is a single assignment; the old child is killed
|
|
132
|
+
// only after a drain delay, so a connection mid-flip is never refused.
|
|
133
|
+
async function reload(reason) {
|
|
134
|
+
if (reloading) {
|
|
135
|
+
pending = true;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
reloading = true;
|
|
139
|
+
try {
|
|
140
|
+
do {
|
|
141
|
+
pending = false;
|
|
142
|
+
if (!buildWorkspace())
|
|
143
|
+
break;
|
|
144
|
+
const next = await boot();
|
|
145
|
+
if (!next) {
|
|
146
|
+
console.error(`[supervisor] new backend failed health check (${reason}) — keeping current`);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
const old = current;
|
|
150
|
+
current = next; // atomic flip: new connections now route to `next`
|
|
151
|
+
console.log(`[supervisor] reloaded (${reason}) → backend :${next.port}`);
|
|
152
|
+
if (old)
|
|
153
|
+
setTimeout(() => { try {
|
|
154
|
+
old.child.kill('SIGTERM');
|
|
155
|
+
}
|
|
156
|
+
catch { /* already gone */ } }, 500);
|
|
157
|
+
} while (pending);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
reloading = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// the public port: a raw byte pipe to the current backend. Works for HTTP and WS upgrades alike.
|
|
164
|
+
// @@@ connection reaping ([[spec-cli]]) - a raw TCP proxy must tear down the PAIR when EITHER end goes, or it
|
|
165
|
+
// leaks the still-open half. The wedge that started the mass-restore incident was exactly this: every
|
|
166
|
+
// client-side timeout-kill left the client's UPSTREAM socket half-open to the child (the old handler bailed
|
|
167
|
+
// only on `error`, so a clean FIN / a silent drop never reaped the upstream) — 135 leaked conns piled on the
|
|
168
|
+
// child and it looked dead. So a close on either side destroys BOTH (idempotent). The abandoned-but-silent
|
|
169
|
+
// case (no FIN/RST ever arrives) is reaped from the CHILD instead — its socket-level reaper (reaper.ts)
|
|
170
|
+
// destroys an idle/stalled socket, whose close then propagates here — so an active WS/SSE (not idle
|
|
171
|
+
// keep-alive) is never mistaken for abandoned and cut.
|
|
172
|
+
const proxy = net.createServer((client) => {
|
|
173
|
+
const target = current;
|
|
174
|
+
if (!target) {
|
|
175
|
+
client.destroy();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const up = net.connect(target.port, '127.0.0.1');
|
|
179
|
+
client.setNoDelay(true);
|
|
180
|
+
up.setNoDelay(true); // proxy a request promptly — don't let Nagle add latency
|
|
181
|
+
const bail = () => { client.destroy(); up.destroy(); };
|
|
182
|
+
client.on('error', bail);
|
|
183
|
+
up.on('error', bail);
|
|
184
|
+
client.once('close', () => up.destroy()); // client abandoned → reap its upstream (THE leak that wedged :8787); nothing left to flush to a gone client
|
|
185
|
+
// upstream gone → drop the client half, but ONLY force it when the close was ABNORMAL: on a normal FIN,
|
|
186
|
+
// `up.pipe(client)` has already called `client.end()` (writableEnded), so let the client flush the last of a
|
|
187
|
+
// large response (e.g. /api/graph) rather than truncate it with a destroy; a crash/half-open close (no prior
|
|
188
|
+
// end) still gets reaped.
|
|
189
|
+
up.once('close', () => { if (!client.writableEnded)
|
|
190
|
+
client.destroy(); });
|
|
191
|
+
client.pipe(up);
|
|
192
|
+
up.pipe(client);
|
|
193
|
+
});
|
|
194
|
+
// @@@ endpoint record - this project's live backend endpoint, published into the per-project runtime tier
|
|
195
|
+
// (~/.spexcode/projects/<enc>/backend.json) only AFTER the public bind succeeds. It's what lets a bare
|
|
196
|
+
// `spex` run from this project's tree find ITS OWN backend instead of an env URL inherited from another
|
|
197
|
+
// project's ([[remote-client]]'s resolution ladder), and what the host gateway ([[host-gateway]]) reconciles
|
|
198
|
+
// its project list from. The write is ATOMIC (tmp + rename) and carries the serve's actual git toplevel +
|
|
199
|
+
// resolved identity — so a reader can VALIDATE the record against the live /api/instance answer,
|
|
200
|
+
// never just trust a URL. Readers health-probe before trusting, so a crashed serve leaves at worst a dead
|
|
201
|
+
// record that is ignored — never followed. The recorded URL is the LOOPBACK face local agents reach (equals
|
|
202
|
+
// the public port when public mode is off), never the password-gated gateway. A linked worktree therefore
|
|
203
|
+
// gets its own encoded slot and can never overwrite the main checkout's endpoint.
|
|
204
|
+
function recordEndpoint(url) {
|
|
205
|
+
try {
|
|
206
|
+
publishEndpoint({
|
|
207
|
+
version: 2,
|
|
208
|
+
url,
|
|
209
|
+
pid: process.pid,
|
|
210
|
+
instanceId,
|
|
211
|
+
root: projectRoot,
|
|
212
|
+
identity: resolveProjectIdentity(projectRoot, projectRoot),
|
|
213
|
+
startedAt: new Date().toISOString(),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
console.error(`[supervisor] could not record the backend endpoint (${e.message}) — cwd-based \`spex\` discovery won't find this backend`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// best-effort removal on a clean stop, only if the record is OURS — matched by instanceId, so a newer serve
|
|
221
|
+
// that already overwrote the record (or another project's) is never deleted by a retiring one.
|
|
222
|
+
function dropEndpoint() {
|
|
223
|
+
try {
|
|
224
|
+
dropOwnEndpoint(instanceId, projectRoot);
|
|
225
|
+
}
|
|
226
|
+
catch { /* not ours / already gone */ }
|
|
227
|
+
}
|
|
228
|
+
const shutdown = () => { dropEndpoint(); unregisterBackendInstance(instanceId); try {
|
|
229
|
+
current?.child.kill('SIGTERM');
|
|
230
|
+
}
|
|
231
|
+
catch { /* */ } process.exit(0); };
|
|
232
|
+
process.on('SIGINT', shutdown);
|
|
233
|
+
process.on('SIGTERM', shutdown);
|
|
234
|
+
const first = await boot();
|
|
235
|
+
if (!first) {
|
|
236
|
+
console.error('[supervisor] initial backend failed to start');
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
current = first;
|
|
240
|
+
// reap the booted child if a port bind fails, so a "can't own my port → exit" never leaves a zombie child.
|
|
241
|
+
// SIGTERM lets the direct Node child release its backend port before the supervisor exits.
|
|
242
|
+
const reapChild = () => { unregisterBackendInstance(instanceId); try {
|
|
243
|
+
current?.child.kill('SIGTERM');
|
|
244
|
+
}
|
|
245
|
+
catch { /* already gone */ } };
|
|
246
|
+
if (publicCfg) {
|
|
247
|
+
// public mode: the raw proxy stays on loopback; the password-gated gateway owns the public port.
|
|
248
|
+
const distDir = resolveDistDir();
|
|
249
|
+
listenOrExit(proxy, proxyPort, { host: '127.0.0.1', label: 'supervisor (loopback proxy)', cleanup: reapChild, onListen: () => { recordEndpoint(childApiBase); console.log(`spec-cli supervisor on loopback :${proxyPort} (zero-downtime reloads, backend :${first.port})`); } });
|
|
250
|
+
startGateway({ publicPort, upstreamPort: proxyPort, password: publicCfg.password, tls: publicCfg.tls, distDir, onBindFail: reapChild });
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
listenOrExit(proxy, publicPort, { label: 'supervisor', cleanup: reapChild, onListen: () => { recordEndpoint(childApiBase); console.log(`spec-cli supervisor serving on http://localhost:${publicPort} (zero-downtime reloads, backend :${first.port})`); } });
|
|
254
|
+
}
|
|
255
|
+
startResourceMonitor();
|
|
256
|
+
// Watch the source closure in a workspace and the emitted directory in an installed package. Debounce a
|
|
257
|
+
// burst of writes (a merge touching several files across packages) into one rebuild and reload. A root that
|
|
258
|
+
// can't be watched (a package absent in some checkout) is logged and skipped — the supervisor owns the
|
|
259
|
+
// public port and must never die over a missing watch.
|
|
260
|
+
// fs.watch — even recursive — is NOT reliable for a long-lived supervisor: its inotify watches are silently
|
|
261
|
+
// dropped when a watched dir is rewritten (a git merge, a `materialize`) and NEVER re-established, so reloads
|
|
262
|
+
// quietly stop (observed: the watcher fired a few times then went deaf for hours). A cheap mtime poll can't go
|
|
263
|
+
// deaf: every 2s, take the newest .ts/.js/.json mtime across the trees; a jump triggers the debounced reload
|
|
264
|
+
// (a burst of writes still lands as one). Polling a few small src trees is negligible cost.
|
|
265
|
+
let timer;
|
|
266
|
+
const newestMtime = async (dir) => {
|
|
267
|
+
let max = 0;
|
|
268
|
+
let entries;
|
|
269
|
+
try {
|
|
270
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return 0;
|
|
274
|
+
} // a tree absent in some checkout → skip
|
|
275
|
+
for (const e of entries) {
|
|
276
|
+
if (e.isDirectory()) {
|
|
277
|
+
if (e.name !== 'node_modules')
|
|
278
|
+
max = Math.max(max, await newestMtime(join(dir, e.name)));
|
|
279
|
+
}
|
|
280
|
+
else if (/\.(ts|js|mjs|json)$/.test(e.name)) {
|
|
281
|
+
try {
|
|
282
|
+
max = Math.max(max, (await stat(join(dir, e.name))).mtimeMs);
|
|
283
|
+
}
|
|
284
|
+
catch { /* raced unlink */ }
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return max;
|
|
288
|
+
};
|
|
289
|
+
const scanMtime = async () => {
|
|
290
|
+
let m = 0;
|
|
291
|
+
for (const root of watchRoots)
|
|
292
|
+
m = Math.max(m, await newestMtime(root));
|
|
293
|
+
return m;
|
|
294
|
+
};
|
|
295
|
+
let lastMtime = await scanMtime(); // baseline at boot — only a LATER change reloads
|
|
296
|
+
let scanning = false;
|
|
297
|
+
setInterval(() => {
|
|
298
|
+
if (scanning)
|
|
299
|
+
return;
|
|
300
|
+
scanning = true;
|
|
301
|
+
void scanMtime().then((m) => {
|
|
302
|
+
if (m > lastMtime) {
|
|
303
|
+
lastMtime = m;
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
timer = setTimeout(() => void reload('code change'), 150);
|
|
306
|
+
}
|
|
307
|
+
}).finally(() => { scanning = false; });
|
|
308
|
+
}, 2000).unref();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type TreeNode = {
|
|
2
|
+
id: string;
|
|
3
|
+
parent: string | null;
|
|
4
|
+
title: string;
|
|
5
|
+
status: string;
|
|
6
|
+
version?: number;
|
|
7
|
+
drift?: number;
|
|
8
|
+
ghost?: boolean;
|
|
9
|
+
reviewSummary?: {
|
|
10
|
+
issues?: {
|
|
11
|
+
open: number;
|
|
12
|
+
};
|
|
13
|
+
evals?: {
|
|
14
|
+
stalePass: number;
|
|
15
|
+
staleFail: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type TreeOpts = {
|
|
20
|
+
node?: string;
|
|
21
|
+
depth?: number;
|
|
22
|
+
color?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare function renderTree(nodes: TreeNode[], opts?: TreeOpts): string;
|
|
25
|
+
export declare function treeJson(nodes: TreeNode[], opts?: TreeOpts): object[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// @@@ spex graph - the CLI's human-readable graph view: the same assembled board the dashboard's
|
|
2
|
+
// tidy-tree renders, as an indented terminal tree. Pure presentation over buildBoard()'s nodes —
|
|
3
|
+
// no read path of its own; status colours and badge semantics mirror the dashboard (drift /
|
|
4
|
+
// stale-eval / open-issues counts). Governed by the spex-tree spec node.
|
|
5
|
+
// dashboard status palette mapped onto ANSI: merged=green, active=cyan (live/in-flight, distinct
|
|
6
|
+
// from the warning yellow), drift=yellow (the dashboard's warning colour), pending=muted grey.
|
|
7
|
+
const STATUS_ANSI = { merged: '32', active: '36', drift: '33', pending: '90' };
|
|
8
|
+
// stale-eval count: declared scenarios whose LATEST reading exists but is no longer fresh.
|
|
9
|
+
// board `evals` is already latest-per-scenario, so this is a straight filter — the same freshness
|
|
10
|
+
// axis the dashboard's grey ✓/✗ badges read (score.jsx readingScore).
|
|
11
|
+
function staleYatsu(n) {
|
|
12
|
+
const summary = n.reviewSummary?.evals;
|
|
13
|
+
return summary ? summary.stalePass + summary.staleFail : 0;
|
|
14
|
+
}
|
|
15
|
+
function childrenIndex(nodes) {
|
|
16
|
+
const byParent = new Map();
|
|
17
|
+
for (const n of nodes) {
|
|
18
|
+
const list = byParent.get(n.parent) ?? [];
|
|
19
|
+
list.push(n);
|
|
20
|
+
byParent.set(n.parent, list);
|
|
21
|
+
}
|
|
22
|
+
return byParent;
|
|
23
|
+
}
|
|
24
|
+
// resolve the display roots: --node picks one subtree; default is the forest of parentless nodes
|
|
25
|
+
// (a node whose parent id isn't on the board — a ghost mid-add, say — surfaces as a root rather
|
|
26
|
+
// than vanishing). Unknown --node throws (fail loud, caller prints + exits 2), never an empty tree.
|
|
27
|
+
function roots(nodes, nodeId) {
|
|
28
|
+
if (nodeId) {
|
|
29
|
+
const hit = nodes.find((n) => n.id === nodeId);
|
|
30
|
+
if (!hit)
|
|
31
|
+
throw new Error(`no spec node "${nodeId}" — spex graph lists every id; spex spec search <topic> finds one by intent`);
|
|
32
|
+
return [hit];
|
|
33
|
+
}
|
|
34
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
35
|
+
return nodes.filter((n) => n.parent === null || !ids.has(n.parent));
|
|
36
|
+
}
|
|
37
|
+
export function renderTree(nodes, opts = {}) {
|
|
38
|
+
const color = opts.color ?? false;
|
|
39
|
+
const c = (code, t) => (color ? `\x1b[${code}m${t}\x1b[0m` : t);
|
|
40
|
+
const byParent = childrenIndex(nodes);
|
|
41
|
+
const lines = [];
|
|
42
|
+
const badges = (n) => {
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (n.ghost)
|
|
45
|
+
parts.push(c('90', 'ghost'));
|
|
46
|
+
if (n.drift)
|
|
47
|
+
parts.push(c('33', `drift:${n.drift}`));
|
|
48
|
+
const stale = staleYatsu(n);
|
|
49
|
+
if (stale)
|
|
50
|
+
parts.push(c('90', `stale:${stale}`));
|
|
51
|
+
if (n.reviewSummary?.issues?.open)
|
|
52
|
+
parts.push(c('31', `issues:${n.reviewSummary.issues.open}`));
|
|
53
|
+
return parts.length ? ' ' + parts.join(' ') : '';
|
|
54
|
+
};
|
|
55
|
+
const line = (n, prefix, branch) => {
|
|
56
|
+
const code = STATUS_ANSI[n.status] ?? '0';
|
|
57
|
+
// base state is invisible: merged (~80% of nodes) is the healthy ground state, already carried by
|
|
58
|
+
// the dot's colour — only deviations get a text label. Without colour the word IS the signal, so
|
|
59
|
+
// every status (merged included) prints in plain-text mode.
|
|
60
|
+
const label = color && n.status === 'merged' ? '' : ' ' + c(code, `[${n.status}]`);
|
|
61
|
+
const title = n.title && n.title !== n.id ? ' ' + c('90', '· ' + n.title) : '';
|
|
62
|
+
lines.push(`${prefix}${branch}${c(code, '●')} ${n.id}${label}${title}${badges(n)}`);
|
|
63
|
+
};
|
|
64
|
+
const walk = (n, prefix, branch, childPrefix, depth) => {
|
|
65
|
+
line(n, prefix, branch);
|
|
66
|
+
const kids = byParent.get(n.id) ?? [];
|
|
67
|
+
if (!kids.length)
|
|
68
|
+
return;
|
|
69
|
+
if (opts.depth !== undefined && depth >= opts.depth) {
|
|
70
|
+
lines.push(`${childPrefix}${c('90', `└─ … ${kids.length} more (raise --depth)`)}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
kids.forEach((k, i) => {
|
|
74
|
+
const last = i === kids.length - 1;
|
|
75
|
+
walk(k, childPrefix, last ? '└─ ' : '├─ ', childPrefix + (last ? ' ' : '│ '), depth + 1);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
for (const r of roots(nodes, opts.node))
|
|
79
|
+
walk(r, '', '', '', 0);
|
|
80
|
+
return lines.join('\n');
|
|
81
|
+
}
|
|
82
|
+
// the machine exit: the same filtered subtree as NESTED objects, badge counts precomputed — a
|
|
83
|
+
// shaped view, not a replacement for `spex graph --json` (which stays the full flat payload). Pruned
|
|
84
|
+
// children degrade to their ids, so --depth still tells the machine what exists below the cut.
|
|
85
|
+
export function treeJson(nodes, opts = {}) {
|
|
86
|
+
const byParent = childrenIndex(nodes);
|
|
87
|
+
const shape = (n, depth) => {
|
|
88
|
+
const kids = byParent.get(n.id) ?? [];
|
|
89
|
+
const pruned = opts.depth !== undefined && depth >= opts.depth;
|
|
90
|
+
return {
|
|
91
|
+
id: n.id, title: n.title, status: n.status, version: n.version ?? 0,
|
|
92
|
+
drift: n.drift ?? 0, staleYatsu: staleYatsu(n), openIssues: n.reviewSummary?.issues?.open ?? 0,
|
|
93
|
+
...(n.ghost ? { ghost: true } : {}),
|
|
94
|
+
children: pruned ? kids.map((k) => k.id) : kids.map((k) => shape(k, depth + 1)),
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
return roots(nodes, opts.node).map((r) => shape(r, 0));
|
|
98
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
// @@@ tsxBin - tsx's JS ENTRY (dist/cli.mjs), dev-or-published, run through `node` by the caller
|
|
4
|
+
// (`spawn(process.execPath, [tsxBin(pkgDir), entry, …])`). In the dev monorepo tsx sits in
|
|
5
|
+
// spec-cli/node_modules; in an installed `spexcode` package npm may hoist it to the consumer's node_modules
|
|
6
|
+
// — Node's own package resolver from spec-cli covers dev, global, and project-local installs in one rule.
|
|
7
|
+
// We resolve the .mjs entry rather than the `.bin/tsx` shim on purpose: the shim is an unspawnable sh
|
|
8
|
+
// script on Windows, so `node dist/cli.mjs` (identical to the shim on POSIX) is the one cross-platform form.
|
|
9
|
+
// `pkgDir` is the spec-cli directory.
|
|
10
|
+
export function tsxBin(pkgDir) {
|
|
11
|
+
try {
|
|
12
|
+
const req = createRequire(join(pkgDir, 'package.json'));
|
|
13
|
+
return join(dirname(req.resolve('tsx/package.json')), 'dist', 'cli.mjs');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`tsx runtime not found from ${pkgDir} — run \`npm install\` in the SpexCode package`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function tsxLoader(pkgDir) {
|
|
20
|
+
try {
|
|
21
|
+
return createRequire(join(pkgDir, 'package.json')).resolve('tsx/esm');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new Error(`tsx runtime not found from ${pkgDir} — run \`npm install\` in the SpexCode package`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// A direct source invocation is a development/test contract. A compiled launcher in the same workspace
|
|
28
|
+
// must still execute dist so an installed release never accidentally reintroduces a tsx requirement.
|
|
29
|
+
function sourceInvocation(pkgDir, callerDir) {
|
|
30
|
+
return resolve(callerDir) === join(resolve(pkgDir), 'src');
|
|
31
|
+
}
|
|
32
|
+
export function cliEntrypointArgs(pkgDir, callerDir) {
|
|
33
|
+
return sourceInvocation(pkgDir, callerDir)
|
|
34
|
+
? ['--import', tsxLoader(pkgDir), join(pkgDir, 'src', 'cli.ts')]
|
|
35
|
+
: [join(pkgDir, 'dist', 'cli.js')];
|
|
36
|
+
}
|
|
37
|
+
export function serverEntrypointArgs(pkgDir, callerDir) {
|
|
38
|
+
return sourceInvocation(pkgDir, callerDir)
|
|
39
|
+
? ['--import', tsxLoader(pkgDir), join(pkgDir, 'src', 'index.ts')]
|
|
40
|
+
: [join(pkgDir, 'dist', 'index.js')];
|
|
41
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
2
|
+
import { basename, join, resolve, relative } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { HARNESSES } from './harness.js';
|
|
6
|
+
import { runtimeRoot, readConfig, mainCheckout } from '@spexcode/spec-core';
|
|
7
|
+
import { resolveHarnessTargets } from './harness-select.js';
|
|
8
|
+
import { loadSkillConfig, loadAgentConfig } from '@spexcode/spec-core';
|
|
9
|
+
import { dematerialize } from './materialize.js';
|
|
10
|
+
import { gitBinary } from '@spexcode/spec-core';
|
|
11
|
+
// the standard plugin-host folders a host agent scans (in addition to any named in spexcode.json's `harnesses`).
|
|
12
|
+
const DEFAULT_PLUGIN_HOSTS = ['.claude', '.codex', '.adopter-a'];
|
|
13
|
+
// Init and uninstall share one ownership source for generated git hooks: the shipped canonical templates.
|
|
14
|
+
// Exact bytes prove the destination is still our derivative; any user edit withdraws that ownership.
|
|
15
|
+
const HOOK_TEMPLATES = fileURLToPath(new URL('../templates/hooks', import.meta.url));
|
|
16
|
+
// is this dir a SpexCode plugin bundle? Either its folder name is the identity stamp, or its
|
|
17
|
+
// `.claude-plugin/plugin.json` declares `name: spexcode`. Read-gated so a user's other plugin is never touched.
|
|
18
|
+
function isSpexcodeBundle(dir) {
|
|
19
|
+
if (basename(dir) === 'spexcode')
|
|
20
|
+
return true;
|
|
21
|
+
const manifest = join(dir, '.claude-plugin', 'plugin.json');
|
|
22
|
+
if (!existsSync(manifest))
|
|
23
|
+
return false;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(manifest, 'utf8')).name === 'spexcode';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// sweep any spexcode-stamped plugin bundle (`<host>/plugins/spexcode`, or a `.claude-plugin/plugin.json` named
|
|
32
|
+
// spexcode) under the project's plugin-host folders. The bundle EMITTER is a later node, so a native-only install
|
|
33
|
+
// has nothing here today; the sweep keeps uninstall a true inverse once it lands (and cleans a hand-dropped one).
|
|
34
|
+
function sweepPluginBundles(proj, hosts) {
|
|
35
|
+
const removed = [];
|
|
36
|
+
for (const host of new Set(hosts)) {
|
|
37
|
+
const pluginsDir = join(proj, host, 'plugins');
|
|
38
|
+
if (!existsSync(pluginsDir))
|
|
39
|
+
continue;
|
|
40
|
+
for (const e of readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
41
|
+
if (!e.isDirectory())
|
|
42
|
+
continue;
|
|
43
|
+
const dir = join(pluginsDir, e.name);
|
|
44
|
+
if (isSpexcodeBundle(dir)) {
|
|
45
|
+
rmSync(dir, { recursive: true, force: true });
|
|
46
|
+
removed.push(relative(proj, dir));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return removed;
|
|
51
|
+
}
|
|
52
|
+
// Arbitrary plugin landing folders are the one materialized path set that cannot be reconstructed from stamps
|
|
53
|
+
// alone, so materialize records them as data. Read every current per-tree ledger plus the legacy project-global
|
|
54
|
+
// ledger before uninstall removes the store; a stale bundle remains removable even after current config stopped
|
|
55
|
+
// naming its former host folder.
|
|
56
|
+
function pluginLedgerHosts(store) {
|
|
57
|
+
const ledgers = [join(store, 'plugin-folders')];
|
|
58
|
+
const trees = join(store, 'trees');
|
|
59
|
+
if (existsSync(trees)) {
|
|
60
|
+
for (const e of readdirSync(trees, { withFileTypes: true })) {
|
|
61
|
+
if (e.isDirectory())
|
|
62
|
+
ledgers.push(join(trees, e.name, 'plugin-folders'));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const hosts = [];
|
|
66
|
+
for (const ledger of ledgers) {
|
|
67
|
+
if (!existsSync(ledger))
|
|
68
|
+
continue;
|
|
69
|
+
hosts.push(...readFileSync(ledger, 'utf8').split('\n').map((line) => line.trim()).filter(Boolean));
|
|
70
|
+
}
|
|
71
|
+
return hosts;
|
|
72
|
+
}
|
|
73
|
+
// resolve the repo's shared git hooks dir (the common dir's hooks/), or null when <dir> isn't a git repo.
|
|
74
|
+
function hooksDir(proj) {
|
|
75
|
+
try {
|
|
76
|
+
const common = execFileSync(gitBinary(process.env), ['-C', proj, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
79
|
+
}).trim();
|
|
80
|
+
return join(common, 'hooks');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Remove only byte-identical products of the canonical hook templates. Enumerating the same template directory
|
|
87
|
+
// init copies keeps every generated hook covered without a second name list; modified and unrelated hooks survive.
|
|
88
|
+
function removeHooks(proj) {
|
|
89
|
+
const dir = hooksDir(proj);
|
|
90
|
+
if (!dir)
|
|
91
|
+
return [];
|
|
92
|
+
const removed = [];
|
|
93
|
+
for (const e of readdirSync(HOOK_TEMPLATES, { withFileTypes: true })) {
|
|
94
|
+
if (!e.isFile())
|
|
95
|
+
continue;
|
|
96
|
+
const hook = join(dir, e.name);
|
|
97
|
+
if (!existsSync(hook) || !readFileSync(hook).equals(readFileSync(join(HOOK_TEMPLATES, e.name))))
|
|
98
|
+
continue;
|
|
99
|
+
rmSync(hook, { force: true });
|
|
100
|
+
removed.push(e.name);
|
|
101
|
+
}
|
|
102
|
+
return removed;
|
|
103
|
+
}
|
|
104
|
+
export function uninstall(targetArg, opts = {}) {
|
|
105
|
+
const proj = resolve(targetArg ?? process.cwd());
|
|
106
|
+
console.log(`spex uninstall → ${proj}`);
|
|
107
|
+
// cwd = the project so the .plugins loaders read THIS tree's surface nodes (the live skill/agent names tell
|
|
108
|
+
// each adapter's clean() exactly which name-scoped on-demand files were its to remove).
|
|
109
|
+
const prevCwd = process.cwd();
|
|
110
|
+
let arts = { skills: [], agents: [] };
|
|
111
|
+
try {
|
|
112
|
+
process.chdir(proj);
|
|
113
|
+
arts = { skills: loadSkillConfig().map((s) => s.name), agents: loadAgentConfig().map((a) => a.name) };
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// no readable .plugins (already partly torn down, or never adopted) — clean still strips the harness wiring.
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
process.chdir(prevCwd);
|
|
120
|
+
}
|
|
121
|
+
// 1+2. materialize(∅): every harness's artifacts (contract block, shim, trust, skills/agents), the managed
|
|
122
|
+
// .gitignore + info/exclude blocks, any legacy skip-worktree bit, and the content filter — the SAME
|
|
123
|
+
// erase phase every materialize runs, asserted against the empty policy. One inverse, never a parallel one.
|
|
124
|
+
dematerialize(proj, arts);
|
|
125
|
+
// 3. Locate the global per-project store and recover every current/legacy plugin landing folder from its
|
|
126
|
+
// ledgers BEFORE deleting it. The store is the runtime tier, not the user's tracked intent asset.
|
|
127
|
+
let store = null;
|
|
128
|
+
let ledgerHosts = [];
|
|
129
|
+
try {
|
|
130
|
+
store = runtimeRoot(proj);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
store = null;
|
|
134
|
+
}
|
|
135
|
+
if (store)
|
|
136
|
+
ledgerHosts = pluginLedgerHosts(store);
|
|
137
|
+
// 4. Any spexcode-stamped plugin bundle under configured, standard, or ledger-recovered hosts. The ledger
|
|
138
|
+
// input is what closes plugin-folder A -> native/folder B even when the previous materialize never finished.
|
|
139
|
+
let pluginHosts = [...DEFAULT_PLUGIN_HOSTS, ...ledgerHosts];
|
|
140
|
+
try {
|
|
141
|
+
const targets = resolveHarnessTargets(readConfig(mainCheckout(proj)).harnesses);
|
|
142
|
+
pluginHosts = [...pluginHosts, ...targets.filter((t) => t.kind === 'plugin').map((t) => t.folder)];
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// an illegal harnesses set doesn't block backout — fall through to the standard hosts.
|
|
146
|
+
}
|
|
147
|
+
const bundles = sweepPluginBundles(proj, pluginHosts);
|
|
148
|
+
// 5. The whole store: per-tree manifests/hashes/ledgers and sessions.
|
|
149
|
+
const removedStores = [];
|
|
150
|
+
for (const candidate of new Set([store].filter((path) => !!path))) {
|
|
151
|
+
if (!existsSync(candidate))
|
|
152
|
+
continue;
|
|
153
|
+
rmSync(candidate, { recursive: true, force: true });
|
|
154
|
+
removedStores.push(candidate);
|
|
155
|
+
}
|
|
156
|
+
console.log(`✓ dematerialized (contract blocks, shims, Codex trust, skills, sub-agents, ignore blocks, content filter) for ${HARNESSES.map((h) => h.id).join(', ')}`);
|
|
157
|
+
if (removedStores.length)
|
|
158
|
+
console.log(`✓ removed the global per-project store (${removedStores.join(', ')})`);
|
|
159
|
+
if (bundles.length)
|
|
160
|
+
console.log(`✓ removed plugin bundle(s): ${bundles.join(', ')}`);
|
|
161
|
+
// Git hooks are per-clone and may carry user logic → preserved unless --hooks (and even then only while
|
|
162
|
+
// byte-identical to a canonical generated template; a user edit withdraws our ownership).
|
|
163
|
+
if (opts.hooks) {
|
|
164
|
+
const removed = removeHooks(proj);
|
|
165
|
+
if (removed.length)
|
|
166
|
+
console.log(`✓ removed git hooks (${removed.join(', ')})`);
|
|
167
|
+
else
|
|
168
|
+
console.log('• no spexcode git hooks to remove');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log('• left git hooks in place (per-clone; pass --hooks to remove the spexcode ones)');
|
|
172
|
+
}
|
|
173
|
+
console.log(`
|
|
174
|
+
SpexCode wiring removed. Your spec data is untouched:
|
|
175
|
+
• .spec/ (including .plugins/) and spexcode.json remain — your tracked intent is never deleted by uninstall.
|
|
176
|
+
• To re-adopt later: \`spex init\` regenerates the shims, contract, trust, and global store.`);
|
|
177
|
+
}
|