spexcode 0.6.2 → 0.6.6
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/session-core/dist/delivery-queue.d.ts +23 -0
- package/node_modules/@spexcode/session-core/dist/delivery-queue.js +195 -0
- package/node_modules/@spexcode/session-core/dist/index.d.ts +5 -0
- package/node_modules/@spexcode/session-core/dist/index.js +5 -0
- package/node_modules/@spexcode/session-core/dist/internal.d.ts +3 -0
- package/node_modules/@spexcode/session-core/dist/internal.js +3 -0
- package/node_modules/@spexcode/session-core/dist/message.d.ts +22 -0
- package/node_modules/@spexcode/session-core/dist/message.js +52 -0
- package/node_modules/@spexcode/session-core/dist/record-lock.d.ts +7 -0
- package/node_modules/@spexcode/session-core/dist/record-lock.js +152 -0
- package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +50 -0
- package/node_modules/@spexcode/session-core/dist/runtime-session.js +286 -0
- package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +14 -0
- package/node_modules/@spexcode/session-core/dist/session-cursors.js +82 -0
- package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +46 -0
- package/node_modules/@spexcode/session-core/dist/session-timeline.js +216 -0
- package/node_modules/@spexcode/session-core/package.json +33 -0
- package/node_modules/@spexcode/spec-cli/bin/spex.mjs +77 -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 +36 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +316 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.js +1908 -0
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +112 -0
- package/node_modules/@spexcode/spec-cli/dist/client.js +481 -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 +58 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +798 -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/doctor.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +553 -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 +363 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +38 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +527 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +717 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +36 -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 +338 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.js +3146 -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 +15 -0
- package/node_modules/@spexcode/spec-cli/dist/help.js +594 -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 +8 -0
- package/node_modules/@spexcode/spec-cli/dist/listen.js +24 -0
- 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 +5 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +215 -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/runtime-rotate.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.js +58 -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-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 +11 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +20 -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 +433 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +4667 -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 +49 -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 +170 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +2768 -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 +187 -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/listen.ts +0 -28
- 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
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { rebasePublishedSessions } from '@spexcode/spec-core';
|
|
5
|
+
import { buildBoard, spliceSessions } from './graphSnapshot.js';
|
|
6
|
+
import { headSha, repoRoot, requireGitWorkspace, withGitAbortSignal } from '@spexcode/spec-core';
|
|
7
|
+
import { listSessionIds, mainBranch, mainCheckout, readPublicRecordEntry, sessionArtifactPath, sessionRecordPath } from '@spexcode/spec-core';
|
|
8
|
+
import { boardThreads } from './issues.js';
|
|
9
|
+
import { resolveForgeHost } from '@spexcode/spec-forge/drivers';
|
|
10
|
+
import { residentForgeState } from '@spexcode/spec-forge/resident';
|
|
11
|
+
import { resolveProjectIdentity } from '@spexcode/spec-core';
|
|
12
|
+
import { readReviewSnapshot } from '@spexcode/spec-core';
|
|
13
|
+
import { sessionEvalProjection } from '@spexcode/spec-eval/sessioneval';
|
|
14
|
+
const DEBUG = process.env.SPEXCODE_BOARD_DEBUG === '1';
|
|
15
|
+
function textOrNull(path) {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(path, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code === 'ENOENT')
|
|
21
|
+
return null;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function gitDirOf(root) {
|
|
26
|
+
const dotgit = join(root, '.git');
|
|
27
|
+
try {
|
|
28
|
+
if (statSync(dotgit).isDirectory())
|
|
29
|
+
return dotgit;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.code === 'ENOENT')
|
|
33
|
+
return '';
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
const dotgitText = textOrNull(dotgit);
|
|
37
|
+
if (dotgitText === null)
|
|
38
|
+
return '';
|
|
39
|
+
const match = dotgitText.match(/^gitdir:\s*(.+)$/m);
|
|
40
|
+
if (!match)
|
|
41
|
+
throw new Error(`unparseable gitdir pointer at ${dotgit}`);
|
|
42
|
+
const dir = match[1].trim();
|
|
43
|
+
return isAbsolute(dir) ? dir : resolve(root, dir);
|
|
44
|
+
}
|
|
45
|
+
function commonDirOf(root) {
|
|
46
|
+
const gitDir = gitDirOf(root);
|
|
47
|
+
if (!gitDir)
|
|
48
|
+
return '';
|
|
49
|
+
const common = textOrNull(join(gitDir, 'commondir'));
|
|
50
|
+
if (common === null)
|
|
51
|
+
return gitDir;
|
|
52
|
+
const dir = common.trim();
|
|
53
|
+
if (!dir)
|
|
54
|
+
throw new Error(`empty commondir pointer at ${join(gitDir, 'commondir')}`);
|
|
55
|
+
return isAbsolute(dir) ? dir : resolve(gitDir, dir);
|
|
56
|
+
}
|
|
57
|
+
function refSha(root, ref) {
|
|
58
|
+
if (/^[0-9a-f]{40}$/.test(ref))
|
|
59
|
+
return ref;
|
|
60
|
+
const common = commonDirOf(root);
|
|
61
|
+
if (!common)
|
|
62
|
+
return '<missing>';
|
|
63
|
+
const names = ref.startsWith('refs/') ? [ref] : [`refs/heads/${ref}`, `refs/remotes/${ref}`];
|
|
64
|
+
for (const name of names) {
|
|
65
|
+
const loose = textOrNull(join(common, name));
|
|
66
|
+
if (loose !== null)
|
|
67
|
+
return loose.trim() || '<missing>';
|
|
68
|
+
}
|
|
69
|
+
const packed = textOrNull(join(common, 'packed-refs'));
|
|
70
|
+
if (packed !== null) {
|
|
71
|
+
for (const line of packed.split('\n')) {
|
|
72
|
+
if (!line || line[0] === '#' || line[0] === '^')
|
|
73
|
+
continue;
|
|
74
|
+
const space = line.indexOf(' ');
|
|
75
|
+
if (space > 0 && names.includes(line.slice(space + 1).trim()))
|
|
76
|
+
return line.slice(0, space).trim();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return '<missing>';
|
|
80
|
+
}
|
|
81
|
+
function headRevision(root) {
|
|
82
|
+
if (!gitDirOf(root))
|
|
83
|
+
return '<missing>';
|
|
84
|
+
return headSha(root);
|
|
85
|
+
}
|
|
86
|
+
// Patrol validation needs a stronger contract than the overlay cache's best-effort mtime key: ctime catches
|
|
87
|
+
// a same-size edit whose mtime was restored, and every non-ENOENT read failure is loud instead of collapsing an
|
|
88
|
+
// unreadable subtree into a stable "unchanged" revision. A path that vanishes during the sample is represented
|
|
89
|
+
// by its absence and is rechecked by the producer fence.
|
|
90
|
+
function strictSpecTreeRevision(wtPath) {
|
|
91
|
+
const root = join(wtPath, '.spec');
|
|
92
|
+
try {
|
|
93
|
+
statSync(root);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error.code === 'ENOENT')
|
|
97
|
+
return '';
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
const parts = [];
|
|
101
|
+
const stack = [root];
|
|
102
|
+
while (stack.length) {
|
|
103
|
+
const dir = stack.pop();
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error.code !== 'ENOENT')
|
|
110
|
+
throw error;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
const path = join(dir, entry.name);
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
stack.push(path);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const stat = statSync(path);
|
|
121
|
+
parts.push(`${path}:${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error.code !== 'ENOENT')
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return parts.sort().join('\n');
|
|
130
|
+
}
|
|
131
|
+
function worktreeRevision(root) {
|
|
132
|
+
return {
|
|
133
|
+
root,
|
|
134
|
+
head: headRevision(root),
|
|
135
|
+
spec: strictSpecTreeRevision(root),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function digest(value) {
|
|
139
|
+
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
140
|
+
}
|
|
141
|
+
// The patrol's cheap authority check. These are exactly the mutable inputs a board assembly reads, folded
|
|
142
|
+
// without doing the assembly: HEAD/config + `.spec`, governed records and active governed worktree state,
|
|
143
|
+
// the issue-store carrier, and the already-resident session-eval projections. It intentionally does not call
|
|
144
|
+
// listSessions or sessionEvalProjections: verification must neither poll tmux again nor mint/schedule eval work.
|
|
145
|
+
// The hot/warm liveness signatures and eval generations remain graph-stream's canonical event-owned axes.
|
|
146
|
+
function sessionInputRevision() {
|
|
147
|
+
const ids = listSessionIds().sort();
|
|
148
|
+
// listSessions projects the structured record plus a one-line preview of the separately-stored originating
|
|
149
|
+
// prompt into each board row. Fold both exact artifacts so a missed store event cannot leave a stale
|
|
150
|
+
// label/preview forever — the prompt file is folded because the preview is derived from it.
|
|
151
|
+
const sessionInputs = ids.map((id) => [
|
|
152
|
+
id,
|
|
153
|
+
textOrNull(sessionRecordPath(id)),
|
|
154
|
+
textOrNull(sessionArtifactPath(id, 'prompt')),
|
|
155
|
+
]);
|
|
156
|
+
const projections = digest(ids.map((id) => [id, sessionEvalProjection(id)]));
|
|
157
|
+
const activeRoots = [...new Set(ids.flatMap((id) => {
|
|
158
|
+
const entry = readPublicRecordEntry(id);
|
|
159
|
+
return entry.kind === 'ok' && entry.raw.governed && !entry.raw.archived ? [entry.raw.worktree_path] : [];
|
|
160
|
+
}))].sort();
|
|
161
|
+
return { sessions: digest(sessionInputs), projections, projectionIds: ids, activeRoots };
|
|
162
|
+
}
|
|
163
|
+
function boardInputRevision(board) {
|
|
164
|
+
const root = repoRoot();
|
|
165
|
+
requireGitWorkspace(root);
|
|
166
|
+
const session = sessionInputRevision();
|
|
167
|
+
// Durable active records are the current root set; a cached ordinary row may be stale and must not replace
|
|
168
|
+
// them. The one projection-only addition is explicit: listSessions republishes an archived-runtime hazard
|
|
169
|
+
// into the working set, and that marked row keeps its root monitored until cold state is repaired.
|
|
170
|
+
const activeRoots = [...new Set([
|
|
171
|
+
...session.activeRoots,
|
|
172
|
+
...(board?.sessions.flatMap((row) => row.archiveHazard ? [row.path] : []) ?? []),
|
|
173
|
+
])].sort();
|
|
174
|
+
const main = mainCheckout();
|
|
175
|
+
const base = mainBranch();
|
|
176
|
+
const mainTip = refSha(main, base);
|
|
177
|
+
const nodeIds = (board?.nodes ?? []).map((node) => node.id);
|
|
178
|
+
const issuesStamp = boardThreads({ host: resolveForgeHost(), state: residentForgeState() }, nodeIds).stamp;
|
|
179
|
+
const worktreeParts = Object.fromEntries(activeRoots.map((worktree) => [worktree, digest(worktreeRevision(worktree))]));
|
|
180
|
+
const fullInputs = {
|
|
181
|
+
root: worktreeRevision(root),
|
|
182
|
+
config: [
|
|
183
|
+
[join(root, 'spexcode.json'), textOrNull(join(root, 'spexcode.json'))],
|
|
184
|
+
[join(root, 'spexcode.local.json'), textOrNull(join(root, 'spexcode.local.json'))],
|
|
185
|
+
[join(main, 'spexcode.json'), textOrNull(join(main, 'spexcode.json'))],
|
|
186
|
+
[join(main, 'spexcode.local.json'), textOrNull(join(main, 'spexcode.local.json'))],
|
|
187
|
+
],
|
|
188
|
+
main: { root: main, branch: base, tip: mainTip },
|
|
189
|
+
worktrees: worktreeParts,
|
|
190
|
+
issuesStamp,
|
|
191
|
+
identity: resolveProjectIdentity(root, root),
|
|
192
|
+
};
|
|
193
|
+
const fullParts = Object.fromEntries(Object.entries(fullInputs).map(([key, value]) => [key, digest(value)]));
|
|
194
|
+
const full = digest(fullParts);
|
|
195
|
+
return {
|
|
196
|
+
full,
|
|
197
|
+
...session,
|
|
198
|
+
combined: digest([full, session.sessions, session.projections]),
|
|
199
|
+
fullParts,
|
|
200
|
+
worktreeParts,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Bind an input sample to what the completed board actually carries. The sample supplies the graph/session
|
|
204
|
+
// revision observed immediately after the producer; issue and projection values come from the returned board
|
|
205
|
+
// itself. Re-reading all inputs here would create a certification race: a blind change after the producer's
|
|
206
|
+
// after-sample could otherwise be mistaken for an input the already-built board contains.
|
|
207
|
+
function revisionCarriedByBoard(sample, board) {
|
|
208
|
+
const fullParts = { ...sample.fullParts, issuesStamp: digest(board.issuesStamp) };
|
|
209
|
+
const full = digest(fullParts);
|
|
210
|
+
const boardProjections = new Map(board.sessions.map((session) => [session.id, session.evalSummary ?? null]));
|
|
211
|
+
const projections = digest(sample.projectionIds.map((id) => [id, boardProjections.get(id) ?? null]));
|
|
212
|
+
return {
|
|
213
|
+
full,
|
|
214
|
+
sessions: sample.sessions,
|
|
215
|
+
projections,
|
|
216
|
+
combined: digest([full, sample.sessions, projections]),
|
|
217
|
+
fullParts,
|
|
218
|
+
worktreeParts: sample.worktreeParts,
|
|
219
|
+
projectionIds: sample.projectionIds,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// A sessions splice reuses its base topology. It may sample fresh record/projection inputs, but it must never
|
|
223
|
+
// certify that those old nodes carry a full revision sampled after the base was built.
|
|
224
|
+
function revisionCarriedBySessionSplice(base, sample, board, stable) {
|
|
225
|
+
const boardProjections = new Map(board.sessions.map((session) => [session.id, session.evalSummary ?? null]));
|
|
226
|
+
const projections = stable
|
|
227
|
+
? digest(sample.projectionIds.map((id) => [id, boardProjections.get(id) ?? null]))
|
|
228
|
+
: sample.projections;
|
|
229
|
+
return {
|
|
230
|
+
full: base.full,
|
|
231
|
+
sessions: sample.sessions,
|
|
232
|
+
projections,
|
|
233
|
+
combined: digest([base.full, sample.sessions, projections]),
|
|
234
|
+
fullParts: base.fullParts,
|
|
235
|
+
worktreeParts: base.worktreeParts,
|
|
236
|
+
projectionIds: sample.projectionIds,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// The full producer's topology may be newer than the last published session projection. When completion
|
|
240
|
+
// re-bases that already-visible projection onto the new topology, keep the full carrier from the producer and
|
|
241
|
+
// only the sessions/projections carrier from the published rows. Do not sample current inputs here: that would
|
|
242
|
+
// certify a write that neither producer actually carried.
|
|
243
|
+
function revisionCarriedByPublishedSessionRebase(full, published) {
|
|
244
|
+
return {
|
|
245
|
+
full: full.full,
|
|
246
|
+
sessions: published.sessions,
|
|
247
|
+
projections: published.projections,
|
|
248
|
+
combined: digest([full.full, published.sessions, published.projections]),
|
|
249
|
+
fullParts: full.fullParts,
|
|
250
|
+
worktreeParts: full.worktreeParts,
|
|
251
|
+
projectionIds: published.projectionIds,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function carrySubtractiveWorktrees(base, activeRoots) {
|
|
255
|
+
const active = new Set(activeRoots);
|
|
256
|
+
const known = Object.keys(base.worktreeParts);
|
|
257
|
+
if (activeRoots.some((root) => !(root in base.worktreeParts)))
|
|
258
|
+
return { revision: base, added: true };
|
|
259
|
+
if (known.every((root) => active.has(root)))
|
|
260
|
+
return { revision: base, added: false };
|
|
261
|
+
const worktreeParts = Object.fromEntries(Object.entries(base.worktreeParts).filter(([root]) => active.has(root)));
|
|
262
|
+
const fullParts = { ...base.fullParts, worktrees: digest(worktreeParts) };
|
|
263
|
+
const full = digest(fullParts);
|
|
264
|
+
return {
|
|
265
|
+
revision: {
|
|
266
|
+
...base,
|
|
267
|
+
full,
|
|
268
|
+
fullParts,
|
|
269
|
+
worktreeParts,
|
|
270
|
+
combined: digest([full, base.sessions, base.projections]),
|
|
271
|
+
},
|
|
272
|
+
added: false,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function isPureSubtractiveWorktreeMove(base, sample) {
|
|
276
|
+
const moved = Object.keys(sample.fullParts).filter((key) => sample.fullParts[key] !== base.fullParts[key]);
|
|
277
|
+
if (moved.length !== 1 || moved[0] !== 'worktrees')
|
|
278
|
+
return false;
|
|
279
|
+
const current = Object.keys(sample.worktreeParts);
|
|
280
|
+
const previous = Object.keys(base.worktreeParts);
|
|
281
|
+
return current.length < previous.length
|
|
282
|
+
&& current.every((root) => sample.worktreeParts[root] === base.worktreeParts[root]);
|
|
283
|
+
}
|
|
284
|
+
// a build slower than this is LOGGED, never silently tolerated — the fail-loud regression alarm. Sized
|
|
285
|
+
// above a warm build (~sub-second once the fs walks yield) but below the cold two-walk first build, so a
|
|
286
|
+
// genuinely-degraded hot path shouts while an ordinary cold start stays quiet-ish.
|
|
287
|
+
const BUDGET_MS = Number(process.env.SPEXCODE_BOARD_BUDGET_MS || 1500);
|
|
288
|
+
// a build that NEVER settles is a different animal from a slow one: `inflight` clears only in the finally
|
|
289
|
+
// below, so a never-settling buildBoard() would pin the single-flight forever — every later read (even of a
|
|
290
|
+
// perfectly good cached board) short-circuits into the pinned promise before `valid` is consulted,
|
|
291
|
+
// invalidation can't help, no log ever fires, and only a restart cures it (the live wedge: hung git
|
|
292
|
+
// children → /api/graph 503 forever, silently). So the build races a generous watchdog that REJECTS loudly;
|
|
293
|
+
// the rejection flows through the SAME finally → inflight clears → the next read retries fresh. Sitting at
|
|
294
|
+
// the single-flight boundary, this one wall bounds every never-settle cause — including ones with no child
|
|
295
|
+
// process at all (fs/promises under libuv threadpool starvation); git.ts's per-child timeouts merely make
|
|
296
|
+
// the common cause die sooner. Generous: well above the slowest legitimate cold build, so it only ever
|
|
297
|
+
// fires on a genuine wedge.
|
|
298
|
+
const BUILD_TIMEOUT_MS = Number(process.env.SPEXCODE_BOARD_BUILD_TIMEOUT_MS || 120000);
|
|
299
|
+
const RETRY_BACKOFF_MS = Number(process.env.SPEXCODE_BOARD_RETRY_BACKOFF_MS || 1000);
|
|
300
|
+
const BACKGROUND_START_DELAY_MS = Number(process.env.SPEXCODE_BOARD_BACKGROUND_START_DELAY_MS || 300);
|
|
301
|
+
let cached = null; // last completed build; served while `dirty === 'none'`
|
|
302
|
+
let cachedJson = null; // JSON.stringify(cached), serialized ONCE per build (see getBoardJson)
|
|
303
|
+
let cachedRevision = null; // input revision represented by `cached`
|
|
304
|
+
let dirty = 'full'; // no cached board yet → the first read builds fully
|
|
305
|
+
let inflight = null;
|
|
306
|
+
let sessionFlight = null;
|
|
307
|
+
let sessionOwed = false;
|
|
308
|
+
let sessionGeneration = 0;
|
|
309
|
+
let sessionProjectionPublication = 0;
|
|
310
|
+
let topologyGeneration = 0;
|
|
311
|
+
let gen = 0; // bumped on invalidation so patrol validation cannot certify across an event
|
|
312
|
+
let retryAt = 0;
|
|
313
|
+
let lastFailure = null;
|
|
314
|
+
// mark the cache stale at a SCOPE. Called by every board-stream freshness source (see
|
|
315
|
+
// boardStream.fireChanged), so a real change forces the next getBoard() to rebuild while a quiet poll storm
|
|
316
|
+
// keeps hitting the cache. `dirty` is the structural producer's scope; `sessionOwed` is intentionally not
|
|
317
|
+
// folded into it, because full+sessions owes both a full convergence and a cheap first projection.
|
|
318
|
+
function mergeDirty(scope) {
|
|
319
|
+
if (scope === 'full' || dirty === 'full')
|
|
320
|
+
dirty = 'full';
|
|
321
|
+
else
|
|
322
|
+
dirty = 'sessions';
|
|
323
|
+
}
|
|
324
|
+
export function invalidateBoard(scope = 'full') {
|
|
325
|
+
gen++;
|
|
326
|
+
if (scope === 'sessions') {
|
|
327
|
+
sessionOwed = true;
|
|
328
|
+
sessionGeneration++;
|
|
329
|
+
}
|
|
330
|
+
mergeDirty(scope);
|
|
331
|
+
retryAt = 0;
|
|
332
|
+
lastFailure = null;
|
|
333
|
+
}
|
|
334
|
+
function traceCacheCommit(scope, startedAt) {
|
|
335
|
+
if (!DEBUG)
|
|
336
|
+
return;
|
|
337
|
+
const at = Date.now();
|
|
338
|
+
console.warn(`spec-cli: graph cache ${JSON.stringify({ at, stage: 'cache-commit', scope, buildMs: at - startedAt })}`);
|
|
339
|
+
}
|
|
340
|
+
function startSessionSplice() {
|
|
341
|
+
if (!cached || !cachedRevision || !sessionOwed)
|
|
342
|
+
return null;
|
|
343
|
+
if (sessionFlight)
|
|
344
|
+
return sessionFlight;
|
|
345
|
+
sessionOwed = false;
|
|
346
|
+
if (dirty === 'sessions')
|
|
347
|
+
dirty = 'none';
|
|
348
|
+
let timedOut = false;
|
|
349
|
+
const startedAt = Date.now();
|
|
350
|
+
let watchdog;
|
|
351
|
+
const timeoutError = () => new Error(`graph session splice did not settle within ${BUILD_TIMEOUT_MS}ms`);
|
|
352
|
+
const producer = Promise.resolve().then(async () => {
|
|
353
|
+
// A full completion may replace the topology while listSessions is in flight. Rebase before publishing;
|
|
354
|
+
// the splice is a projection over whatever last-good structure is current, never a whole-board write race.
|
|
355
|
+
while (true) {
|
|
356
|
+
const base = cached, revision = cachedRevision, generation = topologyGeneration;
|
|
357
|
+
if (!base || !revision)
|
|
358
|
+
throw new Error('graph session splice lost its cached topology');
|
|
359
|
+
const before = sessionInputRevision();
|
|
360
|
+
const board = await spliceSessions(base);
|
|
361
|
+
const after = sessionInputRevision();
|
|
362
|
+
if (base !== cached || revision !== cachedRevision || generation !== topologyGeneration)
|
|
363
|
+
continue;
|
|
364
|
+
const stable = before.sessions === after.sessions && before.projections === after.projections &&
|
|
365
|
+
JSON.stringify(before.activeRoots) === JSON.stringify(after.activeRoots);
|
|
366
|
+
const projectedRoots = [...new Set(board.sessions.map((row) => row.path))].sort();
|
|
367
|
+
const rootTransition = carrySubtractiveWorktrees(revision, projectedRoots);
|
|
368
|
+
const carried = stable ? rootTransition.revision : revision;
|
|
369
|
+
cached = board;
|
|
370
|
+
cachedJson = null;
|
|
371
|
+
cachedRevision = revisionCarriedBySessionSplice(carried, before, board, stable);
|
|
372
|
+
sessionProjectionPublication++;
|
|
373
|
+
if (rootTransition.added)
|
|
374
|
+
mergeDirty('full');
|
|
375
|
+
if (!stable) {
|
|
376
|
+
sessionOwed = true;
|
|
377
|
+
sessionGeneration++;
|
|
378
|
+
mergeDirty('sessions');
|
|
379
|
+
}
|
|
380
|
+
return board;
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
.then((board) => {
|
|
384
|
+
if (timedOut)
|
|
385
|
+
throw timeoutError();
|
|
386
|
+
traceCacheCommit('sessions', startedAt);
|
|
387
|
+
return board;
|
|
388
|
+
})
|
|
389
|
+
.catch((error) => {
|
|
390
|
+
sessionOwed = true;
|
|
391
|
+
mergeDirty('sessions');
|
|
392
|
+
throw error;
|
|
393
|
+
});
|
|
394
|
+
let settle;
|
|
395
|
+
settle = producer.finally(() => {
|
|
396
|
+
clearTimeout(watchdog);
|
|
397
|
+
if (sessionFlight?.settle === settle)
|
|
398
|
+
sessionFlight = null;
|
|
399
|
+
});
|
|
400
|
+
const wait = new Promise((resolve, reject) => {
|
|
401
|
+
watchdog = setTimeout(() => {
|
|
402
|
+
timedOut = true;
|
|
403
|
+
console.warn(`spec-cli: graph session splice did not settle within ${BUILD_TIMEOUT_MS}ms`);
|
|
404
|
+
reject(timeoutError());
|
|
405
|
+
}, BUILD_TIMEOUT_MS);
|
|
406
|
+
watchdog.unref?.();
|
|
407
|
+
settle.then((board) => { if (!timedOut)
|
|
408
|
+
resolve(board); }, (error) => { if (!timedOut)
|
|
409
|
+
reject(error); });
|
|
410
|
+
});
|
|
411
|
+
const flight = { wait, settle };
|
|
412
|
+
sessionFlight = flight;
|
|
413
|
+
void wait.catch(() => { });
|
|
414
|
+
void settle.catch(() => { });
|
|
415
|
+
return flight;
|
|
416
|
+
}
|
|
417
|
+
function startBuild(mode = 'dirty') {
|
|
418
|
+
if (inflight)
|
|
419
|
+
return inflight;
|
|
420
|
+
if (Date.now() < retryAt)
|
|
421
|
+
return null;
|
|
422
|
+
const controller = new AbortController();
|
|
423
|
+
let watchdog;
|
|
424
|
+
let timedOut = false;
|
|
425
|
+
let built = false;
|
|
426
|
+
let buildStartedAt = 0;
|
|
427
|
+
let buildScope = 'full';
|
|
428
|
+
let buildFullStable = true;
|
|
429
|
+
let buildSessionsStable = true;
|
|
430
|
+
let buildSessionGeneration = sessionGeneration;
|
|
431
|
+
let buildSessionProjectionPublication = sessionProjectionPublication;
|
|
432
|
+
let buildStartedWithSessionOwed = false;
|
|
433
|
+
let buildAddedSessionRoot = false;
|
|
434
|
+
let completedRevision = null;
|
|
435
|
+
// Do not invoke the producer inline. buildBoard() has an asynchronous signature but performs a sizeable
|
|
436
|
+
// synchronous setup before its first await (Promise.all evaluates its arguments immediately). A stale HTTP
|
|
437
|
+
// caller must be able to return its last-good bytes before that setup runs; first-cold/fresh callers still
|
|
438
|
+
// await the same deferred promise below.
|
|
439
|
+
let resolveBuild;
|
|
440
|
+
let rejectBuild;
|
|
441
|
+
const build = new Promise((resolve, reject) => {
|
|
442
|
+
resolveBuild = resolve;
|
|
443
|
+
rejectBuild = reject;
|
|
444
|
+
});
|
|
445
|
+
// Give a stale HTTP response a turn to flush before the producer's synchronous setup occupies the event
|
|
446
|
+
// loop. Fresh callers simply absorb this small scheduling window while waiting on the same flight.
|
|
447
|
+
setTimeout(() => {
|
|
448
|
+
if (controller.signal.aborted) {
|
|
449
|
+
rejectBuild(Object.assign(new Error('graph build aborted before start'), { name: 'AbortError' }));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
Promise.resolve(withGitAbortSignal(controller.signal, async () => {
|
|
454
|
+
const prev = cached;
|
|
455
|
+
const anchor = cachedRevision;
|
|
456
|
+
const sampledGen = gen;
|
|
457
|
+
const before = await boardInputRevision(prev);
|
|
458
|
+
if (controller.signal.aborted)
|
|
459
|
+
throw Object.assign(new Error('graph build aborted before producer start'), { name: 'AbortError' });
|
|
460
|
+
// ONE rule for every refresh: the domain a producer runs is DERIVED from the inputs that actually
|
|
461
|
+
// moved, never assigned by whoever signalled. A watcher names the leaf it saw, not what the board
|
|
462
|
+
// reads — a generated harness artifact rewritten inside a live worktree, or a linked worktree no
|
|
463
|
+
// governed record names, moves no board input at all, and used to buy a whole structural assembly
|
|
464
|
+
// anyway. The revision sampled here is the complete full-domain contract, so an unmoved full
|
|
465
|
+
// obligation is DISCHARGED without assembly. The session projection is deliberately NOT
|
|
466
|
+
// dischargeable: liveness lives on graph-stream's poller axis, outside this revision, so a claimed
|
|
467
|
+
// sessions obligation always survives and takes its splice. A sample taken after the write is what
|
|
468
|
+
// the watcher event was about; an invalidation arriving after the sample keeps its own dirty window.
|
|
469
|
+
if (prev && anchor && gen === sampledGen) {
|
|
470
|
+
const fullMoved = before.full !== anchor.full;
|
|
471
|
+
const subtractiveWorktreeMove = fullMoved && isPureSubtractiveWorktreeMove(anchor, before);
|
|
472
|
+
const structuralMoved = fullMoved && !subtractiveWorktreeMove;
|
|
473
|
+
const projectionMoved = before.sessions !== anchor.sessions || before.projections !== anchor.projections;
|
|
474
|
+
// A projection move nobody signalled is this validation doing the patrol's repair job.
|
|
475
|
+
if ((projectionMoved || subtractiveWorktreeMove) && !sessionOwed && dirty !== 'sessions') {
|
|
476
|
+
gen++;
|
|
477
|
+
sessionOwed = true;
|
|
478
|
+
sessionGeneration++;
|
|
479
|
+
}
|
|
480
|
+
const owed = structuralMoved ? 'full' : (sessionOwed || dirty === 'sessions') ? 'sessions' : 'none';
|
|
481
|
+
if (DEBUG && owed !== 'none') {
|
|
482
|
+
const moved = Object.keys(before.fullParts).filter((key) => before.fullParts[key] !== anchor.fullParts[key]);
|
|
483
|
+
console.warn(`spec-cli: graph refresh revision moved — signalled=${dirty} scope=${owed} inputs=[${moved.join(', ')}]`);
|
|
484
|
+
}
|
|
485
|
+
else if (DEBUG && dirty !== 'none') {
|
|
486
|
+
console.warn(`spec-cli: graph refresh discharged a ${dirty} signal — no graph input moved`);
|
|
487
|
+
}
|
|
488
|
+
if (structuralMoved)
|
|
489
|
+
dirty = 'full';
|
|
490
|
+
else if (sessionOwed || dirty === 'sessions')
|
|
491
|
+
dirty = 'sessions';
|
|
492
|
+
// Discharging CONSUMES the claim. Leaving it standing would make every later read re-enter this
|
|
493
|
+
// validation, hold the board permanently stale/refreshing, and never converge.
|
|
494
|
+
else {
|
|
495
|
+
dirty = 'none';
|
|
496
|
+
return prev;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const sessionsOnly = dirty === 'sessions' && prev !== null;
|
|
500
|
+
buildScope = sessionsOnly ? 'sessions' : 'full';
|
|
501
|
+
// Consume the scope this producer is satisfying. Invalidation after this point starts a NEW dirty
|
|
502
|
+
// window with its own domain: a session completion during a long full build owes one splice, not
|
|
503
|
+
// another full build. The occupied `inflight` slot keeps fresh/stale readers joined while dirty is clean.
|
|
504
|
+
dirty = 'none';
|
|
505
|
+
if (sessionsOnly)
|
|
506
|
+
sessionOwed = false;
|
|
507
|
+
built = true;
|
|
508
|
+
buildSessionGeneration = sessionGeneration;
|
|
509
|
+
buildSessionProjectionPublication = sessionProjectionPublication;
|
|
510
|
+
buildStartedWithSessionOwed = sessionOwed;
|
|
511
|
+
buildStartedAt = Date.now();
|
|
512
|
+
const board = await (sessionsOnly ? spliceSessions(prev) : buildBoard());
|
|
513
|
+
const after = await boardInputRevision(prev);
|
|
514
|
+
const movedFull = Object.keys(after.fullParts)
|
|
515
|
+
.filter((key) => after.fullParts[key] !== before.fullParts[key]);
|
|
516
|
+
// The first build can initialize the resident forge carrier that it then returns. That movement is
|
|
517
|
+
// stable only when the finished board carries the after-sample's exact issue stamp. Every other cold
|
|
518
|
+
// movement remains dirty just like a warm one; cold start is not a blanket race exemption.
|
|
519
|
+
const coldIssueInitialization = prev === null
|
|
520
|
+
&& movedFull.every((key) => key === 'issuesStamp')
|
|
521
|
+
&& after.fullParts.issuesStamp === digest(board.issuesStamp);
|
|
522
|
+
buildFullStable = before.full === after.full || coldIssueInitialization;
|
|
523
|
+
buildSessionsStable = before.sessions === after.sessions;
|
|
524
|
+
if (DEBUG && (!buildFullStable || !buildSessionsStable)) {
|
|
525
|
+
console.warn(`spec-cli: graph inputs moved during ${buildScope} producer — next=${buildFullStable ? 'sessions' : 'full'} inputs=[${movedFull.join(', ')}] sessions=${buildSessionsStable ? 'stable' : 'moved'}`);
|
|
526
|
+
}
|
|
527
|
+
// The anchor must name the sample this board is KNOWN to have read, because validation now discharges
|
|
528
|
+
// an unmoved obligation for every refresh, not only the patrol's. An input that moved DURING the
|
|
529
|
+
// producer leaves the board built from the pre-move value while the after-sample already names the
|
|
530
|
+
// post-move one; anchoring on `after` would let the next validation discharge the re-owed dirty scope
|
|
531
|
+
// against a revision this board never carried, and the cache would converge on nothing. So a moved
|
|
532
|
+
// half anchors on `before` — the conservative direction, which costs at most one extra rebuild and can
|
|
533
|
+
// never certify unread bytes.
|
|
534
|
+
if (sessionsOnly && anchor) {
|
|
535
|
+
const projectedRoots = [...new Set(board.sessions.map((row) => row.path))].sort();
|
|
536
|
+
const rootTransition = carrySubtractiveWorktrees(anchor, projectedRoots);
|
|
537
|
+
buildAddedSessionRoot = rootTransition.added;
|
|
538
|
+
completedRevision = revisionCarriedBySessionSplice(rootTransition.revision, before, board, buildFullStable && buildSessionsStable && !rootTransition.added);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
completedRevision = revisionCarriedByBoard(buildFullStable && buildSessionsStable ? after : before, board);
|
|
542
|
+
}
|
|
543
|
+
return board;
|
|
544
|
+
}))
|
|
545
|
+
.then(resolveBuild, rejectBuild);
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
rejectBuild(error);
|
|
549
|
+
}
|
|
550
|
+
}, mode === 'patrol' ? 0 : BACKGROUND_START_DELAY_MS).unref?.();
|
|
551
|
+
const timeoutError = () => new Error(`graph build did not settle within ${BUILD_TIMEOUT_MS}ms`);
|
|
552
|
+
// `settle` owns the real builder. The watchdog only rejects `wait`; the slot remains occupied until this
|
|
553
|
+
// promise settles, so a next read can never overlap an abandoned git/fs build.
|
|
554
|
+
let settle;
|
|
555
|
+
settle = build.then(async (board) => {
|
|
556
|
+
if (timedOut)
|
|
557
|
+
throw timeoutError();
|
|
558
|
+
if (built) {
|
|
559
|
+
// A full build's session snapshot may predate a session projection that clients already saw. Rebase those
|
|
560
|
+
// published rows in memory on the new topology. This completion path must stay bounded: a later/unpublished
|
|
561
|
+
// session generation remains owed to the independent cheap splice rather than making full wait for quiet.
|
|
562
|
+
const publishedDuringBuild = sessionProjectionPublication !== buildSessionProjectionPublication;
|
|
563
|
+
if (buildScope === 'full' && publishedDuringBuild && cached && cachedRevision) {
|
|
564
|
+
board = rebasePublishedSessions(board, cached);
|
|
565
|
+
completedRevision = revisionCarriedByPublishedSessionRebase(completedRevision, cachedRevision);
|
|
566
|
+
}
|
|
567
|
+
const sessionStillOwed = sessionOwed || (!publishedDuringBuild && (buildStartedWithSessionOwed || !buildSessionsStable || buildSessionGeneration !== sessionGeneration));
|
|
568
|
+
if (sessionStillOwed) {
|
|
569
|
+
sessionOwed = true;
|
|
570
|
+
mergeDirty('sessions');
|
|
571
|
+
}
|
|
572
|
+
cached = board;
|
|
573
|
+
cachedJson = null;
|
|
574
|
+
cachedRevision = completedRevision;
|
|
575
|
+
if (buildScope === 'full')
|
|
576
|
+
topologyGeneration++;
|
|
577
|
+
else
|
|
578
|
+
sessionProjectionPublication++;
|
|
579
|
+
if (!buildFullStable || buildAddedSessionRoot)
|
|
580
|
+
mergeDirty('full');
|
|
581
|
+
traceCacheCommit(buildScope, buildStartedAt);
|
|
582
|
+
}
|
|
583
|
+
retryAt = 0;
|
|
584
|
+
lastFailure = null;
|
|
585
|
+
return board;
|
|
586
|
+
}).catch((error) => {
|
|
587
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
588
|
+
if (built)
|
|
589
|
+
mergeDirty(buildScope);
|
|
590
|
+
lastFailure = failure;
|
|
591
|
+
retryAt = Date.now() + RETRY_BACKOFF_MS;
|
|
592
|
+
console.warn(`spec-cli: /api/graph build failed — ${failure.message}`);
|
|
593
|
+
throw failure;
|
|
594
|
+
}).finally(() => {
|
|
595
|
+
clearTimeout(watchdog);
|
|
596
|
+
if (inflight?.settle === settle)
|
|
597
|
+
inflight = null;
|
|
598
|
+
const ms = built ? Date.now() - buildStartedAt : 0;
|
|
599
|
+
if (built && ms > BUDGET_MS)
|
|
600
|
+
console.warn(`spec-cli: /api/graph build took ${ms}ms (budget ${BUDGET_MS}ms) — ${buildScope} path is slow`);
|
|
601
|
+
});
|
|
602
|
+
const wait = new Promise((resolve, reject) => {
|
|
603
|
+
watchdog = setTimeout(() => {
|
|
604
|
+
timedOut = true;
|
|
605
|
+
console.warn(`spec-cli: /api/graph build did not settle within ${BUILD_TIMEOUT_MS}ms — aborting the single-flight build`);
|
|
606
|
+
controller.abort();
|
|
607
|
+
reject(timeoutError());
|
|
608
|
+
}, BUILD_TIMEOUT_MS);
|
|
609
|
+
watchdog.unref?.();
|
|
610
|
+
settle.then((board) => {
|
|
611
|
+
if (!timedOut)
|
|
612
|
+
resolve(board);
|
|
613
|
+
}, (error) => {
|
|
614
|
+
if (!timedOut)
|
|
615
|
+
reject(error);
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
const flight = { wait, settle };
|
|
619
|
+
inflight = flight;
|
|
620
|
+
// Background stale readers intentionally do not await these promises. Observe both rejection paths so a
|
|
621
|
+
// failed build is loud without becoming an unhandled rejection.
|
|
622
|
+
void wait.catch(() => { });
|
|
623
|
+
void settle.catch(() => { });
|
|
624
|
+
return flight;
|
|
625
|
+
}
|
|
626
|
+
export function getBoard() {
|
|
627
|
+
// A clean-looking cache can be under patrol validation. Fresh readers join that flight before taking the
|
|
628
|
+
// cache fast path, otherwise one can return stale bytes while the flight is discovering a missed change.
|
|
629
|
+
if (inflight)
|
|
630
|
+
return inflight.wait;
|
|
631
|
+
if (dirty === 'full') {
|
|
632
|
+
const flight = startBuild('dirty');
|
|
633
|
+
if (flight)
|
|
634
|
+
return flight.wait;
|
|
635
|
+
}
|
|
636
|
+
if (sessionFlight)
|
|
637
|
+
return sessionFlight.wait;
|
|
638
|
+
if (dirty === 'none' && cached)
|
|
639
|
+
return Promise.resolve(cached);
|
|
640
|
+
const flight = startBuild('dirty');
|
|
641
|
+
if (flight)
|
|
642
|
+
return flight.wait;
|
|
643
|
+
return Promise.reject(lastFailure ?? new Error('graph build retry is temporarily backing off'));
|
|
644
|
+
}
|
|
645
|
+
export async function waitForPublishedRevision(required, publication) {
|
|
646
|
+
while (publication.revision() < required) {
|
|
647
|
+
publication.invalidate();
|
|
648
|
+
await publication.wait();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// A board flight can have captured a resident forge slice before its reconcile publishes a newer revision.
|
|
652
|
+
// That older flight may settle, but cannot discharge this request: its invalidation stays owed and the fence
|
|
653
|
+
// consumes the next publication instead of letting a fresh /api/issues read return the old review snapshot.
|
|
654
|
+
export async function getBoardForForgeRevision(required) {
|
|
655
|
+
await waitForPublishedRevision(required, {
|
|
656
|
+
revision: () => readReviewSnapshot().forgeRevision,
|
|
657
|
+
invalidate: () => invalidateBoard('full'),
|
|
658
|
+
wait: async () => {
|
|
659
|
+
const current = inflight;
|
|
660
|
+
if (current)
|
|
661
|
+
await current.settle;
|
|
662
|
+
else
|
|
663
|
+
await getBoard();
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
return cached ?? getBoard();
|
|
667
|
+
}
|
|
668
|
+
// Delta delivery may choose the sessions obligation while a route-owned full is still in flight. Starting
|
|
669
|
+
// the full first preserves structural convergence; returning the splice first keeps the lifecycle surface live.
|
|
670
|
+
export function getBoardForSessionRefresh() {
|
|
671
|
+
if (cached && sessionOwed) {
|
|
672
|
+
if (!inflight && dirty === 'full')
|
|
673
|
+
startBuild('dirty');
|
|
674
|
+
const flight = startSessionSplice();
|
|
675
|
+
if (flight)
|
|
676
|
+
return flight.wait;
|
|
677
|
+
}
|
|
678
|
+
return getBoard();
|
|
679
|
+
}
|
|
680
|
+
// The delta-gated cold tick calls this instead of invalidating. Equal inputs resolve to the cached object;
|
|
681
|
+
// changed inputs repair through the same flight and full producer as a watcher-owned invalidation.
|
|
682
|
+
export function patrolBoard() {
|
|
683
|
+
if (!cached)
|
|
684
|
+
return getBoard();
|
|
685
|
+
const flight = startBuild('patrol');
|
|
686
|
+
if (flight)
|
|
687
|
+
return flight.wait;
|
|
688
|
+
return Promise.reject(lastFailure ?? new Error('graph patrol retry is temporarily backing off'));
|
|
689
|
+
}
|
|
690
|
+
export async function readBoard(consistency = 'fresh') {
|
|
691
|
+
if (consistency === 'stale-ok' && cached) {
|
|
692
|
+
const stale = dirty !== 'none' || inflight !== null || sessionFlight !== null || sessionOwed;
|
|
693
|
+
// A held session splice already owns this refresh. Returning stale bytes must not manufacture a full
|
|
694
|
+
// producer beside it; a real full obligation still starts its one structural producer independently.
|
|
695
|
+
const flight = inflight
|
|
696
|
+
?? (dirty === 'full' ? startBuild('dirty') : null)
|
|
697
|
+
?? sessionFlight
|
|
698
|
+
?? (sessionOwed ? startSessionSplice() : null)
|
|
699
|
+
?? (dirty === 'sessions' ? startBuild('dirty') : null);
|
|
700
|
+
return { board: cached, freshness: stale ? 'stale' : 'fresh', refreshing: !!flight, ...(lastFailure ? { error: lastFailure.message } : {}) };
|
|
701
|
+
}
|
|
702
|
+
const board = await getBoard();
|
|
703
|
+
return { board, freshness: 'fresh', refreshing: false };
|
|
704
|
+
}
|
|
705
|
+
// the SERIALIZED board for the /api/graph route — JSON.stringify runs ONCE per build, not once per poll,
|
|
706
|
+
// so a poll storm of cache hits costs zero serialization CPU (only the etag hash for the 304 path). The SSE
|
|
707
|
+
// path still takes the object (getBoard) because it decomposes it into delta units ([[graph-delta]]).
|
|
708
|
+
export async function getBoardJson(consistency = 'fresh') {
|
|
709
|
+
const result = await readBoard(consistency);
|
|
710
|
+
const board = result.board;
|
|
711
|
+
if (board === cached && cachedJson !== null)
|
|
712
|
+
return { ...result, json: cachedJson };
|
|
713
|
+
const json = JSON.stringify(board);
|
|
714
|
+
if (board === cached)
|
|
715
|
+
cachedJson = json; // memoize only the CURRENT build's serialization
|
|
716
|
+
return { ...result, json };
|
|
717
|
+
}
|