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
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
|
2
|
-
import { readFile, readdir } from 'node:fs/promises'
|
|
3
|
-
import { createHash } from 'node:crypto'
|
|
4
|
-
import { join, relative, basename } from 'node:path'
|
|
5
|
-
import { mintIds, parseFrontmatter } from '../../spec-cli/src/specs.js'
|
|
6
|
-
import { parseRelation, type RelationEntry } from '../../spec-cli/src/anchors.js'
|
|
7
|
-
import { treeTextFiles } from '../../spec-cli/src/git.js'
|
|
8
|
-
|
|
9
|
-
export const EVAL_FILE = 'eval.md'
|
|
10
|
-
export const SIDECAR_FILE = 'evals.ndjson'
|
|
11
|
-
|
|
12
|
-
export type ScenarioTestReference = {
|
|
13
|
-
path: string
|
|
14
|
-
name?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type Scenario = {
|
|
18
|
-
name: string
|
|
19
|
-
description: string
|
|
20
|
-
expected: string
|
|
21
|
-
tags?: string[]
|
|
22
|
-
test?: ScenarioTestReference
|
|
23
|
-
code?: string[]
|
|
24
|
-
related?: string[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const SCENARIO_PROJECTION = 'spex.eval.scenario-index'
|
|
28
|
-
export const SCENARIO_SCHEMA_VERSION = 1
|
|
29
|
-
export type ScenarioSemanticRow = {
|
|
30
|
-
node: string
|
|
31
|
-
name: string
|
|
32
|
-
description: string
|
|
33
|
-
expected: string
|
|
34
|
-
scenarioHash: string
|
|
35
|
-
code: RelationEntry[]
|
|
36
|
-
related: RelationEntry[]
|
|
37
|
-
tags: string[]
|
|
38
|
-
}
|
|
39
|
-
export type ScenarioMeasurementRow = { test: ScenarioTestReference | null }
|
|
40
|
-
export type ScenarioProjectionRow = {
|
|
41
|
-
semantic: ScenarioSemanticRow
|
|
42
|
-
measurement: ScenarioMeasurementRow
|
|
43
|
-
}
|
|
44
|
-
export type ScenarioProjectionNode = {
|
|
45
|
-
id: string
|
|
46
|
-
code: RelationEntry[]
|
|
47
|
-
related: RelationEntry[]
|
|
48
|
-
}
|
|
49
|
-
export type ScenarioProjectionProvenance = { head: string | null; treeSha: string | null }
|
|
50
|
-
export type ScenarioProjection = {
|
|
51
|
-
projection: typeof SCENARIO_PROJECTION
|
|
52
|
-
schemaVersion: typeof SCENARIO_SCHEMA_VERSION
|
|
53
|
-
provenance: ScenarioProjectionProvenance
|
|
54
|
-
nodes: ScenarioProjectionNode[]
|
|
55
|
-
semanticIndexHash: string
|
|
56
|
-
fullIndexHash: string
|
|
57
|
-
planningIndexHash: string
|
|
58
|
-
rows: ScenarioProjectionRow[]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export type EvalNode = {
|
|
62
|
-
id: string
|
|
63
|
-
dir: string
|
|
64
|
-
evalPath: string
|
|
65
|
-
sidecarPath: string
|
|
66
|
-
scenarios: Scenario[]
|
|
67
|
-
evalSource?: string
|
|
68
|
-
specSource?: string
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const SCENARIO_KEYS = ['name', 'description', 'expected', 'tags', 'test', 'code', 'related'] as const
|
|
72
|
-
type ScenarioKey = (typeof SCENARIO_KEYS)[number]
|
|
73
|
-
const LIST_KEYS: readonly ScenarioKey[] = ['tags', 'code', 'related']
|
|
74
|
-
const TEST_KEYS = ['path', 'name'] as const
|
|
75
|
-
type TestKey = (typeof TEST_KEYS)[number]
|
|
76
|
-
|
|
77
|
-
type RawTestObject = {
|
|
78
|
-
fields: Partial<Record<TestKey, string>>
|
|
79
|
-
unknownKeys: string[]
|
|
80
|
-
duplicateKeys: string[]
|
|
81
|
-
malformed: string[]
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
type RawFieldLocation = { startLine: number; endLine: number; indent: string }
|
|
85
|
-
|
|
86
|
-
type RawItem = {
|
|
87
|
-
fields: Partial<Record<ScenarioKey, string>>
|
|
88
|
-
testObject?: RawTestObject
|
|
89
|
-
unknownKeys: string[]
|
|
90
|
-
duplicateKeys: string[]
|
|
91
|
-
malformed: string[]
|
|
92
|
-
locations: Partial<Record<ScenarioKey, RawFieldLocation>>
|
|
93
|
-
fieldIndent?: string
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const leadingIndent = (line: string): string => line.match(/^[ \t]*/)?.[0] ?? ''
|
|
97
|
-
|
|
98
|
-
// Keep parsing and validation on the same structural walk.
|
|
99
|
-
function walkScenarios(src: string): { hasFrontmatter: boolean; hasKey: boolean; items: RawItem[]; malformed: string[] } {
|
|
100
|
-
const normalized = src.replace(/\r\n?/g, '\n')
|
|
101
|
-
const m = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/)
|
|
102
|
-
if (!m) return { hasFrontmatter: false, hasKey: false, items: [], malformed: [] }
|
|
103
|
-
const lines = m[1].split('\n')
|
|
104
|
-
const scenarioKeys = lines.flatMap((line, index) => /^scenarios:\s*$/.test(line) ? [index] : [])
|
|
105
|
-
let i = scenarioKeys[0] ?? -1
|
|
106
|
-
if (i < 0) return { hasFrontmatter: true, hasKey: false, items: [], malformed: [] }
|
|
107
|
-
const items: RawItem[] = []
|
|
108
|
-
const malformed = scenarioKeys.length > 1
|
|
109
|
-
? [`duplicate top-level \`scenarios:\` key (${scenarioKeys.length}×) — eval.md must have exactly one declaration list`]
|
|
110
|
-
: []
|
|
111
|
-
let cur: RawItem | null = null
|
|
112
|
-
let itemIndent = -1 // the indent of the `- ` that starts each scenario (set by the first one)
|
|
113
|
-
for (i++; i < lines.length; i++) {
|
|
114
|
-
const line = lines[i]
|
|
115
|
-
if (!line.trim()) continue
|
|
116
|
-
const prefix = leadingIndent(line)
|
|
117
|
-
if (prefix.includes('\t')) malformed.push(`line ${i + 2}: tab indentation is not valid in an eval.md scenario mapping`)
|
|
118
|
-
const indent = prefix.length
|
|
119
|
-
if (indent === 0) break // dedented to another top-level key — scenarios block is done
|
|
120
|
-
const trimmed = line.trim()
|
|
121
|
-
const dash = trimmed.startsWith('- ') || trimmed === '-'
|
|
122
|
-
if (dash && (itemIndent < 0 || indent <= itemIndent)) {
|
|
123
|
-
cur = {
|
|
124
|
-
fields: {}, unknownKeys: [], duplicateKeys: [], malformed: [], locations: {},
|
|
125
|
-
...(trimmed.slice(1).trim() ? { fieldIndent: `${prefix} ` } : {}),
|
|
126
|
-
}
|
|
127
|
-
items.push(cur)
|
|
128
|
-
itemIndent = indent
|
|
129
|
-
const inline = trimmed.slice(1).trim() // text after the dash
|
|
130
|
-
if (inline) i = assignField(cur, inline, lines, i, indent, true)
|
|
131
|
-
continue
|
|
132
|
-
}
|
|
133
|
-
if (!cur) {
|
|
134
|
-
if (!trimmed.startsWith('#')) malformed.push(`invalid scenarios entry \`${trimmed}\` before the first scenario`)
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
if (!trimmed.startsWith('#')) {
|
|
138
|
-
if (cur.fieldIndent === undefined) cur.fieldIndent = prefix
|
|
139
|
-
else if (prefix !== cur.fieldIndent) {
|
|
140
|
-
cur.malformed.push(`inconsistent scenario field indentation: expected ${cur.fieldIndent.length} spaces, got ${prefix.length}`)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
i = assignField(cur, trimmed, lines, i, indent)
|
|
144
|
-
}
|
|
145
|
-
return { hasFrontmatter: true, hasKey: true, items, malformed }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function assignField(cur: RawItem, kv: string, lines: string[], idx: number, keyIndent: number, inline = false): number {
|
|
149
|
-
const f = kv.match(/^([A-Za-z_][\w-]*):\s*(.*)$/)
|
|
150
|
-
if (!f) {
|
|
151
|
-
if (!kv.startsWith('#')) cur.malformed.push(`invalid scenario entry \`${kv}\``)
|
|
152
|
-
return idx
|
|
153
|
-
}
|
|
154
|
-
const key = f[1]
|
|
155
|
-
const scenarioKey = (SCENARIO_KEYS as readonly string[]).includes(key) ? key as ScenarioKey : null
|
|
156
|
-
const finish = (parserEnd: number, locationEnd = parserEnd): number => {
|
|
157
|
-
if (scenarioKey) {
|
|
158
|
-
if (cur.locations[scenarioKey]) cur.duplicateKeys.push(key)
|
|
159
|
-
else cur.locations[scenarioKey] = {
|
|
160
|
-
startLine: idx,
|
|
161
|
-
endLine: locationEnd,
|
|
162
|
-
indent: `${leadingIndent(lines[idx])}${inline ? ' ' : ''}`,
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return parserEnd
|
|
166
|
-
}
|
|
167
|
-
if (key === 'test') {
|
|
168
|
-
const raw = f[2].trim()
|
|
169
|
-
if (!raw) {
|
|
170
|
-
const parsed = emptyTestObject()
|
|
171
|
-
let childIndent = -1
|
|
172
|
-
let lastChild = idx
|
|
173
|
-
let j = idx + 1
|
|
174
|
-
for (; j < lines.length; j++) {
|
|
175
|
-
const line = lines[j]
|
|
176
|
-
if (!line.trim()) continue
|
|
177
|
-
const prefix = leadingIndent(line)
|
|
178
|
-
if (prefix.includes('\t')) parsed.malformed.push(`tab indentation is not valid in nested \`test\` metadata`)
|
|
179
|
-
const indent = prefix.length
|
|
180
|
-
if (indent <= keyIndent) break
|
|
181
|
-
lastChild = j
|
|
182
|
-
if (childIndent < 0) childIndent = indent
|
|
183
|
-
if (indent !== childIndent) {
|
|
184
|
-
parsed.malformed.push(`invalid nested test object entry \`${line.trim()}\``)
|
|
185
|
-
continue
|
|
186
|
-
}
|
|
187
|
-
assignTestField(parsed, line.trim())
|
|
188
|
-
}
|
|
189
|
-
cur.testObject = parsed
|
|
190
|
-
return finish(j - 1, lastChild)
|
|
191
|
-
}
|
|
192
|
-
if (raw.startsWith('{') || raw.endsWith('}')) {
|
|
193
|
-
cur.testObject = parseFlowTestObject(raw)
|
|
194
|
-
return finish(idx)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if ((LIST_KEYS as readonly string[]).includes(key) && f[2].trim() === '') {
|
|
198
|
-
const items: string[] = []
|
|
199
|
-
let lastItem = idx
|
|
200
|
-
let j = idx + 1
|
|
201
|
-
for (; j < lines.length; j++) {
|
|
202
|
-
const l = lines[j]
|
|
203
|
-
if (!l.trim()) continue
|
|
204
|
-
const prefix = leadingIndent(l)
|
|
205
|
-
if (prefix.includes('\t')) cur.malformed.push(`tab indentation is not valid in \`${key}\` metadata`)
|
|
206
|
-
const ind = prefix.length
|
|
207
|
-
if (ind <= keyIndent) break
|
|
208
|
-
const it = l.trim().match(/^-\s*(.+)$/)
|
|
209
|
-
if (!it) break
|
|
210
|
-
items.push(unquote(it[1]))
|
|
211
|
-
lastItem = j
|
|
212
|
-
}
|
|
213
|
-
if (items.length) { cur.fields[key as ScenarioKey] = items.join(','); return finish(j - 1, lastItem) }
|
|
214
|
-
}
|
|
215
|
-
let value: string
|
|
216
|
-
let end = idx
|
|
217
|
-
const block = f[2].match(/^([|>])[+-]?\s*$/)
|
|
218
|
-
if (block) {
|
|
219
|
-
const fold = block[1] === '>'
|
|
220
|
-
const body: string[] = []
|
|
221
|
-
let base = -1, j = idx + 1
|
|
222
|
-
for (; j < lines.length; j++) {
|
|
223
|
-
const l = lines[j]
|
|
224
|
-
const spaces = l.match(/^ */)?.[0] ?? ''
|
|
225
|
-
const tabBeforeContent = l[spaces.length] === '\t'
|
|
226
|
-
const requiredIndent = base < 0 ? keyIndent + 1 : base
|
|
227
|
-
const tabInIndent = tabBeforeContent && spaces.length < requiredIndent
|
|
228
|
-
if (tabInIndent) cur.malformed.push(`tab indentation is not valid in block scalar \`${key}\``)
|
|
229
|
-
if (!l.trim() && !tabBeforeContent) { body.push(''); continue }
|
|
230
|
-
// A tab after the required indent is scalar content, not indentation.
|
|
231
|
-
const ind = tabInIndent ? leadingIndent(l).length : spaces.length
|
|
232
|
-
if (ind <= keyIndent) break // dedented to a sibling field / next item → the block is done
|
|
233
|
-
if (base < 0) base = ind
|
|
234
|
-
else if (ind < base) cur.malformed.push(`inconsistent block scalar indentation in \`${key}\`: expected at least ${base} spaces, got ${ind}`)
|
|
235
|
-
body.push(l.slice(base))
|
|
236
|
-
}
|
|
237
|
-
while (body.length && body[body.length - 1] === '') body.pop() // strip trailing blanks
|
|
238
|
-
value = fold ? body.join(' ').replace(/\s+/g, ' ').trim() : body.join('\n')
|
|
239
|
-
end = j - 1
|
|
240
|
-
} else {
|
|
241
|
-
value = key === 'test' ? testValue(f[2]) : unquote(f[2])
|
|
242
|
-
}
|
|
243
|
-
if ((SCENARIO_KEYS as readonly string[]).includes(key)) cur.fields[key as ScenarioKey] = value
|
|
244
|
-
else cur.unknownKeys.push(key)
|
|
245
|
-
return finish(end)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const unquote = (s: string) => s.replace(/^["'](.*)["']$/, '$1').trim()
|
|
249
|
-
|
|
250
|
-
const emptyTestObject = (): RawTestObject => ({ fields: {}, unknownKeys: [], duplicateKeys: [], malformed: [] })
|
|
251
|
-
|
|
252
|
-
function testValue(raw: string, opaque = false): string {
|
|
253
|
-
const value = raw.trim()
|
|
254
|
-
if (value.startsWith('"') && value.endsWith('"')) {
|
|
255
|
-
try { return JSON.parse(value) } catch { /* fall through to the parser's legacy quote handling */ }
|
|
256
|
-
}
|
|
257
|
-
const quoted = value.match(/^(["'])([\s\S]*)\1$/)
|
|
258
|
-
return quoted ? quoted[2] : opaque ? value : unquote(value)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function assignTestField(obj: RawTestObject, entry: string): void {
|
|
262
|
-
const f = entry.match(/^([A-Za-z_][\w-]*):\s*(.*)$/)
|
|
263
|
-
if (!f) { obj.malformed.push(`invalid test object entry \`${entry}\``); return }
|
|
264
|
-
const key = f[1]
|
|
265
|
-
if (!(TEST_KEYS as readonly string[]).includes(key)) { obj.unknownKeys.push(key); return }
|
|
266
|
-
if (key in obj.fields) obj.duplicateKeys.push(key)
|
|
267
|
-
obj.fields[key as TestKey] = testValue(f[2], key === 'name')
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Test names may contain commas inside quotes.
|
|
271
|
-
function parseFlowTestObject(raw: string): RawTestObject {
|
|
272
|
-
const obj = emptyTestObject()
|
|
273
|
-
if (!raw.startsWith('{') || !raw.endsWith('}')) {
|
|
274
|
-
obj.malformed.push('`test` object must be a mapping with exactly `path` and `name`')
|
|
275
|
-
return obj
|
|
276
|
-
}
|
|
277
|
-
const body = raw.slice(1, -1).trim()
|
|
278
|
-
if (!body) return obj
|
|
279
|
-
const entries: string[] = []
|
|
280
|
-
let start = 0
|
|
281
|
-
let quote = ''
|
|
282
|
-
let escaped = false
|
|
283
|
-
for (let i = 0; i < body.length; i++) {
|
|
284
|
-
const ch = body[i]
|
|
285
|
-
if (quote) {
|
|
286
|
-
if (escaped) { escaped = false; continue }
|
|
287
|
-
if (ch === '\\') { escaped = true; continue }
|
|
288
|
-
if (ch === quote) quote = ''
|
|
289
|
-
continue
|
|
290
|
-
}
|
|
291
|
-
if (ch === '"' || ch === "'") { quote = ch; continue }
|
|
292
|
-
if (ch === ',') { entries.push(body.slice(start, i).trim()); start = i + 1 }
|
|
293
|
-
}
|
|
294
|
-
if (quote) obj.malformed.push('`test` object has an unterminated quoted value')
|
|
295
|
-
entries.push(body.slice(start).trim())
|
|
296
|
-
for (const entry of entries) assignTestField(obj, entry)
|
|
297
|
-
return obj
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function normalizedTest(it: RawItem): ScenarioTestReference | undefined {
|
|
301
|
-
if (it.testObject) {
|
|
302
|
-
const path = it.testObject.fields.path
|
|
303
|
-
if (!path) return undefined
|
|
304
|
-
const name = it.testObject.fields.name
|
|
305
|
-
return { path, ...(name ? { name } : {}) }
|
|
306
|
-
}
|
|
307
|
-
const path = it.fields.test
|
|
308
|
-
return path ? { path } : undefined
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const normSemantic = (s: string) => s.replace(/\s+/g, ' ').trim()
|
|
312
|
-
export function scenarioHash(s: Pick<Scenario, 'description' | 'expected'>): string {
|
|
313
|
-
return createHash('sha256').update(`${normSemantic(s.description)}\n${normSemantic(s.expected)}`, 'utf8').digest('hex')
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export type ScenarioCodeAxis = { entries: RelationEntry[]; paths: string[]; problems: string[] }
|
|
317
|
-
export type ScenarioCodeAxisSource = readonly string[] | readonly RelationEntry[]
|
|
318
|
-
export function scenarioCodeAxis(scenarioCode: readonly string[] | undefined, nodeCode: ScenarioCodeAxisSource = []): ScenarioCodeAxis {
|
|
319
|
-
const parsed = scenarioCode?.length
|
|
320
|
-
? parseRelation([...scenarioCode], 'code')
|
|
321
|
-
: nodeCode.length && typeof nodeCode[0] !== 'string'
|
|
322
|
-
? { entries: (nodeCode as readonly RelationEntry[]).map((e) => ({ path: e.path, selectors: [...e.selectors] })), problems: [] }
|
|
323
|
-
: parseRelation([...(nodeCode as readonly string[])], 'code')
|
|
324
|
-
const { entries, problems } = parsed
|
|
325
|
-
return { entries, paths: entries.map((e) => e.path), problems }
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function parseCodeList(raw: string): string[] {
|
|
329
|
-
return raw.replace(/^\[|\]$/g, '').split(',').map((s) => unquote(s.trim())).filter(Boolean)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
export function parseScenarios(src: string): Scenario[] {
|
|
333
|
-
return walkScenarios(src).items
|
|
334
|
-
.map((it): Scenario => {
|
|
335
|
-
const tags = it.fields.tags ? parseCodeList(it.fields.tags) : []
|
|
336
|
-
const code = it.fields.code ? parseCodeList(it.fields.code) : []
|
|
337
|
-
const related = it.fields.related ? parseCodeList(it.fields.related) : []
|
|
338
|
-
const test = normalizedTest(it)
|
|
339
|
-
return {
|
|
340
|
-
name: it.fields.name ?? '',
|
|
341
|
-
description: it.fields.description ?? '',
|
|
342
|
-
expected: it.fields.expected ?? '',
|
|
343
|
-
...(tags.length ? { tags } : {}),
|
|
344
|
-
...(test ? { test } : {}),
|
|
345
|
-
...(code.length ? { code } : {}),
|
|
346
|
-
...(related.length ? { related } : {}),
|
|
347
|
-
}
|
|
348
|
-
})
|
|
349
|
-
.filter((s) => s.name)
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const compareStable = (a: string, b: string): number => a < b ? -1 : a > b ? 1 : 0
|
|
353
|
-
const relationRows = (raw: readonly string[] | undefined, relation: 'code' | 'related'): RelationEntry[] =>
|
|
354
|
-
parseRelation([...(raw ?? [])], relation).entries.map((entry) => ({ path: entry.path, selectors: [...entry.selectors] }))
|
|
355
|
-
|
|
356
|
-
const hashProjection = (value: unknown): string =>
|
|
357
|
-
createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex')
|
|
358
|
-
|
|
359
|
-
const semanticOnly = (row: ScenarioProjectionRow): ScenarioSemanticRow => row.semantic
|
|
360
|
-
const frontmatterList = (value: string | string[] | undefined): string[] =>
|
|
361
|
-
Array.isArray(value) ? value : value ? [value] : []
|
|
362
|
-
|
|
363
|
-
function projectionNode(node: Pick<EvalNode, 'id' | 'specSource'>): ScenarioProjectionNode {
|
|
364
|
-
const fm = node.specSource ? parseFrontmatter(node.specSource).fm : {}
|
|
365
|
-
const code = parseRelation(frontmatterList(fm.code), 'code')
|
|
366
|
-
const related = parseRelation(frontmatterList(fm.related), 'related')
|
|
367
|
-
const problems = [...code.problems, ...related.problems]
|
|
368
|
-
if (problems.length) {
|
|
369
|
-
throw new Error(`node '${node.id}' has malformed spec relations:\n${problems.map((e) => ` - ${e}`).join('\n')}`)
|
|
370
|
-
}
|
|
371
|
-
return {
|
|
372
|
-
id: node.id,
|
|
373
|
-
code: code.entries.map((entry) => ({ path: entry.path, selectors: [...entry.selectors] })),
|
|
374
|
-
related: related.entries.map((entry) => ({ path: entry.path, selectors: [...entry.selectors] })),
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export function scenarioProjection(
|
|
379
|
-
nodes: readonly Pick<EvalNode, 'id' | 'scenarios' | 'evalSource' | 'specSource'>[],
|
|
380
|
-
provenance: Partial<ScenarioProjectionProvenance> = {},
|
|
381
|
-
): ScenarioProjection {
|
|
382
|
-
const nodeRows = nodes.map(projectionNode).sort((a, b) => compareStable(a.id, b.id))
|
|
383
|
-
const rows: ScenarioProjectionRow[] = []
|
|
384
|
-
for (const node of nodes) {
|
|
385
|
-
if ('evalSource' in node && node.evalSource !== undefined) {
|
|
386
|
-
const schemaErrors = validateScenarios(node.evalSource)
|
|
387
|
-
const relationErrors = node.scenarios.flatMap((scenario) => [
|
|
388
|
-
...parseRelation(scenario.code ?? [], 'code').problems,
|
|
389
|
-
...parseRelation(scenario.related ?? [], 'related').problems,
|
|
390
|
-
])
|
|
391
|
-
const errors = [...schemaErrors, ...relationErrors]
|
|
392
|
-
if (errors.length) throw new Error(`node '${node.id}' has malformed eval.md:\n${errors.map((e) => ` - ${e}`).join('\n')}`)
|
|
393
|
-
}
|
|
394
|
-
for (const scenario of node.scenarios) rows.push({
|
|
395
|
-
semantic: {
|
|
396
|
-
node: node.id,
|
|
397
|
-
name: scenario.name,
|
|
398
|
-
description: scenario.description,
|
|
399
|
-
expected: scenario.expected,
|
|
400
|
-
scenarioHash: scenarioHash(scenario),
|
|
401
|
-
code: relationRows(scenario.code, 'code'),
|
|
402
|
-
related: relationRows(scenario.related, 'related'),
|
|
403
|
-
tags: [...(scenario.tags ?? [])],
|
|
404
|
-
},
|
|
405
|
-
measurement: { test: scenario.test ? { ...scenario.test } : null },
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
rows.sort((a, b) => {
|
|
409
|
-
const node = compareStable(a.semantic.node, b.semantic.node)
|
|
410
|
-
return node || compareStable(a.semantic.name, b.semantic.name)
|
|
411
|
-
})
|
|
412
|
-
const semanticRows = rows.map(semanticOnly)
|
|
413
|
-
return {
|
|
414
|
-
projection: SCENARIO_PROJECTION,
|
|
415
|
-
schemaVersion: SCENARIO_SCHEMA_VERSION,
|
|
416
|
-
provenance: { head: provenance.head ?? null, treeSha: provenance.treeSha ?? null },
|
|
417
|
-
nodes: nodeRows,
|
|
418
|
-
semanticIndexHash: hashProjection(semanticRows),
|
|
419
|
-
fullIndexHash: hashProjection(rows),
|
|
420
|
-
planningIndexHash: hashProjection({ nodes: nodeRows, rows }),
|
|
421
|
-
rows,
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export function validateScenarios(src: string, tagLibrary: string[] = [], pathRoot?: string): string[] {
|
|
426
|
-
const { hasFrontmatter, hasKey, items, malformed } = walkScenarios(src)
|
|
427
|
-
if (!hasFrontmatter) return ['no frontmatter block — an eval.md must declare a `scenarios:` list']
|
|
428
|
-
if (!hasKey) return ['frontmatter has no `scenarios:` key — declare at least one scenario']
|
|
429
|
-
if (!items.length) return ['`scenarios:` declares no scenarios — add one (name + description + expected)']
|
|
430
|
-
const errs: string[] = [...malformed]
|
|
431
|
-
const counts = new Map<string, number>()
|
|
432
|
-
const lib = tagLibrary.length ? ` (library: ${tagLibrary.join(', ')})` : ''
|
|
433
|
-
items.forEach((it, idx) => {
|
|
434
|
-
const label = it.fields.name ? `scenario '${it.fields.name}'` : `scenario #${idx + 1}`
|
|
435
|
-
for (const k of ['name', 'description', 'expected'] as const) {
|
|
436
|
-
if (!it.fields[k]?.trim()) errs.push(`${label}: missing required field \`${k}\``)
|
|
437
|
-
}
|
|
438
|
-
const tags = it.fields.tags ? parseCodeList(it.fields.tags) : []
|
|
439
|
-
if (!tags.length) {
|
|
440
|
-
errs.push(`${label}: missing required field \`tags\` — every scenario needs ≥1 tag from the library${lib}; pick one, or add a new tag to lint.scenarioTags in spexcode.json to create it`)
|
|
441
|
-
} else if (tagLibrary.length) {
|
|
442
|
-
for (const t of tags) if (!tagLibrary.includes(t)) {
|
|
443
|
-
errs.push(`${label}: tag \`${t}\` is not in the configured tag library${lib} — use an existing tag, or add \`${t}\` to lint.scenarioTags in spexcode.json to create it`)
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
for (const entry of it.malformed) errs.push(`${label}: ${entry}`)
|
|
447
|
-
for (const d of it.duplicateKeys) errs.push(`${label}: duplicate field \`${d}\``)
|
|
448
|
-
for (const u of it.unknownKeys) errs.push(`${label}: unknown field \`${u}\` (allowed: ${SCENARIO_KEYS.join(', ')})`)
|
|
449
|
-
if (it.testObject) {
|
|
450
|
-
for (const u of it.testObject.unknownKeys) errs.push(`${label}: unknown \`test\` field \`${u}\` (allowed: ${TEST_KEYS.join(', ')})`)
|
|
451
|
-
for (const d of it.testObject.duplicateKeys) errs.push(`${label}: duplicate \`test.${d}\` field`)
|
|
452
|
-
for (const e of it.testObject.malformed) errs.push(`${label}: ${e}`)
|
|
453
|
-
for (const k of TEST_KEYS) if (!it.testObject.fields[k]?.length) errs.push(`${label}: \`test\` object missing required field \`${k}\``)
|
|
454
|
-
} else if ('test' in it.fields && !it.fields.test?.length) {
|
|
455
|
-
errs.push(`${label}: \`test\` scalar path must not be empty`)
|
|
456
|
-
}
|
|
457
|
-
const test = normalizedTest(it)
|
|
458
|
-
if (test && pathRoot && !existsSync(join(pathRoot, test.path))) {
|
|
459
|
-
errs.push(`${label}: \`test.path\` not found: ${test.path}`)
|
|
460
|
-
}
|
|
461
|
-
if (it.fields.name) counts.set(it.fields.name, (counts.get(it.fields.name) ?? 0) + 1)
|
|
462
|
-
})
|
|
463
|
-
for (const [n, c] of counts) if (c > 1) errs.push(`duplicate scenario name '${n}' (${c}×) — names must be unique within an eval.md`)
|
|
464
|
-
return errs
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export type ScenarioMeasurementMetadataMutation =
|
|
468
|
-
| { scenario: string; insert: { test: string | { path: string; name: string } } }
|
|
469
|
-
| { scenario: string; delete: 'test' }
|
|
470
|
-
|
|
471
|
-
type ParsedMetadataMutation =
|
|
472
|
-
| { scenario: string; action: 'insert'; test: ScenarioTestReference }
|
|
473
|
-
| { scenario: string; action: 'delete' }
|
|
474
|
-
|
|
475
|
-
const recordOf = (value: unknown): Record<string, unknown> | null =>
|
|
476
|
-
value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null
|
|
477
|
-
|
|
478
|
-
function parseMetadataMutation(value: unknown): ParsedMetadataMutation {
|
|
479
|
-
const mutation = recordOf(value)
|
|
480
|
-
if (!mutation || typeof mutation.scenario !== 'string' || !mutation.scenario.trim()) {
|
|
481
|
-
throw new Error('a metadata mutation must name exactly one scenario with a non-empty `scenario` string')
|
|
482
|
-
}
|
|
483
|
-
const unknown = Object.keys(mutation).filter((key) => !['scenario', 'insert', 'delete'].includes(key))
|
|
484
|
-
if (unknown.length) throw new Error(`metadata mutation has unknown field(s): ${unknown.join(', ')}`)
|
|
485
|
-
const actions = ['insert', 'delete'].filter((key) => key in mutation)
|
|
486
|
-
if (actions.length !== 1) throw new Error('a metadata mutation must contain exactly one action: `insert` or `delete`')
|
|
487
|
-
|
|
488
|
-
if ('delete' in mutation) {
|
|
489
|
-
if (mutation.delete !== 'test') throw new Error('`delete` must name exactly one measurement field: `test`')
|
|
490
|
-
return { scenario: mutation.scenario, action: 'delete' }
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const insert = recordOf(mutation.insert)
|
|
494
|
-
if (!insert || Object.keys(insert).length !== 1 || !('test' in insert)) {
|
|
495
|
-
throw new Error('`insert` must contain exactly one measurement field: `test`')
|
|
496
|
-
}
|
|
497
|
-
if (typeof insert.test === 'string') {
|
|
498
|
-
if (!insert.test.trim()) throw new Error('`insert.test` path must be a non-empty string')
|
|
499
|
-
return { scenario: mutation.scenario, action: 'insert', test: { path: insert.test } }
|
|
500
|
-
}
|
|
501
|
-
const test = recordOf(insert.test)
|
|
502
|
-
if (!test || Object.keys(test).sort().join(',') !== 'name,path'
|
|
503
|
-
|| typeof test.path !== 'string' || !test.path.trim()
|
|
504
|
-
|| typeof test.name !== 'string' || !test.name.trim()) {
|
|
505
|
-
throw new Error('`insert.test` must be a path string or an exact `{path,name}` string mapping')
|
|
506
|
-
}
|
|
507
|
-
return { scenario: mutation.scenario, action: 'insert', test: { path: test.path, name: test.name } }
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function declarationLineEnding(source: string): '\n' | '\r\n' {
|
|
511
|
-
const withoutCrlf = source.replace(/\r\n/g, '')
|
|
512
|
-
if (withoutCrlf.includes('\r')) throw new Error('eval.md uses unsupported bare CR line endings')
|
|
513
|
-
if (source.includes('\r\n') && withoutCrlf.includes('\n')) {
|
|
514
|
-
throw new Error('eval.md mixes LF and CRLF line endings; normalize it before applying metadata')
|
|
515
|
-
}
|
|
516
|
-
return source.includes('\r\n') ? '\r\n' : '\n'
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function malformedDeclaration(errors: string[]): Error {
|
|
520
|
-
return new Error(`malformed eval.md:\n${errors.map((error) => ` - ${error}`).join('\n')}`)
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
export function writeScenarioMeasurementMetadata(source: string, request: unknown): string {
|
|
524
|
-
const mutation = parseMetadataMutation(request)
|
|
525
|
-
const beforeErrors = validateScenarios(source)
|
|
526
|
-
if (beforeErrors.length) throw malformedDeclaration(beforeErrors)
|
|
527
|
-
|
|
528
|
-
const walked = walkScenarios(source)
|
|
529
|
-
const matches = walked.items.filter((item) => item.fields.name === mutation.scenario)
|
|
530
|
-
if (matches.length !== 1) {
|
|
531
|
-
throw new Error(matches.length
|
|
532
|
-
? `scenario '${mutation.scenario}' is ambiguous (${matches.length} declarations)`
|
|
533
|
-
: `scenario '${mutation.scenario}' was not found in eval.md`)
|
|
534
|
-
}
|
|
535
|
-
const item = matches[0]
|
|
536
|
-
const lineEnding = declarationLineEnding(source)
|
|
537
|
-
const lines = source.split(lineEnding)
|
|
538
|
-
|
|
539
|
-
if (mutation.action === 'insert') {
|
|
540
|
-
if (item.locations.test) throw new Error(`scenario '${mutation.scenario}' already has \`test\`; refusing to overwrite authoritative metadata`)
|
|
541
|
-
const tags = item.locations.tags
|
|
542
|
-
if (!tags) throw new Error(`scenario '${mutation.scenario}' has no structural \`tags\` field`)
|
|
543
|
-
const keyIndent = tags.indent
|
|
544
|
-
const childIndent = `${tags.indent} `
|
|
545
|
-
const rendered = mutation.test.name === undefined
|
|
546
|
-
? [`${keyIndent}test: ${JSON.stringify(mutation.test.path)}`]
|
|
547
|
-
: [
|
|
548
|
-
`${keyIndent}test:`,
|
|
549
|
-
`${childIndent}path: ${JSON.stringify(mutation.test.path)}`,
|
|
550
|
-
`${childIndent}name: ${JSON.stringify(mutation.test.name)}`,
|
|
551
|
-
]
|
|
552
|
-
lines.splice(tags.endLine + 2, 0, ...rendered)
|
|
553
|
-
} else {
|
|
554
|
-
const test = item.locations.test
|
|
555
|
-
if (!test) throw new Error(`scenario '${mutation.scenario}' has no \`test\` field to delete`)
|
|
556
|
-
lines.splice(test.startLine + 1, test.endLine - test.startLine + 1)
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const proposed = lines.join(lineEnding)
|
|
560
|
-
const afterErrors = validateScenarios(proposed)
|
|
561
|
-
if (afterErrors.length) throw new Error(`metadata mutation produced ${malformedDeclaration(afterErrors).message}`)
|
|
562
|
-
const after = parseScenarios(proposed).filter((scenario) => scenario.name === mutation.scenario)
|
|
563
|
-
if (after.length !== 1) throw new Error(`metadata mutation lost the unique scenario '${mutation.scenario}'`)
|
|
564
|
-
if (mutation.action === 'insert') {
|
|
565
|
-
if (JSON.stringify(after[0].test) !== JSON.stringify(mutation.test)) {
|
|
566
|
-
throw new Error(`metadata mutation did not round-trip the exact requested \`test\` mapping for scenario '${mutation.scenario}'`)
|
|
567
|
-
}
|
|
568
|
-
} else if (after[0].test !== undefined) {
|
|
569
|
-
throw new Error(`metadata mutation did not delete \`test\` from scenario '${mutation.scenario}'`)
|
|
570
|
-
}
|
|
571
|
-
return proposed
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function assembleNodes(root: string, specDirs: string[], hits: { dir: string; src: string; specSource?: string }[]): EvalNode[] {
|
|
575
|
-
const specBase = join(root, '.spec')
|
|
576
|
-
const ids = mintIds(specDirs.map((d) => relative(specBase, d).split(/[/\\]/)))
|
|
577
|
-
const idByDir = new Map(specDirs.map((d, i) => [d, ids[i]]))
|
|
578
|
-
return hits
|
|
579
|
-
.map(({ dir, src, specSource }) => ({
|
|
580
|
-
id: idByDir.get(dir) ?? basename(dir),
|
|
581
|
-
dir,
|
|
582
|
-
evalPath: relative(root, join(dir, EVAL_FILE)),
|
|
583
|
-
sidecarPath: join(dir, SIDECAR_FILE),
|
|
584
|
-
scenarios: parseScenarios(src),
|
|
585
|
-
evalSource: src,
|
|
586
|
-
...(specSource !== undefined ? { specSource } : {}),
|
|
587
|
-
}))
|
|
588
|
-
.sort((a, b) => a.id.localeCompare(b.id))
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
export function evalNodes(root: string): EvalNode[] {
|
|
592
|
-
const specDir = join(root, '.spec')
|
|
593
|
-
const specDirs: string[] = []
|
|
594
|
-
const hits: { dir: string; src: string; specSource?: string }[] = []
|
|
595
|
-
const stack = existsSync(specDir) ? [specDir] : []
|
|
596
|
-
while (stack.length) {
|
|
597
|
-
const dir = stack.pop()!
|
|
598
|
-
let ents
|
|
599
|
-
try { ents = readdirSync(dir, { withFileTypes: true }) } catch { continue }
|
|
600
|
-
if (existsSync(join(dir, 'spec.md'))) specDirs.push(dir)
|
|
601
|
-
if (existsSync(join(dir, EVAL_FILE))) {
|
|
602
|
-
const specPath = join(dir, 'spec.md')
|
|
603
|
-
hits.push({
|
|
604
|
-
dir,
|
|
605
|
-
src: readFileSync(join(dir, EVAL_FILE), 'utf8'),
|
|
606
|
-
...(existsSync(specPath) ? { specSource: readFileSync(specPath, 'utf8') } : {}),
|
|
607
|
-
})
|
|
608
|
-
}
|
|
609
|
-
for (const e of ents) if (e.isDirectory()) stack.push(join(dir, e.name))
|
|
610
|
-
}
|
|
611
|
-
return assembleNodes(root, specDirs, hits)
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
export function evalNodesAt(root: string, tip: string): EvalNode[] {
|
|
615
|
-
const files = treeTextFiles(root, tip, '.spec')
|
|
616
|
-
const paths = [...files.keys()]
|
|
617
|
-
const specDirs = paths
|
|
618
|
-
.filter((path) => path.endsWith('/spec.md'))
|
|
619
|
-
.map((path) => join(root, path.slice(0, -'/spec.md'.length)))
|
|
620
|
-
const hits = paths
|
|
621
|
-
.filter((path) => path.endsWith(`/${EVAL_FILE}`))
|
|
622
|
-
.map((path) => {
|
|
623
|
-
const relDir = path.slice(0, -`/${EVAL_FILE}`.length)
|
|
624
|
-
const specSource = files.get(`${relDir}/spec.md`)
|
|
625
|
-
return {
|
|
626
|
-
dir: join(root, relDir),
|
|
627
|
-
src: files.get(path)!,
|
|
628
|
-
...(specSource !== undefined ? { specSource } : {}),
|
|
629
|
-
}
|
|
630
|
-
})
|
|
631
|
-
return assembleNodes(root, specDirs, hits)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// async twin of evalNodes for the HOT board build ([[graph-cache]]): reading each eval.md through
|
|
635
|
-
// fs/promises YIELDS the event loop between files, so the walk no longer stalls a `/health` probe in one
|
|
636
|
-
// ~600ms uninterrupted stretch. Same output (canonical ids, id-sorted) as evalNodes; only buildBoard uses
|
|
637
|
-
// it, other callers keep the sync form.
|
|
638
|
-
export async function evalNodesAsync(root: string): Promise<EvalNode[]> {
|
|
639
|
-
const specDir = join(root, '.spec')
|
|
640
|
-
const specDirs: string[] = []
|
|
641
|
-
const hits: { dir: string; src: string; specSource?: string }[] = []
|
|
642
|
-
const stack = existsSync(specDir) ? [specDir] : []
|
|
643
|
-
while (stack.length) {
|
|
644
|
-
const dir = stack.pop()!
|
|
645
|
-
let ents
|
|
646
|
-
try { ents = await readdir(dir, { withFileTypes: true }) } catch { continue }
|
|
647
|
-
if (existsSync(join(dir, 'spec.md'))) specDirs.push(dir)
|
|
648
|
-
if (existsSync(join(dir, EVAL_FILE))) {
|
|
649
|
-
const specPath = join(dir, 'spec.md')
|
|
650
|
-
hits.push({
|
|
651
|
-
dir,
|
|
652
|
-
src: await readFile(join(dir, EVAL_FILE), 'utf8'),
|
|
653
|
-
...(existsSync(specPath) ? { specSource: await readFile(specPath, 'utf8') } : {}),
|
|
654
|
-
})
|
|
655
|
-
}
|
|
656
|
-
for (const e of ents) if (e.isDirectory()) stack.push(join(dir, e.name))
|
|
657
|
-
}
|
|
658
|
-
return assembleNodes(root, specDirs, hits)
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
export type EvalResolution<T> = { ok: true; node: T } | { ok: false; ambiguous: boolean; error: string }
|
|
662
|
-
|
|
663
|
-
export function resolveEvalNode<T extends Pick<EvalNode, 'id' | 'dir'>>(nodes: T[], ref: string): EvalResolution<T> {
|
|
664
|
-
const exact = nodes.find((n) => n.id === ref)
|
|
665
|
-
if (exact) return { ok: true, node: exact }
|
|
666
|
-
const byLeaf = nodes.filter((n) => basename(n.dir) === ref)
|
|
667
|
-
if (byLeaf.length === 1) return { ok: true, node: byLeaf[0] }
|
|
668
|
-
if (byLeaf.length > 1) {
|
|
669
|
-
return { ok: false, ambiguous: true, error: `'${ref}' is ambiguous — ${byLeaf.length} measurable nodes share that leaf name; use a canonical id: ${byLeaf.map((n) => n.id).sort().join(', ')}` }
|
|
670
|
-
}
|
|
671
|
-
return { ok: false, ambiguous: false, error: `no measurable node '${ref}' (a node needs an eval.md)` }
|
|
672
|
-
}
|