spexcode 0.6.2 → 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 +3 -1
- 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} +36 -32
- 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 +1 -1
- 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/{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/{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-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 -728
- package/spec-cli/src/attach.ts +0 -43
- package/spec-cli/src/claude-headless.ts +0 -321
- package/spec-cli/src/cli.ts +0 -1258
- package/spec-cli/src/client.ts +0 -414
- 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 -165
- package/spec-cli/src/doctor.ts +0 -516
- package/spec-cli/src/execution-trace.ts +0 -444
- 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 -358
- package/spec-cli/src/gateway.ts +0 -461
- package/spec-cli/src/git.ts +0 -2556
- package/spec-cli/src/graph.ts +0 -271
- package/spec-cli/src/graphCache.ts +0 -710
- 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 -3025
- package/spec-cli/src/headless-controller.ts +0 -63
- package/spec-cli/src/help.ts +0 -492
- package/spec-cli/src/host-resources.ts +0 -762
- 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 -138
- package/spec-cli/src/index.ts +0 -818
- 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 -640
- package/spec-cli/src/lint.ts +0 -488
- package/spec-cli/src/localIssues.ts +0 -538
- package/spec-cli/src/materialize.ts +0 -417
- 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 -434
- 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-execution.ts +0 -68
- package/spec-cli/src/session-files.ts +0 -121
- package/spec-cli/src/session-follow.ts +0 -182
- package/spec-cli/src/session-reparent.ts +0 -33
- package/spec-cli/src/session-timeline.ts +0 -247
- package/spec-cli/src/session-web.ts +0 -135
- package/spec-cli/src/sessions.ts +0 -3883
- 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-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
- package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
- package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.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-D9yPMR3A.js +0 -2
- package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
- package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
- package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
- package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
- package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
- package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
- package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
- package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
- package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
- package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
- package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
- package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.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 -894
- package/spec-eval/src/evaltab.ts +0 -264
- package/spec-eval/src/filing.ts +0 -39
- package/spec-eval/src/freshness.ts +0 -836
- 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 -2220
- 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/comment-altitude/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/core/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/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
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process'
|
|
2
|
-
import { closeSync, openSync, readFileSync, readSync, readdirSync, statSync } from 'node:fs'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
|
|
6
|
-
export type ExecutionTurn = Readonly<{
|
|
7
|
-
token: string
|
|
8
|
-
acceptedAt: string
|
|
9
|
-
}>
|
|
10
|
-
export type ExecutionStepKind = 'command' | 'read' | 'write' | 'search' | 'tool'
|
|
11
|
-
export type ExecutionStep = Readonly<{
|
|
12
|
-
id: string
|
|
13
|
-
kind: ExecutionStepKind
|
|
14
|
-
label: string
|
|
15
|
-
detail?: string
|
|
16
|
-
state: 'running' | 'done'
|
|
17
|
-
}>
|
|
18
|
-
export type ExecutionTrace = Readonly<{
|
|
19
|
-
revision: string
|
|
20
|
-
turnId: string | null
|
|
21
|
-
workingNote: string | null
|
|
22
|
-
steps: readonly ExecutionStep[]
|
|
23
|
-
}>
|
|
24
|
-
|
|
25
|
-
type TraceEvent =
|
|
26
|
-
| Readonly<{ type: 'boundary'; ids: readonly string[]; at: number | null }>
|
|
27
|
-
| Readonly<{ type: 'note'; text: string }>
|
|
28
|
-
| Readonly<{ type: 'call'; step: ExecutionStep }>
|
|
29
|
-
| Readonly<{ type: 'done'; id: string }>
|
|
30
|
-
type CachedTrace = { size: number; remainder: Buffer; events: TraceEvent[] }
|
|
31
|
-
type ApplyEvent = (value: unknown, ordinal: number) => readonly TraceEvent[]
|
|
32
|
-
type ExportLoader = (threadId: string) => string
|
|
33
|
-
|
|
34
|
-
const cache = new Map<string, CachedTrace>()
|
|
35
|
-
const sessionJsonlPaths = new Map<string, string>()
|
|
36
|
-
const localStoreCache = new Map<string, { revision: string; events: TraceEvent[] }>()
|
|
37
|
-
|
|
38
|
-
const emptyTrace = (turn: ExecutionTurn | null, revision = '0'): ExecutionTrace => ({
|
|
39
|
-
revision: `${turn?.token ?? '0'}:${revision}`,
|
|
40
|
-
turnId: turn?.token ?? null,
|
|
41
|
-
workingNote: null,
|
|
42
|
-
steps: [],
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const object = (value: unknown): Record<string, unknown> | null =>
|
|
46
|
-
value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null
|
|
47
|
-
const items = (value: unknown): readonly unknown[] => Array.isArray(value) ? value : []
|
|
48
|
-
const text = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value.trim() : null
|
|
49
|
-
const field = (value: Record<string, unknown>, ...names: string[]): string | null => {
|
|
50
|
-
for (const name of names) {
|
|
51
|
-
const found = text(value[name])
|
|
52
|
-
if (found) return found
|
|
53
|
-
}
|
|
54
|
-
return null
|
|
55
|
-
}
|
|
56
|
-
const children = (dir: string): string[] => { try { return readdirSync(dir).sort().reverse() } catch { return [] } }
|
|
57
|
-
|
|
58
|
-
const sensitive = /(token|secret|password|authorization|bearer|cookie|credential|api[_-]?key|private[ _-]?key)/i
|
|
59
|
-
const detailFields = [
|
|
60
|
-
['cmd', 'cmd'], ['command', 'command'], ['path', 'path'], ['file_path', 'path'],
|
|
61
|
-
['query', 'query'], ['pattern', 'pattern'], ['url', 'url'], ['workdir', 'in'],
|
|
62
|
-
] as const
|
|
63
|
-
|
|
64
|
-
function toolKind(name: string): ExecutionStepKind {
|
|
65
|
-
const normalized = name.toLowerCase()
|
|
66
|
-
if (/(search|browse|web)/.test(normalized)) return 'search'
|
|
67
|
-
if (/(read|list|find|grep|glob|stat)/.test(normalized)) return 'read'
|
|
68
|
-
if (/(write|edit|patch|create|delete|move|rename)/.test(normalized)) return 'write'
|
|
69
|
-
if (/(exec|shell|command|terminal|bash|npm|git)/.test(normalized)) return 'command'
|
|
70
|
-
return 'tool'
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function toolLabel(name: string, kind: ExecutionStepKind): string {
|
|
74
|
-
const labels: Record<ExecutionStepKind, string> = {
|
|
75
|
-
command: 'Run command', read: 'Read files', write: 'Update files', search: 'Search', tool: 'Use tool',
|
|
76
|
-
}
|
|
77
|
-
return name.trim() || labels[kind]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function recordInput(value: unknown): Record<string, unknown> | null {
|
|
81
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>
|
|
82
|
-
if (typeof value !== 'string') return null
|
|
83
|
-
try {
|
|
84
|
-
const parsed = JSON.parse(value)
|
|
85
|
-
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null
|
|
86
|
-
} catch { return null }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function sensitiveInput(value: unknown): boolean {
|
|
90
|
-
if (typeof value === 'string') return sensitive.test(value)
|
|
91
|
-
if (Array.isArray(value)) return value.some(sensitiveInput)
|
|
92
|
-
if (value && typeof value === 'object') return Object.entries(value as Record<string, unknown>)
|
|
93
|
-
.some(([key, entry]) => sensitive.test(key) || sensitiveInput(entry))
|
|
94
|
-
return false
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function compactValue(key: string, value: string | number | boolean): string {
|
|
98
|
-
const compact = String(value).trim().replace(/\s+/g, ' ')
|
|
99
|
-
if (key === 'path' || key === 'file_path' || key === 'workdir') {
|
|
100
|
-
const parts = compact.replace(/\\/g, '/').split('/').filter(Boolean)
|
|
101
|
-
return parts.slice(-2).join('/') || compact
|
|
102
|
-
}
|
|
103
|
-
return compact.length > 96 ? `${compact.slice(0, 95)}...` : compact
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function detailFromInput(value: unknown): string | undefined {
|
|
107
|
-
const input = recordInput(value)
|
|
108
|
-
if (!input || sensitiveInput(input)) return undefined
|
|
109
|
-
const details: string[] = []
|
|
110
|
-
for (const [key, title] of detailFields) {
|
|
111
|
-
const value = input[key]
|
|
112
|
-
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') continue
|
|
113
|
-
details.push(`${title}: ${compactValue(key, value)}`)
|
|
114
|
-
if (details.length === 2) break
|
|
115
|
-
}
|
|
116
|
-
const lineStart = input.line_start
|
|
117
|
-
const lineEnd = input.line_end
|
|
118
|
-
if (details.length < 2 && (typeof lineStart === 'number' || typeof lineEnd === 'number')) {
|
|
119
|
-
details.push(`lines: ${typeof lineStart === 'number' ? lineStart : 1}-${typeof lineEnd === 'number' ? lineEnd : lineStart}`)
|
|
120
|
-
}
|
|
121
|
-
return details.length ? details.join(' · ') : undefined
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function displayableNote(value: unknown): string | null {
|
|
125
|
-
const note = text(value)?.replace(/\s+/g, ' ')
|
|
126
|
-
return note ? (note.length > 240 ? `${note.slice(0, 239)}...` : note) : null
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function stepFromInput(id: string, name: string, input: unknown, state: ExecutionStep['state']): ExecutionStep {
|
|
130
|
-
const kind = toolKind(name)
|
|
131
|
-
const detail = detailFromInput(input)
|
|
132
|
-
return { id, kind, label: toolLabel(name, kind), ...(detail ? { detail } : {}), state }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function timestamp(value: unknown): number | null {
|
|
136
|
-
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
137
|
-
if (typeof value !== 'string' || !value.trim()) return null
|
|
138
|
-
const number = Number(value)
|
|
139
|
-
if (Number.isFinite(number)) return number
|
|
140
|
-
const parsed = Date.parse(value)
|
|
141
|
-
return Number.isFinite(parsed) ? parsed : null
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function identities(...values: Array<Record<string, unknown> | null>): string[] {
|
|
145
|
-
const result = new Set<string>()
|
|
146
|
-
for (const value of values) for (const key of ['id', 'uuid', 'message_id', 'messageId', 'client_id', 'clientId', 'client_user_message_id', 'clientUserMessageId', 'promptId']) {
|
|
147
|
-
const id = field(value || {}, key)
|
|
148
|
-
if (id) result.add(id)
|
|
149
|
-
}
|
|
150
|
-
return [...result]
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function at(value: Record<string, unknown>): number | null {
|
|
154
|
-
for (const key of ['timestamp', 'created_at', 'createdAt', 'created', 'time']) {
|
|
155
|
-
const found = timestamp(value[key])
|
|
156
|
-
if (found !== null) return found
|
|
157
|
-
}
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function isPrompt(content: unknown): boolean {
|
|
162
|
-
if (typeof content === 'string') return !!content.trim()
|
|
163
|
-
const blocks = items(content)
|
|
164
|
-
return blocks.some((block) => object(block)?.type !== 'tool_result')
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function project(events: readonly TraceEvent[], sourceRevision: string, turn: ExecutionTurn | null): ExecutionTrace {
|
|
168
|
-
let start = -1
|
|
169
|
-
if (!turn) {
|
|
170
|
-
for (const [index, event] of events.entries()) if (event.type === 'boundary') start = index
|
|
171
|
-
} else {
|
|
172
|
-
const acceptedAt = timestamp(turn.acceptedAt)
|
|
173
|
-
const exact = events.flatMap((event, index) => event.type === 'boundary' && event.ids.includes(turn.token) ? [index] : [])
|
|
174
|
-
start = exact.length === 1 ? exact[0] : -1
|
|
175
|
-
if (start < 0 && exact.length === 0 && acceptedAt !== null) {
|
|
176
|
-
const candidates = events.flatMap((event, index) => event.type === 'boundary' && event.at !== null && event.at >= acceptedAt
|
|
177
|
-
? [{ index, at: event.at }] : [])
|
|
178
|
-
const earliest = candidates.reduce<number | null>((value, candidate) => value === null || candidate.at < value ? candidate.at : value, null)
|
|
179
|
-
const matches = earliest === null ? [] : candidates.filter((candidate) => candidate.at === earliest)
|
|
180
|
-
if (matches.length === 1) start = matches[0].index
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (turn && start < 0) return emptyTrace(turn, sourceRevision)
|
|
184
|
-
|
|
185
|
-
let note: string | null = null
|
|
186
|
-
let steps: ExecutionStep[] = []
|
|
187
|
-
for (let index = start + 1; index < events.length; index++) {
|
|
188
|
-
const event = events[index]
|
|
189
|
-
if (event.type === 'boundary') return emptyTrace(turn, sourceRevision)
|
|
190
|
-
if (event.type === 'note') { note = event.text; steps = []; continue }
|
|
191
|
-
if (event.type === 'call') {
|
|
192
|
-
if (note) steps.push(event.step)
|
|
193
|
-
continue
|
|
194
|
-
}
|
|
195
|
-
const step = steps.find((value) => value.id === event.id)
|
|
196
|
-
if (step) steps[steps.indexOf(step)] = { ...step, state: 'done' }
|
|
197
|
-
}
|
|
198
|
-
return note
|
|
199
|
-
? { revision: `${turn?.token ?? '0'}:${sourceRevision}`, turnId: turn?.token ?? null, workingNote: note, steps }
|
|
200
|
-
: emptyTrace(turn, sourceRevision)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function applyRolloutEvent(value: unknown, ordinal: number): readonly TraceEvent[] {
|
|
204
|
-
const event = object(value)
|
|
205
|
-
const payload = object(event?.payload)
|
|
206
|
-
if (!event || !payload) return []
|
|
207
|
-
const result: TraceEvent[] = []
|
|
208
|
-
const type = field(payload, 'type')
|
|
209
|
-
if ((event.type === 'event_msg' && type === 'user_message')
|
|
210
|
-
|| (event.type === 'response_item' && (type === 'message' || type === 'input_message') && payload.role === 'user')) {
|
|
211
|
-
result.push({ type: 'boundary', ids: identities(payload, event), at: at(payload) ?? at(event) })
|
|
212
|
-
}
|
|
213
|
-
if (event.type === 'event_msg' && type === 'agent_message' && payload.phase === 'commentary') {
|
|
214
|
-
const note = displayableNote(payload.message ?? payload.text)
|
|
215
|
-
if (note) result.push({ type: 'note', text: note })
|
|
216
|
-
}
|
|
217
|
-
if (event.type === 'response_item' && (type === 'custom_tool_call' || type === 'function_call')) {
|
|
218
|
-
const name = field(payload, 'name', 'tool_name') || 'tool'
|
|
219
|
-
const id = field(payload, 'call_id', 'id') || `tool-${ordinal}`
|
|
220
|
-
result.push({ type: 'call', step: stepFromInput(id, name, payload.input ?? payload.arguments ?? payload.args, 'running') })
|
|
221
|
-
}
|
|
222
|
-
if (event.type === 'response_item' && (type === 'custom_tool_call_output' || type === 'function_call_output')) {
|
|
223
|
-
const id = field(payload, 'call_id', 'id')
|
|
224
|
-
if (id) result.push({ type: 'done', id })
|
|
225
|
-
}
|
|
226
|
-
if (event.type === 'event_msg' && type === 'web_search_end') {
|
|
227
|
-
result.push({ type: 'call', step: stepFromInput(field(payload, 'id', 'call_id') || `search-${ordinal}`, 'Search', payload, 'done') })
|
|
228
|
-
}
|
|
229
|
-
return result
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function applyProjectJsonlEvent(value: unknown, ordinal: number): readonly TraceEvent[] {
|
|
233
|
-
const entry = object(value)
|
|
234
|
-
const message = object(entry?.message)
|
|
235
|
-
if (!entry || !message) return []
|
|
236
|
-
const result: TraceEvent[] = []
|
|
237
|
-
if (entry.type === 'user' && message.role === 'user' && isPrompt(message.content))
|
|
238
|
-
result.push({ type: 'boundary', ids: identities(entry, message), at: at(entry) ?? at(message) })
|
|
239
|
-
if (entry.type === 'assistant' && message.role === 'assistant') for (const blockValue of items(message.content)) {
|
|
240
|
-
const block = object(blockValue)
|
|
241
|
-
if (block?.type === 'text') {
|
|
242
|
-
const note = displayableNote(block.text)
|
|
243
|
-
if (note) result.push({ type: 'note', text: note })
|
|
244
|
-
}
|
|
245
|
-
if (block?.type === 'tool_use') {
|
|
246
|
-
const id = field(block, 'id') || `tool-${ordinal}`
|
|
247
|
-
result.push({ type: 'call', step: stepFromInput(id, field(block, 'name') || 'tool', block.input, 'running') })
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (entry.type === 'user' && message.role === 'user') for (const blockValue of items(message.content)) {
|
|
251
|
-
const block = object(blockValue)
|
|
252
|
-
if (block?.type === 'tool_result') {
|
|
253
|
-
const id = field(block, 'tool_use_id')
|
|
254
|
-
if (id) result.push({ type: 'done', id })
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return result
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function applySessionJsonlEvent(value: unknown, ordinal: number): readonly TraceEvent[] {
|
|
261
|
-
const entry = object(value)
|
|
262
|
-
const message = object(entry?.message)
|
|
263
|
-
if (!entry || entry.type !== 'message' || !message) return []
|
|
264
|
-
const result: TraceEvent[] = []
|
|
265
|
-
if (message.role === 'user' && isPrompt(message.content))
|
|
266
|
-
result.push({ type: 'boundary', ids: identities(entry, message), at: at(entry) ?? at(message) })
|
|
267
|
-
if (message.role === 'assistant') for (const blockValue of items(message.content)) {
|
|
268
|
-
const block = object(blockValue)
|
|
269
|
-
if (block?.type === 'text') {
|
|
270
|
-
const note = displayableNote(block.text)
|
|
271
|
-
if (note) result.push({ type: 'note', text: note })
|
|
272
|
-
}
|
|
273
|
-
if (block?.type === 'toolCall') {
|
|
274
|
-
const id = field(block, 'id') || `tool-${ordinal}`
|
|
275
|
-
result.push({ type: 'call', step: stepFromInput(id, field(block, 'name') || 'tool', block.arguments, 'running') })
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (message.role === 'toolResult') {
|
|
279
|
-
const id = field(message, 'toolCallId')
|
|
280
|
-
if (id) result.push({ type: 'done', id })
|
|
281
|
-
}
|
|
282
|
-
return result
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function completeLines(buffer: Buffer): { lines: Buffer[]; remainder: Buffer } {
|
|
286
|
-
const lines: Buffer[] = []
|
|
287
|
-
let start = 0
|
|
288
|
-
for (let index = 0; index < buffer.length; index++) {
|
|
289
|
-
if (buffer[index] !== 10) continue
|
|
290
|
-
if (index > start) lines.push(buffer.subarray(start, index))
|
|
291
|
-
start = index + 1
|
|
292
|
-
}
|
|
293
|
-
return { lines, remainder: buffer.subarray(start) }
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function readFrom(path: string, start: number): Buffer | null {
|
|
297
|
-
let fd: number | null = null
|
|
298
|
-
try {
|
|
299
|
-
const size = statSync(path).size
|
|
300
|
-
if (size <= start) return Buffer.alloc(0)
|
|
301
|
-
const chunk = Buffer.allocUnsafe(size - start)
|
|
302
|
-
fd = openSync(path, 'r')
|
|
303
|
-
readSync(fd, chunk, 0, chunk.length, start)
|
|
304
|
-
return chunk
|
|
305
|
-
} catch { return null }
|
|
306
|
-
finally { if (fd !== null) closeSync(fd) }
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function readIncremental(path: string | null, apply: ApplyEvent, turn: ExecutionTurn | null): ExecutionTrace {
|
|
310
|
-
if (!path) return emptyTrace(turn)
|
|
311
|
-
let size: number
|
|
312
|
-
try { size = statSync(path).size } catch { return emptyTrace(turn) }
|
|
313
|
-
const prior = cache.get(path)
|
|
314
|
-
const trace = prior && prior.size <= size
|
|
315
|
-
? { ...prior, events: [...prior.events] }
|
|
316
|
-
: { size: 0, remainder: Buffer.alloc(0), events: [] as TraceEvent[] }
|
|
317
|
-
const appended = readFrom(path, trace.size)
|
|
318
|
-
if (appended === null) return emptyTrace(turn)
|
|
319
|
-
const { lines, remainder } = completeLines(Buffer.concat([trace.remainder, appended]))
|
|
320
|
-
for (const [index, line] of lines.entries()) {
|
|
321
|
-
try { trace.events.push(...apply(JSON.parse(line.toString('utf8')), trace.size + index)) } catch { /* malformed native data is ignored */ }
|
|
322
|
-
}
|
|
323
|
-
trace.size = size
|
|
324
|
-
trace.remainder = remainder
|
|
325
|
-
cache.set(path, trace)
|
|
326
|
-
return project(trace.events, String(size), turn)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const codexSessionsDir = () => join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'sessions')
|
|
330
|
-
|
|
331
|
-
export function codexRolloutPath(threadId: string, root = codexSessionsDir()): string | null {
|
|
332
|
-
for (const year of children(root)) for (const month of children(join(root, year))) for (const day of children(join(root, year, month))) {
|
|
333
|
-
const dir = join(root, year, month, day)
|
|
334
|
-
const file = children(dir).find((name) => name.includes(threadId))
|
|
335
|
-
if (file) return join(dir, file)
|
|
336
|
-
}
|
|
337
|
-
return null
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export function readCodexExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = codexSessionsDir()): ExecutionTrace {
|
|
341
|
-
return readIncremental(codexRolloutPath(threadId, root), applyRolloutEvent, turn)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const projectTranscriptRoot = () => join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'projects')
|
|
345
|
-
|
|
346
|
-
function projectJsonlPath(threadId: string, root = projectTranscriptRoot()): string | null {
|
|
347
|
-
for (const project of children(root)) {
|
|
348
|
-
const path = join(root, project, `${threadId}.jsonl`)
|
|
349
|
-
try { if (statSync(path).isFile()) return path } catch { /* try next */ }
|
|
350
|
-
}
|
|
351
|
-
return null
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
export function readProjectJsonlExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = projectTranscriptRoot()): ExecutionTrace {
|
|
355
|
-
return readIncremental(projectJsonlPath(threadId, root), applyProjectJsonlEvent, turn)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const sessionJsonlRoot = () => join(process.env.SPEXCODE_PI_AGENT_DIR || join(homedir(), '.pi', 'agent'), 'sessions')
|
|
359
|
-
|
|
360
|
-
function sessionJsonlPath(threadId: string, root = sessionJsonlRoot()): string | null {
|
|
361
|
-
const key = `${root}:${threadId}`
|
|
362
|
-
const cached = sessionJsonlPaths.get(key)
|
|
363
|
-
if (cached) {
|
|
364
|
-
try { if (statSync(cached).isFile()) return cached } catch { sessionJsonlPaths.delete(key) }
|
|
365
|
-
}
|
|
366
|
-
for (const directory of children(root)) for (const file of children(join(root, directory))) {
|
|
367
|
-
if (!file.endsWith('.jsonl')) continue
|
|
368
|
-
const path = join(root, directory, file)
|
|
369
|
-
try {
|
|
370
|
-
const header = object(JSON.parse(readFileSync(path, 'utf8').split('\n', 1)[0]))
|
|
371
|
-
if (header?.type === 'session' && header.id === threadId) {
|
|
372
|
-
sessionJsonlPaths.set(key, path)
|
|
373
|
-
return path
|
|
374
|
-
}
|
|
375
|
-
} catch { /* unreadable entries are not a match */ }
|
|
376
|
-
}
|
|
377
|
-
return null
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export function readSessionJsonlExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = sessionJsonlRoot()): ExecutionTrace {
|
|
381
|
-
return readIncremental(sessionJsonlPath(threadId, root), applySessionJsonlEvent, turn)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const localStoreRoot = () => process.env.SPEXCODE_OPENCODE_DATA_DIR
|
|
385
|
-
|| join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'opencode')
|
|
386
|
-
|
|
387
|
-
function readLocalExport(threadId: string): string {
|
|
388
|
-
return execFileSync(process.env.SPEXCODE_OPENCODE_CMD || 'opencode', ['export', threadId, '--sanitize'], {
|
|
389
|
-
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
390
|
-
})
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function localStoreRevision(root: string): string | null {
|
|
394
|
-
try {
|
|
395
|
-
const database = statSync(join(root, 'opencode.db'))
|
|
396
|
-
let writeAheadLog = '0:0'
|
|
397
|
-
try {
|
|
398
|
-
const stat = statSync(join(root, 'opencode.db-wal'))
|
|
399
|
-
writeAheadLog = `${stat.size}:${Math.floor(stat.mtimeMs)}`
|
|
400
|
-
} catch { /* a checkpointed database has no separate write-ahead log */ }
|
|
401
|
-
return `${database.size}:${Math.floor(database.mtimeMs)}:${writeAheadLog}`
|
|
402
|
-
} catch { return null }
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function localStoreEvents(value: unknown): TraceEvent[] {
|
|
406
|
-
const events: TraceEvent[] = []
|
|
407
|
-
for (const messageValue of items(object(value)?.messages)) {
|
|
408
|
-
const message = object(messageValue)
|
|
409
|
-
const info = object(message?.info)
|
|
410
|
-
if (!message || !info) continue
|
|
411
|
-
if (info.role === 'user') events.push({ type: 'boundary', ids: identities(info, message), at: at(object(info.time) || info) })
|
|
412
|
-
if (info.role !== 'assistant') continue
|
|
413
|
-
for (const partValue of items(message.parts)) {
|
|
414
|
-
const part = object(partValue)
|
|
415
|
-
if (part?.type === 'text') {
|
|
416
|
-
const note = displayableNote(part.text)
|
|
417
|
-
if (note) events.push({ type: 'note', text: note })
|
|
418
|
-
}
|
|
419
|
-
if (part?.type === 'tool') {
|
|
420
|
-
const state = object(part.state)
|
|
421
|
-
const status = field(state || {}, 'status')?.toLowerCase() || ''
|
|
422
|
-
const id = field(part, 'callID', 'id') || `tool-${events.length}`
|
|
423
|
-
events.push({ type: 'call', step: stepFromInput(id, field(part, 'tool') || 'tool', state?.input, /completed|error|cancelled/.test(status) ? 'done' : 'running') })
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return events
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export function readLocalStoreExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = localStoreRoot(), load: ExportLoader = readLocalExport): ExecutionTrace {
|
|
431
|
-
const revision = localStoreRevision(root)
|
|
432
|
-
if (!revision) return emptyTrace(turn)
|
|
433
|
-
const key = `${root}:${threadId}`
|
|
434
|
-
let cached = localStoreCache.get(key)
|
|
435
|
-
if (!cached || cached.revision !== revision) {
|
|
436
|
-
try {
|
|
437
|
-
cached = { revision, events: localStoreEvents(JSON.parse(load(threadId))) }
|
|
438
|
-
localStoreCache.set(key, cached)
|
|
439
|
-
} catch { return emptyTrace(turn, revision) }
|
|
440
|
-
}
|
|
441
|
-
return project(cached.events, revision, turn)
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export const noExecutionTrace = (_threadId: string, _turn: ExecutionTurn | null): ExecutionTrace | null => null
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { copyFileSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
|
|
3
|
-
export function writeFileIfChanged(path: string, content: string | Uint8Array): boolean {
|
|
4
|
-
const next = Buffer.from(content)
|
|
5
|
-
try {
|
|
6
|
-
if (readFileSync(path).equals(next)) return false
|
|
7
|
-
} catch (error: any) {
|
|
8
|
-
if (error?.code !== 'ENOENT') throw error
|
|
9
|
-
}
|
|
10
|
-
writeFileSync(path, content)
|
|
11
|
-
return true
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function copyFileIfChanged(source: string, target: string): boolean {
|
|
15
|
-
try {
|
|
16
|
-
if (readFileSync(target).equals(readFileSync(source))) return false
|
|
17
|
-
} catch (error: any) {
|
|
18
|
-
if (error?.code !== 'ENOENT') throw error
|
|
19
|
-
}
|
|
20
|
-
copyFileSync(source, target)
|
|
21
|
-
return true
|
|
22
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { createHmac, createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto'
|
|
2
|
-
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { dirname, join } from 'node:path'
|
|
4
|
-
import { spexcodeHome } from './layout.js'
|
|
5
|
-
|
|
6
|
-
export type Verifier = { algo: 'scrypt'; N: number; r: number; p: number; salt: string; hash: string; gen: string }
|
|
7
|
-
export type AuthStore = { v: 1; secret: string; admin?: Verifier; projects: Record<string, Verifier> }
|
|
8
|
-
export type Scope = { s: 'admin' } | { s: 'project'; p: string }
|
|
9
|
-
export type Claims = { v: 1; s: 'admin' | 'project'; p?: string; g: string; t: number }
|
|
10
|
-
export type Decision =
|
|
11
|
-
| { ok: true; via: 'admin' | 'project' | 'open' | 'loopback' }
|
|
12
|
-
| { ok: false; reason: 'admin-login' | 'locked' | 'project-login' }
|
|
13
|
-
|
|
14
|
-
const TOKEN_TTL_MS = 30 * 24 * 3600 * 1000 // matches the single-project gateway's 30-day cookie
|
|
15
|
-
const SCRYPT = { N: 16384, r: 8, p: 1 } as const
|
|
16
|
-
|
|
17
|
-
// ---- the private per-user store --------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
export function authStorePath(): string {
|
|
20
|
-
return join(spexcodeHome(), 'gateway', 'auth.json')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// first load creates the store (and its random signing secret) so sessions survive restarts; the file is
|
|
24
|
-
// 0600 in a 0700 dir — this is a per-USER secret store, no other principal has business reading it.
|
|
25
|
-
export function loadAuthStore(): AuthStore {
|
|
26
|
-
try {
|
|
27
|
-
const raw = JSON.parse(readFileSync(authStorePath(), 'utf8'))
|
|
28
|
-
if (raw?.v === 1 && typeof raw.secret === 'string' && raw.secret) return { v: 1, secret: raw.secret, admin: raw.admin, projects: raw.projects ?? {} }
|
|
29
|
-
} catch { /* absent or unreadable → fresh below; malformed secrets must not be half-trusted */ }
|
|
30
|
-
const fresh: AuthStore = { v: 1, secret: randomBytes(32).toString('base64url'), projects: {} }
|
|
31
|
-
saveAuthStore(fresh)
|
|
32
|
-
return fresh
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function saveAuthStore(store: AuthStore): void {
|
|
36
|
-
const file = authStorePath()
|
|
37
|
-
mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
|
|
38
|
-
const tmp = `${file}.tmp`
|
|
39
|
-
writeFileSync(tmp, JSON.stringify(store, null, 2) + '\n', { mode: 0o600 })
|
|
40
|
-
renameSync(tmp, file)
|
|
41
|
-
chmodSync(file, 0o600) // rename preserves the tmp mode, but never let a pre-existing looser file win
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ---- password verifiers (scrypt, salted, constant-time) --------------------------------------------
|
|
45
|
-
|
|
46
|
-
export function makeVerifier(password: string): Verifier {
|
|
47
|
-
const salt = randomBytes(16)
|
|
48
|
-
const hash = scryptSync(password, salt, 32, SCRYPT)
|
|
49
|
-
return { algo: 'scrypt', ...SCRYPT, salt: salt.toString('base64url'), hash: hash.toString('base64url'), gen: randomBytes(8).toString('base64url') }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function verifyPassword(v: Verifier | undefined, password: string): boolean {
|
|
53
|
-
if (!v || v.algo !== 'scrypt' || typeof password !== 'string') return false
|
|
54
|
-
const got = scryptSync(password, Buffer.from(v.salt, 'base64url'), 32, { N: v.N, r: v.r, p: v.p })
|
|
55
|
-
return constEq(got, Buffer.from(v.hash, 'base64url'))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function constEq(a: Buffer | string, b: Buffer | string): boolean {
|
|
59
|
-
const ab = Buffer.isBuffer(a) ? a : Buffer.from(a)
|
|
60
|
-
const bb = Buffer.isBuffer(b) ? b : Buffer.from(b)
|
|
61
|
-
return ab.length === bb.length && timingSafeEqual(ab, bb)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ---- verifier management (the gateway admin APIs write through these) ------------------------------
|
|
65
|
-
|
|
66
|
-
export function setAdminPassword(password: string): AuthStore {
|
|
67
|
-
const store = loadAuthStore()
|
|
68
|
-
store.admin = makeVerifier(password)
|
|
69
|
-
saveAuthStore(store)
|
|
70
|
-
return store
|
|
71
|
-
}
|
|
72
|
-
export function clearAdminPassword(): AuthStore {
|
|
73
|
-
const store = loadAuthStore()
|
|
74
|
-
delete store.admin
|
|
75
|
-
saveAuthStore(store)
|
|
76
|
-
return store
|
|
77
|
-
}
|
|
78
|
-
export function setProjectPassword(projectId: string, password: string): AuthStore {
|
|
79
|
-
const store = loadAuthStore()
|
|
80
|
-
store.projects[projectId] = makeVerifier(password)
|
|
81
|
-
saveAuthStore(store)
|
|
82
|
-
return store
|
|
83
|
-
}
|
|
84
|
-
export function clearProjectPassword(projectId: string): AuthStore {
|
|
85
|
-
const store = loadAuthStore()
|
|
86
|
-
delete store.projects[projectId]
|
|
87
|
-
saveAuthStore(store)
|
|
88
|
-
return store
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ---- signed session tokens --------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
function sign(secret: string, payload: string): string {
|
|
94
|
-
return createHmac('sha256', Buffer.from(secret, 'base64url')).update(payload).digest('base64url')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// a token exists only for a scope that has a verifier — its `g` claim is that verifier's current gen.
|
|
98
|
-
export function mintToken(store: AuthStore, scope: Scope, nowMs = Date.now()): string {
|
|
99
|
-
const v = scope.s === 'admin' ? store.admin : store.projects[scope.p]
|
|
100
|
-
if (!v) throw new Error(`gateway-auth: cannot mint a ${scope.s} token with no verifier configured`)
|
|
101
|
-
const claims: Claims = { v: 1, s: scope.s, ...(scope.s === 'project' ? { p: scope.p } : {}), g: v.gen, t: nowMs }
|
|
102
|
-
const payload = Buffer.from(JSON.stringify(claims)).toString('base64url')
|
|
103
|
-
return `${payload}.${sign(store.secret, payload)}`
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// full validity lives here: signature (constant-time), shape, expiry, and the gen check against the
|
|
107
|
-
// CURRENT verifier — a cleared or re-set password leaves no valid tokens behind.
|
|
108
|
-
export function verifyToken(store: AuthStore, token: string, nowMs = Date.now()): Claims | null {
|
|
109
|
-
const dot = token.indexOf('.')
|
|
110
|
-
if (dot <= 0) return null
|
|
111
|
-
const payload = token.slice(0, dot), sig = token.slice(dot + 1)
|
|
112
|
-
if (!constEq(sig, sign(store.secret, payload))) return null
|
|
113
|
-
let claims: Claims
|
|
114
|
-
try { claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) } catch { return null }
|
|
115
|
-
if (claims?.v !== 1 || typeof claims.t !== 'number' || typeof claims.g !== 'string') return null
|
|
116
|
-
if (nowMs > claims.t + TOKEN_TTL_MS || claims.t > nowMs + 60_000) return null
|
|
117
|
-
if (claims.s === 'admin') return store.admin && constEq(claims.g, store.admin.gen) ? claims : null
|
|
118
|
-
if (claims.s === 'project' && typeof claims.p === 'string') {
|
|
119
|
-
const v = store.projects[claims.p]
|
|
120
|
-
return v && constEq(claims.g, v.gen) ? claims : null
|
|
121
|
-
}
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ---- cookies ----------------------------------------------------------------------------------------
|
|
126
|
-
// Cookie names are keyed by the gateway's public port (cookies are host-scoped; the port is the
|
|
127
|
-
// discriminator — same rationale as the single-project gateway) and, for project scopes, by a hash of the
|
|
128
|
-
// projectId (ids are path-derived and may hold non-token chars). The NAME is only a mailbox: authorization
|
|
129
|
-
// always re-validates the token's own projectId claim against the requested route — never the cookie's
|
|
130
|
-
// name, and never its Path attribute, both of which are client-controlled.
|
|
131
|
-
|
|
132
|
-
export function adminCookieName(port: number): string { return `spex_admin_${port}` }
|
|
133
|
-
export function projectCookieName(port: number, projectId: string): string {
|
|
134
|
-
return `spex_proj_${port}_${createHash('sha256').update(projectId).digest('hex').slice(0, 12)}`
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function cookieOf(header: string | undefined, name: string): string | null {
|
|
138
|
-
for (const part of (header ?? '').split(';')) {
|
|
139
|
-
const eq = part.indexOf('=')
|
|
140
|
-
if (eq > 0 && part.slice(0, eq).trim() === name) return decodeURIComponent(part.slice(eq + 1).trim())
|
|
141
|
-
}
|
|
142
|
-
return null
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ---- loopback ---------------------------------------------------------------------------------------
|
|
146
|
-
// The implicit-admin decision reads the SOCKET's remote address only. Never a header (X-Forwarded-For,
|
|
147
|
-
// X-Real-IP): headers are attacker-controlled, and this gateway is designed to face the internet directly,
|
|
148
|
-
// not to sit behind a trusted proxy.
|
|
149
|
-
export function isLoopback(addr: string | null | undefined): boolean {
|
|
150
|
-
if (!addr) return false
|
|
151
|
-
const a = addr.startsWith('::ffff:') ? addr.slice(7) : addr
|
|
152
|
-
return a === '::1' || /^127(\.\d{1,3}){3}$/.test(a)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ---- the decision -----------------------------------------------------------------------------------
|
|
156
|
-
|
|
157
|
-
export type Route = { kind: 'admin' } | { kind: 'project'; projectId: string }
|
|
158
|
-
|
|
159
|
-
export function authorize(store: AuthStore, route: Route, cookieHeader: string | undefined, remoteAddr: string | null | undefined, port: number): Decision {
|
|
160
|
-
const adminTok = cookieOf(cookieHeader, adminCookieName(port))
|
|
161
|
-
const adminClaims = adminTok ? verifyToken(store, adminTok) : null
|
|
162
|
-
const adminOk = adminClaims?.s === 'admin'
|
|
163
|
-
|
|
164
|
-
if (route.kind === 'admin') {
|
|
165
|
-
if (adminOk) return { ok: true, via: 'admin' }
|
|
166
|
-
// no admin verifier configured: loopback manages implicitly (the bootstrap path — set the first
|
|
167
|
-
// password from the machine itself); anyone else finds /projects locked, not open.
|
|
168
|
-
if (!store.admin) return isLoopback(remoteAddr) ? { ok: true, via: 'loopback' } : { ok: false, reason: 'locked' }
|
|
169
|
-
return { ok: false, reason: 'admin-login' }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// project route — admin reaches every project; then the project's own gate decides.
|
|
173
|
-
if (adminOk) return { ok: true, via: 'admin' }
|
|
174
|
-
if (!store.projects[route.projectId]) return { ok: true, via: 'open' }
|
|
175
|
-
const tok = cookieOf(cookieHeader, projectCookieName(port, route.projectId))
|
|
176
|
-
const claims = tok ? verifyToken(store, tok) : null
|
|
177
|
-
// the projectId CLAIM must match the route — presenting project A's token under project B's cookie
|
|
178
|
-
// name (or any Path trick) authorizes nothing.
|
|
179
|
-
if (claims?.s === 'project' && claims.p === route.projectId) return { ok: true, via: 'project' }
|
|
180
|
-
return { ok: false, reason: 'project-login' }
|
|
181
|
-
}
|