@hassangameryt/freeflow 0.2.0 → 0.4.0

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.
Files changed (312) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +4 -4
  3. package/{plugins/freeflow/.claude-plugin → .claude-plugin}/plugin.json +5 -2
  4. package/{plugins/freeflow/.codex-plugin → .codex-plugin}/plugin.json +8 -9
  5. package/README.md +194 -127
  6. package/assets/feedback-loop.png +0 -0
  7. package/assets/freeflow-plugin-icon.png +0 -0
  8. package/assets/freeflow_workflow_map.png +0 -0
  9. package/assets/workflow.png +0 -0
  10. package/command-surface.json +129 -0
  11. package/hooks/freeflow-runtime-context.mjs +419 -0
  12. package/hooks/tests/check-runtime-context-hook.sh +223 -0
  13. package/package.json +41 -22
  14. package/pi-extension/dist/index.js +269 -0
  15. package/pi-extension/dist/integrations/mcp-config.js +29 -0
  16. package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
  17. package/pi-extension/dist/output-router/native-safety-net.js +216 -0
  18. package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
  19. package/pi-extension/dist/output-router/renderers.js +1023 -0
  20. package/pi-extension/dist/output-router/router-tools.js +554 -0
  21. package/pi-extension/dist/output-router/schemas.js +383 -0
  22. package/pi-extension/dist/output-router/status.js +744 -0
  23. package/pi-extension/dist/output-router/utils.js +502 -0
  24. package/pi-extension/dist/runtime/runtime-context.js +798 -0
  25. package/pi-extension/dist/settings/settings-tui.js +314 -0
  26. package/pi-extension/dist/settings/settings-ui.js +1487 -0
  27. package/pi-extension/freeflow/index.js +1 -0
  28. package/pi-extension/src/index.ts +295 -0
  29. package/pi-extension/src/integrations/mcp-config.ts +32 -0
  30. package/pi-extension/src/output-router/host-producer-identification.ts +256 -0
  31. package/pi-extension/src/output-router/native-safety-net.ts +248 -0
  32. package/pi-extension/src/output-router/observed-tool-routing.ts +134 -0
  33. package/pi-extension/src/output-router/renderers.ts +1108 -0
  34. package/pi-extension/src/output-router/router-tools.ts +579 -0
  35. package/pi-extension/src/output-router/schemas.ts +387 -0
  36. package/pi-extension/src/output-router/status.ts +791 -0
  37. package/pi-extension/src/output-router/utils.ts +554 -0
  38. package/pi-extension/src/runtime/runtime-context.ts +895 -0
  39. package/pi-extension/src/settings/settings-tui.ts +408 -0
  40. package/pi-extension/src/settings/settings-ui.ts +1730 -0
  41. package/pi-extension/tests/integration/pi-extension.test.js +4018 -0
  42. package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
  43. package/pi-extension/tests/output-router/search.test.js +722 -0
  44. package/pi-extension/tests/settings/settings-tui.test.js +243 -0
  45. package/pi-extension/tsconfig.json +16 -0
  46. package/{plugins/freeflow/router/dist → router/dist/benchmarks}/benchmark-harness.d.ts +16 -13
  47. package/router/dist/benchmarks/benchmark-harness.js +92 -0
  48. package/router/dist/benchmarks/benchmarks.d.ts +109 -0
  49. package/router/dist/benchmarks/benchmarks.js +979 -0
  50. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +95 -0
  51. package/router/dist/benchmarks/codex-qa-benchmarks.js +500 -0
  52. package/router/dist/benchmarks/command-benchmarks.d.ts +143 -0
  53. package/router/dist/benchmarks/command-benchmarks.js +698 -0
  54. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +82 -0
  55. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +773 -0
  56. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +132 -0
  57. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1380 -0
  58. package/router/dist/benchmarks/context-saving-baseline.d.ts +60 -0
  59. package/router/dist/benchmarks/context-saving-baseline.js +470 -0
  60. package/router/dist/benchmarks/index-benchmarks.d.ts +103 -0
  61. package/router/dist/benchmarks/index-benchmarks.js +862 -0
  62. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +101 -0
  63. package/router/dist/benchmarks/storage-policy-benchmarks.js +618 -0
  64. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +56 -0
  65. package/router/dist/benchmarks/vault-index-benchmarks.js +322 -0
  66. package/router/dist/config/config.d.ts +103 -0
  67. package/router/dist/config/config.js +632 -0
  68. package/{plugins/freeflow/router/dist → router/dist/config}/router-contract.d.ts +2 -2
  69. package/router/dist/config/router-contract.js +54 -0
  70. package/{plugins/freeflow/router/dist → router/dist/config}/schema.d.ts +19 -10
  71. package/router/dist/config/schema.js +648 -0
  72. package/router/dist/config/types.d.ts +573 -0
  73. package/router/dist/config/types.js +41 -0
  74. package/router/dist/evidence/bounded-evidence.d.ts +40 -0
  75. package/router/dist/evidence/bounded-evidence.js +323 -0
  76. package/router/dist/evidence/evidence-range-selector.d.ts +24 -0
  77. package/router/dist/evidence/evidence-range-selector.js +232 -0
  78. package/router/dist/evidence/evidence-search.d.ts +27 -0
  79. package/router/dist/evidence/evidence-search.js +490 -0
  80. package/{plugins/freeflow/router/dist → router/dist/evidence}/evidence.d.ts +18 -18
  81. package/router/dist/evidence/evidence.js +200 -0
  82. package/router/dist/evidence/failure-contracts.d.ts +37 -0
  83. package/router/dist/evidence/failure-contracts.js +166 -0
  84. package/router/dist/evidence/line-ranges.d.ts +24 -0
  85. package/router/dist/evidence/line-ranges.js +22 -0
  86. package/router/dist/experiments/local-index.d.ts +64 -0
  87. package/router/dist/experiments/local-index.js +581 -0
  88. package/router/dist/index.d.ts +30 -0
  89. package/router/dist/index.js +32 -0
  90. package/router/dist/local/local-traversal.d.ts +18 -0
  91. package/router/dist/local/local-traversal.js +334 -0
  92. package/router/dist/processing/engine.d.ts +132 -0
  93. package/router/dist/processing/engine.js +533 -0
  94. package/router/dist/processing/reducers.d.ts +280 -0
  95. package/router/dist/processing/reducers.js +1673 -0
  96. package/router/dist/processing/renderers.d.ts +31 -0
  97. package/router/dist/processing/renderers.js +195 -0
  98. package/router/dist/processing/scripts.d.ts +91 -0
  99. package/router/dist/processing/scripts.js +386 -0
  100. package/{plugins/freeflow/router/dist → router/dist/repo}/repo-traversal.d.ts +9 -9
  101. package/router/dist/repo/repo-traversal.js +245 -0
  102. package/router/dist/routing/observed-reducers.d.ts +41 -0
  103. package/router/dist/routing/observed-reducers.js +661 -0
  104. package/router/dist/routing/observed-routing.d.ts +26 -0
  105. package/router/dist/routing/observed-routing.js +378 -0
  106. package/router/dist/routing/parsers.d.ts +16 -0
  107. package/router/dist/routing/parsers.js +521 -0
  108. package/router/dist/routing/run-filters.d.ts +60 -0
  109. package/router/dist/routing/run-filters.js +238 -0
  110. package/router/dist/routing/run-reducers.d.ts +49 -0
  111. package/router/dist/routing/run-reducers.js +122 -0
  112. package/router/dist/sandbox/adapter-roots.d.ts +46 -0
  113. package/router/dist/sandbox/adapter-roots.js +66 -0
  114. package/router/dist/sandbox/eryx-python-adapter.d.ts +13 -0
  115. package/router/dist/sandbox/eryx-python-adapter.js +868 -0
  116. package/router/dist/sandbox/jq-wasm-adapter.d.ts +9 -0
  117. package/router/dist/sandbox/jq-wasm-adapter.js +429 -0
  118. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +13 -0
  119. package/router/dist/sandbox/quickjs-wasi-adapter.js +384 -0
  120. package/router/dist/sandbox/script-sandbox.d.ts +134 -0
  121. package/router/dist/sandbox/script-sandbox.js +337 -0
  122. package/router/dist/setup/script-transform-adapters.d.ts +32 -0
  123. package/router/dist/setup/script-transform-adapters.js +278 -0
  124. package/router/dist/tools/batch.d.ts +33 -0
  125. package/router/dist/tools/batch.js +586 -0
  126. package/router/dist/tools/run.d.ts +62 -0
  127. package/router/dist/tools/run.js +1304 -0
  128. package/router/dist/tools/search.d.ts +66 -0
  129. package/router/dist/tools/search.js +1364 -0
  130. package/router/dist/transform/engine.d.ts +168 -0
  131. package/router/dist/transform/engine.js +2072 -0
  132. package/router/dist/vault/vault-index.d.ts +125 -0
  133. package/router/dist/vault/vault-index.js +416 -0
  134. package/router/dist/vault/vault.d.ts +138 -0
  135. package/router/dist/vault/vault.js +630 -0
  136. package/runtime/interaction-contract.md +16 -0
  137. package/skills/bypass/SKILL.md +82 -0
  138. package/skills/commit-work/SKILL.md +106 -0
  139. package/skills/commit-work/references/staging-decisions.md +73 -0
  140. package/skills/decision-gate/SKILL.md +48 -0
  141. package/skills/design-for-depth/SKILL.md +106 -0
  142. package/skills/design-for-depth/references/design-pressure-signals.md +314 -0
  143. package/skills/design-for-depth/references/interface-design-loop.md +156 -0
  144. package/skills/design-for-depth/references/software-design-philosophy.md +248 -0
  145. package/skills/diagnose-failure/SKILL.md +105 -0
  146. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
  147. package/skills/diagnose-failure/references/flaky-and-performance.md +43 -0
  148. package/skills/discuss/SKILL.md +121 -0
  149. package/skills/discuss/references/checkpoints.md +66 -0
  150. package/skills/evaluate-skill/SKILL.md +105 -0
  151. package/skills/evaluate-skill/references/definition-schema.md +345 -0
  152. package/skills/evaluate-skill/references/evaluation-design.md +86 -0
  153. package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
  154. package/skills/evaluate-skill/references/review-and-revision.md +51 -0
  155. package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
  156. package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
  157. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
  158. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
  159. package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
  160. package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
  161. package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
  162. package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
  163. package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
  164. package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
  165. package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
  166. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
  167. package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
  168. package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
  169. package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
  170. package/skills/execute-work/SKILL.md +99 -0
  171. package/skills/execute-work/references/code-practices.md +78 -0
  172. package/skills/execute-work/references/execution-loop.md +77 -0
  173. package/skills/finish-branch/SKILL.md +117 -0
  174. package/skills/finish-branch/references/integration-options.md +80 -0
  175. package/skills/handoff/SKILL.md +92 -0
  176. package/skills/handoff/references/templates.md +141 -0
  177. package/skills/launch-work/SKILL.md +116 -0
  178. package/skills/launch-work/references/launch-readiness.md +85 -0
  179. package/skills/migration-work/SKILL.md +115 -0
  180. package/skills/migration-work/references/migration-lifecycle.md +82 -0
  181. package/skills/mode-contract/SKILL.md +82 -0
  182. package/skills/output-router/SKILL.md +206 -0
  183. package/skills/output-router/references/safety-policy.md +40 -0
  184. package/skills/release-work/SKILL.md +118 -0
  185. package/skills/release-work/references/release-evidence.md +78 -0
  186. package/skills/review-artifact/SKILL.md +126 -0
  187. package/skills/review-artifact/references/reviewer-prompt.md +148 -0
  188. package/skills/review-work/SKILL.md +126 -0
  189. package/skills/review-work/references/reviewer-prompt.md +134 -0
  190. package/skills/review-work/references/security-risk-lens.md +67 -0
  191. package/skills/setup-freeflow/SKILL.md +98 -0
  192. package/skills/setup-freeflow/references/activation-contract.md +120 -0
  193. package/skills/setup-freeflow/references/host-setup.md +72 -0
  194. package/skills/setup-freeflow/references/output-router-setup.md +110 -0
  195. package/skills/simplify-code/SKILL.md +80 -0
  196. package/skills/simplify-code/references/simplification-patterns.md +70 -0
  197. package/skills/tdd/SKILL.md +96 -0
  198. package/skills/tdd/references/test-design.md +96 -0
  199. package/skills/track-work/SKILL.md +84 -0
  200. package/skills/track-work/references/working-record-schema.md +378 -0
  201. package/skills/verify-work/SKILL.md +85 -0
  202. package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
  203. package/skills/verify-work/references/integration-evidence.md +51 -0
  204. package/skills/verify-work/references/performance-evidence.md +57 -0
  205. package/skills/workflow/SKILL.md +91 -0
  206. package/skills/workflow/references/domain-skill-composition.md +57 -0
  207. package/skills/workflow/references/workflow-loop.md +92 -0
  208. package/skills/write-plan/SKILL.md +123 -0
  209. package/skills/write-plan/references/plan-shapes.md +156 -0
  210. package/skills/write-skill/SKILL.md +119 -0
  211. package/skills/write-skill/references/activation-boundaries.md +62 -0
  212. package/skills/write-skill/references/agent-first-instructions.md +87 -0
  213. package/skills/write-skill/references/progressive-disclosure.md +63 -0
  214. package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
  215. package/skills/write-skill/scripts/skill-author.mjs +104 -0
  216. package/skills/write-spec/SKILL.md +104 -0
  217. package/skills/write-spec/references/artifact-standards.md +86 -0
  218. package/skills/write-spec/references/decision-records.md +77 -0
  219. package/skills/write-spec/references/spec-shapes.md +128 -0
  220. package/plugins/freeflow/command-surface.json +0 -77
  221. package/plugins/freeflow/docs/README.md +0 -40
  222. package/plugins/freeflow/docs/adr/0001-three-modes.md +0 -27
  223. package/plugins/freeflow/docs/adr/0002-evals-before-hooks.md +0 -17
  224. package/plugins/freeflow/docs/adr/0003-release-boundary.md +0 -21
  225. package/plugins/freeflow/docs/adr/0004-research-replaces-shallow-discovery-skills.md +0 -26
  226. package/plugins/freeflow/docs/adr/README.md +0 -8
  227. package/plugins/freeflow/docs/architecture.md +0 -86
  228. package/plugins/freeflow/docs/output-router.md +0 -212
  229. package/plugins/freeflow/docs/release-evidence.md +0 -69
  230. package/plugins/freeflow/docs/skills.md +0 -43
  231. package/plugins/freeflow/docs/workflow.md +0 -57
  232. package/plugins/freeflow/hooks/freeflow-runtime-context.mjs +0 -257
  233. package/plugins/freeflow/pi-extension/index.js +0 -1176
  234. package/plugins/freeflow/router/dist/benchmark-harness.js +0 -92
  235. package/plugins/freeflow/router/dist/benchmarks.d.ts +0 -102
  236. package/plugins/freeflow/router/dist/benchmarks.js +0 -880
  237. package/plugins/freeflow/router/dist/bounded-evidence.d.ts +0 -40
  238. package/plugins/freeflow/router/dist/bounded-evidence.js +0 -310
  239. package/plugins/freeflow/router/dist/codex-qa-benchmarks.d.ts +0 -91
  240. package/plugins/freeflow/router/dist/codex-qa-benchmarks.js +0 -461
  241. package/plugins/freeflow/router/dist/command-benchmarks.d.ts +0 -128
  242. package/plugins/freeflow/router/dist/command-benchmarks.js +0 -652
  243. package/plugins/freeflow/router/dist/config.d.ts +0 -22
  244. package/plugins/freeflow/router/dist/config.js +0 -114
  245. package/plugins/freeflow/router/dist/evidence-range-selector.d.ts +0 -22
  246. package/plugins/freeflow/router/dist/evidence-range-selector.js +0 -180
  247. package/plugins/freeflow/router/dist/evidence-search.d.ts +0 -25
  248. package/plugins/freeflow/router/dist/evidence-search.js +0 -450
  249. package/plugins/freeflow/router/dist/evidence.js +0 -196
  250. package/plugins/freeflow/router/dist/experimental-local-index.d.ts +0 -1
  251. package/plugins/freeflow/router/dist/experimental-local-index.js +0 -1
  252. package/plugins/freeflow/router/dist/experiments/local-index.d.ts +0 -58
  253. package/plugins/freeflow/router/dist/experiments/local-index.js +0 -562
  254. package/plugins/freeflow/router/dist/index-benchmarks.d.ts +0 -84
  255. package/plugins/freeflow/router/dist/index-benchmarks.js +0 -458
  256. package/plugins/freeflow/router/dist/index.d.ts +0 -12
  257. package/plugins/freeflow/router/dist/index.js +0 -12
  258. package/plugins/freeflow/router/dist/line-ranges.d.ts +0 -22
  259. package/plugins/freeflow/router/dist/line-ranges.js +0 -22
  260. package/plugins/freeflow/router/dist/parsers.d.ts +0 -16
  261. package/plugins/freeflow/router/dist/parsers.js +0 -473
  262. package/plugins/freeflow/router/dist/repo-traversal.js +0 -242
  263. package/plugins/freeflow/router/dist/retrieve.d.ts +0 -33
  264. package/plugins/freeflow/router/dist/retrieve.js +0 -855
  265. package/plugins/freeflow/router/dist/router-contract.js +0 -54
  266. package/plugins/freeflow/router/dist/run.d.ts +0 -26
  267. package/plugins/freeflow/router/dist/run.js +0 -356
  268. package/plugins/freeflow/router/dist/schema.js +0 -354
  269. package/plugins/freeflow/router/dist/types.d.ts +0 -234
  270. package/plugins/freeflow/router/dist/types.js +0 -9
  271. package/plugins/freeflow/router/dist/vault.d.ts +0 -80
  272. package/plugins/freeflow/router/dist/vault.js +0 -484
  273. package/plugins/freeflow/skills/bypass/SKILL.md +0 -68
  274. package/plugins/freeflow/skills/commit-work/SKILL.md +0 -98
  275. package/plugins/freeflow/skills/commit-work/references/staging-decisions.md +0 -51
  276. package/plugins/freeflow/skills/diagnose-failure/SKILL.md +0 -95
  277. package/plugins/freeflow/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
  278. package/plugins/freeflow/skills/diagnose-failure/references/flaky-and-performance.md +0 -31
  279. package/plugins/freeflow/skills/evaluate-skill/SKILL.md +0 -96
  280. package/plugins/freeflow/skills/evaluate-skill/references/eval-patterns.md +0 -41
  281. package/plugins/freeflow/skills/evaluate-skill/references/grading-priority.md +0 -33
  282. package/plugins/freeflow/skills/execute-plan/SKILL.md +0 -132
  283. package/plugins/freeflow/skills/execute-plan/references/execution-map.md +0 -122
  284. package/plugins/freeflow/skills/handoff/SKILL.md +0 -66
  285. package/plugins/freeflow/skills/handoff/references/templates.md +0 -64
  286. package/plugins/freeflow/skills/interview-gate/SKILL.md +0 -90
  287. package/plugins/freeflow/skills/mode-contract/SKILL.md +0 -79
  288. package/plugins/freeflow/skills/output-router/SKILL.md +0 -69
  289. package/plugins/freeflow/skills/output-router/references/safety-policy.md +0 -33
  290. package/plugins/freeflow/skills/research/ARTIFACT-DESTINATIONS.md +0 -58
  291. package/plugins/freeflow/skills/research/CHECKPOINTS.md +0 -74
  292. package/plugins/freeflow/skills/research/LANGUAGE.md +0 -61
  293. package/plugins/freeflow/skills/research/SKILL.md +0 -128
  294. package/plugins/freeflow/skills/review-artifact/SKILL.md +0 -115
  295. package/plugins/freeflow/skills/review-artifact/references/reviewer-prompt.md +0 -56
  296. package/plugins/freeflow/skills/review-work/SKILL.md +0 -96
  297. package/plugins/freeflow/skills/review-work/references/reviewer-prompt.md +0 -97
  298. package/plugins/freeflow/skills/setup-freeflow/SKILL.md +0 -94
  299. package/plugins/freeflow/skills/setup-freeflow/references/activation-contract.md +0 -99
  300. package/plugins/freeflow/skills/setup-freeflow/references/host-setup.md +0 -35
  301. package/plugins/freeflow/skills/setup-freeflow/references/output-router-setup.md +0 -52
  302. package/plugins/freeflow/skills/verify-work/SKILL.md +0 -62
  303. package/plugins/freeflow/skills/workflow/SKILL.md +0 -95
  304. package/plugins/freeflow/skills/workflow/references/workflow-map.md +0 -102
  305. package/plugins/freeflow/skills/write-plan/SKILL.md +0 -88
  306. package/plugins/freeflow/skills/write-plan/references/plan-shapes.md +0 -70
  307. package/plugins/freeflow/skills/write-skill/SKILL.md +0 -88
  308. package/plugins/freeflow/skills/write-spec/SKILL.md +0 -90
  309. package/plugins/freeflow/skills/write-spec/references/artifact-standards.md +0 -67
  310. package/plugins/freeflow/skills/write-spec/references/spec-shapes.md +0 -69
  311. /package/{plugins/freeflow/assets → assets}/freeflow-plugin-icon-transparent.png +0 -0
  312. /package/{plugins/freeflow/hooks → hooks}/hooks.json +0 -0
@@ -0,0 +1,337 @@
1
+ import { readFile, realpath, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ const VARIANTS = ["baseline", "candidate"];
5
+ const DEFAULT_SELECTORS = /** @type {{group: string | null, variant: string | null}} */ ({
6
+ group: null,
7
+ variant: null,
8
+ });
9
+
10
+ class ViewError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ViewError";
14
+ }
15
+ }
16
+
17
+ /**
18
+ * @param {string} target
19
+ * @param {{group: string | null, variant: string | null}} [selectors]
20
+ * @param {{root?: string}} [options]
21
+ */
22
+
23
+ export async function renderResult(target, selectors = DEFAULT_SELECTORS, { root = process.cwd() } = {}) {
24
+ const resultDirectory = await resolveResult(target, root);
25
+ const summary = await readJson(path.join(resultDirectory, "summary.json"), "result summary");
26
+ let groups = Array.isArray(summary.groups) ? summary.groups : [];
27
+ if (summary.definitionKind === "group" && selectors.group !== null) {
28
+ throw new ViewError("--group cannot be used with a direct group result");
29
+ }
30
+ if (selectors.group !== null) {
31
+ const position = /^\d+$/.test(String(selectors.group)) ? Number(selectors.group) : null;
32
+ groups =
33
+ position === null
34
+ ? groups.filter((group) => group.id === selectors.group)
35
+ : groups.filter((group) => group.position === position);
36
+ if (groups.length === 0) throw new ViewError(`Unknown group selector: ${selectors.group}`);
37
+ }
38
+ if (selectors.variant !== null && !VARIANTS.includes(selectors.variant)) {
39
+ throw new ViewError("variant must be baseline or candidate");
40
+ }
41
+
42
+ const lines = [`Result ${summary.id} [${summary.state}]`, `Path: ${escapeCell(resultDirectory)}`];
43
+ for (const group of groups) {
44
+ lines.push("", ...(await renderGroup(resultDirectory, group, selectors.variant)));
45
+ }
46
+ return `${lines.join("\n")}\n`;
47
+ }
48
+
49
+ async function renderGroup(resultDirectory, group, selectedVariant) {
50
+ const groupDirectory = path.join(resultDirectory, "groups", group.id);
51
+ const definition = await readJson(path.join(groupDirectory, "definition.json"), `definition for ${group.id}`);
52
+ const gradeFile = path.join(groupDirectory, group.artifacts?.grade ?? "deterministic-grade.json");
53
+ const grade = await readJson(gradeFile, `grade for ${group.id}`);
54
+ const variants = selectedVariant === null ? VARIANTS : [selectedVariant];
55
+ const lines = [`Group ${group.id} [${group.state}] type=${definition.type}`];
56
+
57
+ appendSharedEvidence(lines, definition);
58
+ appendGradeEvidence(lines, grade, group, definition, selectedVariant);
59
+ for (const variant of variants) {
60
+ await appendVariantEvidence(lines, resultDirectory, groupDirectory, group.id, variant);
61
+ }
62
+ lines.push(
63
+ compactRow("artifacts", [
64
+ `definition=${relativeArtifact(resultDirectory, path.join(groupDirectory, "definition.json"))}`,
65
+ `grade=${relativeArtifact(resultDirectory, gradeFile)}`,
66
+ `group=${relativeArtifact(resultDirectory, path.join(groupDirectory, group.artifacts?.group ?? "group.json"))}`,
67
+ ]),
68
+ );
69
+ return lines;
70
+ }
71
+
72
+ function appendSharedEvidence(lines, definition) {
73
+ if (typeof definition.input?.prompt === "string") {
74
+ appendLabeledText(lines, "Prompt", definition.input.prompt);
75
+ } else {
76
+ const prompts = Array.isArray(definition.input?.turns) ? definition.input.turns : [];
77
+ lines.push(`Turns: ${prompts.length}`);
78
+ for (const [index, prompt] of prompts.entries()) {
79
+ appendLabeledText(lines, ` Turn ${index + 1} prompt`, prompt, " ");
80
+ }
81
+ }
82
+ const reviewQuestions = Array.isArray(definition.review_questions) ? definition.review_questions : [];
83
+ if (reviewQuestions.length > 0) {
84
+ lines.push("Review questions:");
85
+ for (const question of reviewQuestions) lines.push(` - ${question}`);
86
+ }
87
+ }
88
+
89
+ function appendGradeEvidence(lines, grade, group, definition, selectedVariant) {
90
+ const checks = Array.isArray(grade.checks)
91
+ ? grade.checks.filter((check) => selectedVariant === null || check.variant === selectedVariant)
92
+ : [];
93
+ const gradeErrors = selectedGradeErrors(grade.errors, definition, selectedVariant);
94
+ lines.push(`Grade [${selectedGradeState(grade.state, checks, gradeErrors, selectedVariant)}]`);
95
+ if (checks.length === 0) lines.push(" (no selected checks)");
96
+ else {
97
+ for (const check of checks) {
98
+ lines.push(
99
+ compactRow("check", [
100
+ check.id,
101
+ check.variant,
102
+ check.kind,
103
+ check.state,
104
+ `expected=${compactJson(check.expected)}`,
105
+ `observed=${compactObserved(check)}`,
106
+ ]),
107
+ );
108
+ }
109
+ }
110
+ if (gradeErrors.length > 0) {
111
+ lines.push("Grade errors:");
112
+ for (const error of gradeErrors) {
113
+ lines.push(compactRow("error", ["grade", error.id ?? "system", error.reason]));
114
+ }
115
+ }
116
+ if (Array.isArray(group.errors) && group.errors.length > 0) {
117
+ lines.push("Group errors:");
118
+ for (const error of group.errors) {
119
+ lines.push(compactRow("error", ["group", error.artifact ?? error.kind, error.message]));
120
+ }
121
+ }
122
+ if (selectedVariant === null && Array.isArray(grade.comparisons) && grade.comparisons.length > 0) {
123
+ lines.push("Comparisons:");
124
+ for (const comparison of grade.comparisons) {
125
+ lines.push(
126
+ compactRow("comparison", [
127
+ comparison.id,
128
+ comparison.kind,
129
+ comparison.transition,
130
+ `baseline=${comparison.baseline.check}:${comparison.baseline.state}`,
131
+ `candidate=${comparison.candidate.check}:${comparison.candidate.state}`,
132
+ ]),
133
+ );
134
+ }
135
+ }
136
+ }
137
+
138
+ function selectedGradeErrors(errors, definition, selectedVariant) {
139
+ if (!Array.isArray(errors)) return [];
140
+ if (selectedVariant === null) return errors;
141
+ const expectations = /** @type {Array<any>} */ (
142
+ Array.isArray(definition.expectations) ? definition.expectations : []
143
+ );
144
+ const expectationById = new Map(expectations.map((expectation) => [expectation.id, expectation]));
145
+ const comparisonIds = new Set(
146
+ expectations.map((expectation) => expectation.comparison).filter((comparison) => typeof comparison === "string"),
147
+ );
148
+ return errors.filter((error) => {
149
+ if (error.id === "system" || comparisonIds.has(error.id)) return true;
150
+ const expectation = /** @type {any} */ (expectationById.get(error.id));
151
+ return expectation === undefined || expectation.variant === selectedVariant;
152
+ });
153
+ }
154
+
155
+ function selectedGradeState(state, checks, errors, selectedVariant) {
156
+ if (selectedVariant === null) return state;
157
+ if (errors.length > 0) return "grade-error";
158
+ if (checks.length > 0 && checks.every((check) => check.state === "unavailable")) return "unavailable";
159
+ return "complete";
160
+ }
161
+
162
+ async function appendVariantEvidence(lines, resultDirectory, groupDirectory, groupId, variant) {
163
+ const runFile = path.join(groupDirectory, variant, "run.json");
164
+ const run = await readJson(runFile, `${variant} run for ${groupId}`);
165
+ lines.push("", `${title(variant)} [${run.state}]`);
166
+ appendRunEvidence(lines, run);
167
+ const variantDirectory = path.dirname(runFile);
168
+ const artifacts = [["run", relativeArtifact(resultDirectory, runFile)]];
169
+ if (typeof run.workspace === "string") {
170
+ artifacts.push(["workspace", relativeArtifact(resultDirectory, run.workspace)]);
171
+ }
172
+ if (typeof run.transcript === "string") {
173
+ artifacts.push(["transcript", relativeArtifact(resultDirectory, path.resolve(variantDirectory, run.transcript))]);
174
+ }
175
+ for (const [name, artifact] of Object.entries(run.artifacts ?? {})) {
176
+ if (typeof artifact === "string") {
177
+ artifacts.push([name, relativeArtifact(resultDirectory, path.resolve(variantDirectory, artifact))]);
178
+ }
179
+ }
180
+ lines.push(
181
+ compactRow(
182
+ " artifacts",
183
+ artifacts.map(([name, artifact]) => `${name}=${artifact}`),
184
+ ),
185
+ );
186
+ }
187
+
188
+ async function resolveResult(target, root) {
189
+ const explicit = path.resolve(root, target);
190
+ const stored = path.join(root, ".skill-eval", "runs", target);
191
+ const isPath = path.isAbsolute(target) || target.includes("/") || target.includes("\\") || target.startsWith(".");
192
+ const candidates = isPath ? [explicit] : [stored, explicit];
193
+ for (const candidate of candidates) {
194
+ const canonical = await realpath(candidate).catch(() => null);
195
+ if (canonical === null) continue;
196
+ const candidateStat = await stat(canonical).catch(() => null);
197
+ if (candidateStat?.isDirectory()) return canonical;
198
+ }
199
+ throw new ViewError(`Cannot find result: ${target}`);
200
+ }
201
+
202
+ async function readJson(file, label) {
203
+ let contents;
204
+ try {
205
+ contents = await readFile(file, "utf8");
206
+ } catch {
207
+ throw new ViewError(`Cannot read ${label}: ${file}`);
208
+ }
209
+ try {
210
+ return JSON.parse(contents);
211
+ } catch {
212
+ throw new ViewError(`${label} is not valid JSON: ${file}`);
213
+ }
214
+ }
215
+
216
+ function title(value) {
217
+ return `${value[0].toUpperCase()}${value.slice(1)}`;
218
+ }
219
+
220
+ function appendRunEvidence(lines, run) {
221
+ const turns = Array.isArray(run.turns) ? run.turns : null;
222
+ const hasObservedTurns = turns !== null && turns.length > 0;
223
+ if (run.evaluationType === "description" && (run.state === "complete" || hasObservedTurns)) {
224
+ const prefix = run.state === "complete" ? "" : "observed ";
225
+ lines.push(
226
+ ` ${prefix}target-read: ${run.activation?.targetRead === true ? "yes" : "no"}`,
227
+ ` ${prefix}read-turns: ${(run.activation?.readTurns ?? []).join(",") || "none"}`,
228
+ );
229
+ }
230
+ if (run.evaluationType === "body" && run.delivery) {
231
+ lines.push(` delivery: ${run.delivery.kind} on turn ${run.delivery.turn}`);
232
+ const activity = turns === null ? (run.toolActivity ?? []) : turns.flatMap((turn) => turn.toolActivity ?? []);
233
+ const toolsUsed = [...new Set(activity.map((entry) => entry.toolName).filter(Boolean))];
234
+ lines.push(` tools-used: ${toolsUsed.join(",") || "none"}`);
235
+ const changes = run.effects?.changes;
236
+ if (changes) {
237
+ appendChangedPaths(lines, "created", changes.created);
238
+ appendChangedPaths(lines, "modified", changes.modified);
239
+ appendChangedPaths(lines, "deleted", changes.deleted);
240
+ }
241
+ }
242
+ if (turns !== null) {
243
+ for (const turn of turns) {
244
+ const labels = [turn.settled ? "settled" : "unsettled"];
245
+ if (turn.targetRead === true) labels.push("target-read");
246
+ lines.push(` Turn ${turn.turn} [${labels.join(", ")}]`);
247
+ appendResponse(lines, turn.response ?? "", " ");
248
+ }
249
+ } else if (run.state === "complete") {
250
+ lines.push(" response:", indent(run.response ?? ""));
251
+ }
252
+ if (run.usage && typeof run.usage === "object") {
253
+ const usage = [
254
+ `input=${formatMetric(run.usage.input)}`,
255
+ `output=${formatMetric(run.usage.output)}`,
256
+ `cache-read=${formatMetric(run.usage.cacheRead)}`,
257
+ `cache-write=${formatMetric(run.usage.cacheWrite)}`,
258
+ ];
259
+ if (typeof run.usage.cost?.total === "number") usage.push(`cost=${formatNumber(run.usage.cost.total)}`);
260
+ lines.push(compactRow(" usage", usage));
261
+ }
262
+ if (run.process?.assistantError) lines.push(` assistant-error: ${run.process.assistantError}`);
263
+ if (run.process?.terminationReason) {
264
+ lines.push(` termination: ${run.process.terminationReason}`);
265
+ }
266
+ if (Array.isArray(run.process?.protocolErrors) && run.process.protocolErrors.length > 0) {
267
+ lines.push(` protocol-errors: ${run.process.protocolErrors.length}`);
268
+ }
269
+ if (run.error?.message) lines.push(` error: ${run.error.message}`);
270
+ }
271
+
272
+ function appendChangedPaths(lines, kind, paths) {
273
+ lines.push(compactRow(" changes", [kind, ...(Array.isArray(paths) ? paths : [])]));
274
+ }
275
+
276
+ function appendResponse(lines, response, prefix) {
277
+ const value = String(response);
278
+ if (!value.includes("\n")) {
279
+ lines.push(`${prefix}response: ${value}`);
280
+ return;
281
+ }
282
+ lines.push(`${prefix}response:`, indentWith(value, `${prefix} `));
283
+ }
284
+
285
+ function indentWith(value, prefix) {
286
+ return String(value)
287
+ .split("\n")
288
+ .map((line) => `${prefix}${line}`)
289
+ .join("\n");
290
+ }
291
+
292
+ function indent(value) {
293
+ return String(value)
294
+ .split("\n")
295
+ .map((line) => ` ${line}`)
296
+ .join("\n");
297
+ }
298
+
299
+ function appendLabeledText(lines, label, value, blockPrefix = " ") {
300
+ const text = String(value);
301
+ if (!text.includes("\n")) {
302
+ lines.push(`${label}: ${text}`);
303
+ return;
304
+ }
305
+ lines.push(`${label}:`, indentWith(text, blockPrefix));
306
+ }
307
+
308
+ function compactRow(kind, values) {
309
+ return [kind, ...values].map(escapeCell).join("\t");
310
+ }
311
+
312
+ function compactJson(value) {
313
+ return value === undefined ? "-" : JSON.stringify(value);
314
+ }
315
+
316
+ function compactObserved(check) {
317
+ if (check.kind === "response-text" && typeof check.observed?.response === "string") {
318
+ return `response@turn:${check.observed.turn ?? "final"}`;
319
+ }
320
+ return compactJson(check.observed);
321
+ }
322
+
323
+ function formatNumber(value) {
324
+ return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(8)));
325
+ }
326
+
327
+ function formatMetric(value) {
328
+ return typeof value === "number" && Number.isFinite(value) ? formatNumber(value) : "unavailable";
329
+ }
330
+
331
+ function escapeCell(value) {
332
+ return String(value).replace(/\\/g, "\\\\").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
333
+ }
334
+
335
+ function relativeArtifact(resultDirectory, file) {
336
+ return path.relative(resultDirectory, file).split(path.sep).join("/");
337
+ }
@@ -0,0 +1,164 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstatSync, readFileSync, realpathSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ const PATH_TOOLS = new Set(["read", "write", "edit"]);
6
+
7
+ function parseStringArray(value) {
8
+ try {
9
+ const parsed = JSON.parse(value ?? "[]");
10
+ return Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string") ? parsed : [];
11
+ } catch {
12
+ return [];
13
+ }
14
+ }
15
+
16
+ function isContained(root, target) {
17
+ const relative = path.relative(root, target);
18
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
19
+ }
20
+
21
+ function resolveWritablePath(cwd, requested) {
22
+ const absolute = path.resolve(cwd, requested);
23
+ /** @type {string[]} */
24
+ const missingSegments = [];
25
+ let candidate = absolute;
26
+ while (true) {
27
+ try {
28
+ return path.join(realpathSync(candidate), ...missingSegments);
29
+ } catch {
30
+ try {
31
+ lstatSync(candidate);
32
+ throw new Error(`Existing writable path cannot be resolved: ${requested}`);
33
+ } catch (error) {
34
+ if (error?.code !== "ENOENT" && error?.code !== "ENOTDIR") throw error;
35
+ }
36
+ const parent = path.dirname(candidate);
37
+ if (parent === candidate) throw new Error(`Cannot resolve writable path: ${requested}`);
38
+ missingSegments.unshift(path.basename(candidate));
39
+ candidate = parent;
40
+ }
41
+ }
42
+ }
43
+
44
+ function escapeXml(value) {
45
+ const entities = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" };
46
+ return value.replace(/[&<>"']/g, (character) => entities[character]);
47
+ }
48
+
49
+ function loadDeclaredContext(manifestPath) {
50
+ if (!manifestPath) return [];
51
+ let manifest;
52
+ try {
53
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
54
+ } catch (error) {
55
+ throw new Error(
56
+ `Declared context manifest cannot be read: ${error instanceof Error ? error.message : String(error)}`,
57
+ );
58
+ }
59
+ if (manifest?.schema_version !== 1 || !Array.isArray(manifest.entries)) {
60
+ throw new Error("Declared context manifest must use schema_version 1 and contain entries");
61
+ }
62
+ for (const entry of manifest.entries) {
63
+ if (typeof entry?.declaredPath !== "string" || !Array.isArray(entry.files)) {
64
+ throw new Error("Declared context manifest entry is malformed");
65
+ }
66
+ for (const file of entry.files) {
67
+ if (
68
+ typeof file?.path !== "string" ||
69
+ typeof file.sha256 !== "string" ||
70
+ typeof file.content !== "string" ||
71
+ createHash("sha256").update(file.content).digest("hex") !== file.sha256
72
+ ) {
73
+ throw new Error(`Declared context file is malformed or changed: ${file?.path ?? "unknown"}`);
74
+ }
75
+ }
76
+ }
77
+ return manifest.entries;
78
+ }
79
+
80
+ function buildSubjectSystemPrompt(options, declaredContext) {
81
+ const tools = options.selectedTools ?? [];
82
+ const toolLines = tools.map((tool) => `- ${tool}: ${options.toolSnippets?.[tool] ?? "Declared evaluation tool"}`);
83
+ const lines = [
84
+ "You are an expert coding assistant operating inside pi, a coding agent harness.",
85
+ "Help the user with the task using only the tools and resources listed below.",
86
+ "",
87
+ "Available tools:",
88
+ ...(toolLines.length > 0 ? toolLines : ["(none)"]),
89
+ "",
90
+ "Guidelines:",
91
+ "- Use read to load a skill's SKILL.md when the task matches its description.",
92
+ "- Resolve relative skill resources from the skill directory.",
93
+ "- Be concise in your response.",
94
+ ];
95
+ const skills = (options.skills ?? []).filter((skill) => !skill.disableModelInvocation);
96
+ if (skills.length > 0) {
97
+ lines.push("", "<available_skills>");
98
+ for (const skill of skills) {
99
+ lines.push(
100
+ " <skill>",
101
+ ` <name>${escapeXml(skill.name)}</name>`,
102
+ ` <description>${escapeXml(skill.description)}</description>`,
103
+ ` <location>${escapeXml(skill.filePath)}</location>`,
104
+ " </skill>",
105
+ );
106
+ }
107
+ lines.push("</available_skills>");
108
+ }
109
+ if (declaredContext.length > 0) {
110
+ lines.push("", "<declared_context>");
111
+ for (const entry of declaredContext) {
112
+ lines.push(` <context declared-path="${escapeXml(entry.declaredPath)}">`);
113
+ for (const file of entry.files) {
114
+ lines.push(` <file path="${escapeXml(file.path)}">\n${file.content}</file>`);
115
+ }
116
+ lines.push(" </context>");
117
+ }
118
+ lines.push("</declared_context>");
119
+ }
120
+ lines.push(`Current working directory: ${options.cwd}`);
121
+ return lines.join("\n");
122
+ }
123
+
124
+ export default function registerSkillEvalGuard(pi) {
125
+ const allowedRoots = parseStringArray(process.env.SKILL_EVAL_ALLOWED_ROOTS).map((root) => realpathSync(root));
126
+ const writableRootValue = process.env.SKILL_EVAL_WRITABLE_ROOT;
127
+ if (!writableRootValue) throw new Error("SKILL_EVAL_WRITABLE_ROOT is required");
128
+ const writableRoot = realpathSync(writableRootValue);
129
+ const allowedTools = new Set(parseStringArray(process.env.SKILL_EVAL_ALLOWED_TOOLS));
130
+ const declaredContext = loadDeclaredContext(process.env.SKILL_EVAL_CONTEXT_MANIFEST);
131
+
132
+ pi.on("before_agent_start", (event) => ({
133
+ systemPrompt: buildSubjectSystemPrompt(event.systemPromptOptions, declaredContext),
134
+ }));
135
+
136
+ pi.on("tool_call", (event, ctx) => {
137
+ if (!allowedTools.has(event.toolName)) {
138
+ return { block: true, reason: `Tool is not declared for this evaluation: ${event.toolName}` };
139
+ }
140
+ if (!PATH_TOOLS.has(event.toolName)) return undefined;
141
+ if (typeof event.input?.path !== "string") {
142
+ return { block: true, reason: `${event.toolName} requires a path inside the evaluation environment` };
143
+ }
144
+
145
+ const requested = event.input.path.startsWith("@") ? event.input.path.slice(1) : event.input.path;
146
+ let canonical;
147
+ try {
148
+ canonical =
149
+ event.toolName === "write"
150
+ ? resolveWritablePath(ctx.cwd, requested)
151
+ : realpathSync(path.resolve(ctx.cwd, requested));
152
+ } catch {
153
+ return {
154
+ block: true,
155
+ reason: `${event.toolName} path cannot be resolved inside the evaluation environment: ${requested}`,
156
+ };
157
+ }
158
+ const roots = event.toolName === "read" ? allowedRoots : [writableRoot];
159
+ if (!roots.some((root) => isContained(root, canonical))) {
160
+ return { block: true, reason: `${event.toolName} path escapes the evaluation environment: ${requested}` };
161
+ }
162
+ return undefined;
163
+ });
164
+ }
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runEvaluation } from "./lib/runner.mjs";
4
+ import { renderResult } from "./lib/view.mjs";
5
+
6
+ const commands = new Set(["run", "view"]);
7
+ const argv = process.argv.slice(2);
8
+ const [command] = argv;
9
+
10
+ function printUsage() {
11
+ process.stdout.write(
12
+ `Usage:
13
+ skill-eval run <suite-or-group-path> [options]
14
+ skill-eval view <result-id-or-directory> [options]
15
+
16
+ Commands:
17
+ run Execute selected evaluation groups
18
+ view Render selected stored evidence
19
+
20
+ Selectors:
21
+ --group <id-or-position>
22
+ --variant <baseline|candidate>
23
+
24
+ Paths:
25
+ Definition paths resolve from the current working directory.
26
+ Suite group references resolve relative to the suite file.
27
+ Results are stored under <cwd>/.skill-eval/runs/<result-id>.
28
+ view accepts a stored result ID or an explicit result directory.
29
+
30
+ Selection:
31
+ No selectors choose every suite group and both variants.
32
+ --group is invalid for a direct group definition or result.
33
+
34
+ Current run support:
35
+ Description and explicit-body groups with prompt or ordered turns
36
+ Working-tree or Git-backed ordered skills/context; optional fresh fixture copies
37
+ Description tools: read; body tools: read, write, edit
38
+ Deterministic reads, paths, changed paths, text, JSON, and factual comparisons
39
+ Ordered suites run serially and continue after isolated variant, group, or post-processing failures
40
+ Grade-first views show compact criterion details, usage, and result-relative artifact paths
41
+ `,
42
+ );
43
+ }
44
+
45
+ function fail(message, exitCode = 2) {
46
+ process.stderr.write(`${message}\n`);
47
+ process.exitCode = exitCode;
48
+ }
49
+
50
+ function parseOperation(values) {
51
+ const [target, ...options] = values;
52
+ if (!target) throw new Error(`skill-eval ${command} requires a target`);
53
+ let group = null;
54
+ let variant = null;
55
+ for (let index = 0; index < options.length; index += 1) {
56
+ const option = options[index];
57
+ const value = options[index + 1];
58
+ if (option !== "--group" && option !== "--variant") throw new Error(`Unknown option: ${option}`);
59
+ if (!value || value.startsWith("--")) throw new Error(`${option} requires a value`);
60
+ if (option === "--group") {
61
+ if (group !== null) throw new Error("--group may be supplied only once");
62
+ group = value;
63
+ } else {
64
+ if (variant !== null) throw new Error("--variant may be supplied only once");
65
+ variant = value;
66
+ }
67
+ index += 1;
68
+ }
69
+ return { target, selectors: { group, variant } };
70
+ }
71
+
72
+ if (!command || command === "--help" || command === "-h") {
73
+ printUsage();
74
+ } else if (!commands.has(command)) {
75
+ fail(`Unknown command: ${command}`);
76
+ } else {
77
+ try {
78
+ const operation = parseOperation(argv.slice(1));
79
+ if (command === "view") {
80
+ process.stdout.write(await renderResult(operation.target, operation.selectors, { root: process.cwd() }));
81
+ } else {
82
+ const controller = new AbortController();
83
+ const abort = () => controller.abort();
84
+ process.once("SIGINT", abort);
85
+ process.once("SIGTERM", abort);
86
+ try {
87
+ const result = await runEvaluation(operation.target, operation.selectors, {
88
+ root: process.cwd(),
89
+ signal: controller.signal,
90
+ });
91
+ process.stdout.write(`Result: ${result.id}\nPath: ${result.path}\nState: ${result.state}\n`);
92
+ if (result.state !== "complete") process.exitCode = 1;
93
+ } finally {
94
+ process.removeListener("SIGINT", abort);
95
+ process.removeListener("SIGTERM", abort);
96
+ }
97
+ }
98
+ } catch (error) {
99
+ fail(error instanceof Error ? error.message : String(error), 1);
100
+ }
101
+ }