@hassangameryt/freeflow 0.8.0 → 0.9.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 (290) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/README.md +96 -149
  7. package/capabilities/cognitive-routing/SKILL.md +21 -129
  8. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  9. package/capabilities/cognitive-routing/references/worker.md +38 -0
  10. package/capabilities/compaction/SKILL.md +3 -3
  11. package/capabilities/compaction/references/summary-format.md +26 -15
  12. package/capabilities/messaging/SKILL.md +49 -0
  13. package/capabilities/task-memory/SKILL.md +87 -0
  14. package/command-surface.json +6 -54
  15. package/gemini-extension.json +1 -1
  16. package/hooks/claude/hooks.json +13 -3
  17. package/hooks/codex/hooks.json +2 -1
  18. package/hooks/shared/runtime-context.mjs +69 -77
  19. package/package.json +25 -8
  20. package/pi-extension/dist/ask-user/ask.js +108 -0
  21. package/pi-extension/dist/ask-user/form.js +250 -0
  22. package/pi-extension/dist/ask-user/questions.js +66 -0
  23. package/pi-extension/dist/ask-user/tool.js +124 -0
  24. package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
  25. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  26. package/pi-extension/dist/cognitive-routing/config.js +3 -4
  27. package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
  28. package/pi-extension/dist/cognitive-routing/gate.js +20 -5
  29. package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
  30. package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
  31. package/pi-extension/dist/cognitive-routing/projection.js +23 -6
  32. package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
  33. package/pi-extension/dist/cognitive-routing/render.js +15 -15
  34. package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
  35. package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
  36. package/pi-extension/dist/cognitive-routing/session.js +3 -1
  37. package/pi-extension/dist/cognitive-routing/sources.js +29 -4
  38. package/pi-extension/dist/cognitive-routing/state.js +18 -8
  39. package/pi-extension/dist/cognitive-routing/status.js +4 -3
  40. package/pi-extension/dist/cognitive-routing/tools.js +29 -15
  41. package/pi-extension/dist/cognitive-routing/types.js +10 -1
  42. package/pi-extension/dist/compaction/carry.js +36 -22
  43. package/pi-extension/dist/compaction/config.js +4 -2
  44. package/pi-extension/dist/compaction/controller.js +52 -29
  45. package/pi-extension/dist/compaction/harness.js +19 -3
  46. package/pi-extension/dist/compaction/results.js +14 -13
  47. package/pi-extension/dist/compaction/tool.js +6 -6
  48. package/pi-extension/dist/host/catalog.js +20 -35
  49. package/pi-extension/dist/host/config-validation.js +233 -0
  50. package/pi-extension/dist/host/config.js +78 -91
  51. package/pi-extension/dist/host/inbound.js +142 -0
  52. package/pi-extension/dist/host/projection-tags.js +1 -1
  53. package/pi-extension/dist/host/prompts.js +24 -13
  54. package/pi-extension/dist/host/read-only-session.js +6 -1
  55. package/pi-extension/dist/host/request-history.js +7 -3
  56. package/pi-extension/dist/host/rows.js +25 -0
  57. package/pi-extension/dist/host/runtime-state.js +17 -2
  58. package/pi-extension/dist/host/section-keeper.js +85 -0
  59. package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
  60. package/pi-extension/dist/host/settings/settings-view.js +27 -8
  61. package/pi-extension/dist/host/status.js +19 -5
  62. package/pi-extension/dist/index.js +638 -72
  63. package/pi-extension/dist/messaging/config.js +27 -0
  64. package/pi-extension/dist/messaging/messaging.js +398 -0
  65. package/pi-extension/dist/messaging/registry.js +57 -0
  66. package/pi-extension/dist/messaging/render.js +90 -0
  67. package/pi-extension/dist/messaging/tools.js +109 -0
  68. package/pi-extension/dist/messaging/transport.js +101 -0
  69. package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
  70. package/pi-extension/dist/task-memory/config.js +49 -0
  71. package/pi-extension/dist/task-memory/registry.js +119 -0
  72. package/pi-extension/dist/task-memory/render.js +107 -0
  73. package/pi-extension/dist/task-memory/runtime.js +994 -0
  74. package/pi-extension/dist/task-memory/tool.js +233 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
  76. package/pi-extension/dist/tool-execution/background.js +220 -28
  77. package/pi-extension/dist/tool-execution/config.js +14 -3
  78. package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
  79. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  80. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  81. package/pi-extension/dist/tool-execution/tools.js +2 -2
  82. package/runtime/prompts/cognitive-routing.md +4 -4
  83. package/runtime/prompts/core.md +24 -14
  84. package/runtime/prompts/interaction-contract.md +7 -4
  85. package/runtime/prompts/tool-execution.md +3 -16
  86. package/runtime/prompts/working-method.md +16 -99
  87. package/skills/deliver-work/SKILL.md +76 -0
  88. package/skills/deliver-work/references/branch-closeout.md +134 -0
  89. package/skills/deliver-work/references/commit.md +83 -0
  90. package/skills/deliver-work/references/launch.md +94 -0
  91. package/skills/deliver-work/references/release.md +85 -0
  92. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  93. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  94. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  95. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  96. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  97. package/skills/discuss-work/SKILL.md +148 -0
  98. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
  99. package/skills/discuss-work/references/learning-action.md +36 -0
  100. package/skills/discuss-work/scripts/choices.mjs +101 -0
  101. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  102. package/skills/evaluate-behavior/SKILL.md +107 -0
  103. package/skills/evaluate-behavior/references/authoring.md +381 -0
  104. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  105. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  106. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  107. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  108. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  109. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  110. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  111. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  112. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  113. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  114. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  115. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  116. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  117. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  118. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  119. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  120. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  121. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  122. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  123. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  124. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  125. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  126. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  127. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  128. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  129. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  130. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  131. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  132. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  133. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  134. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  135. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  136. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  137. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  138. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  139. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  140. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  141. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  142. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  143. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  144. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  145. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  146. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  147. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  148. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  149. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  150. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  151. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  152. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  153. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  154. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
  156. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  157. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  158. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  159. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  160. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  161. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  162. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
  163. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  164. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  165. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  166. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  167. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
  168. package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
  169. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  170. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  171. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  172. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  173. package/skills/execute-work/SKILL.md +96 -104
  174. package/skills/execute-work/references/code-practices.md +27 -51
  175. package/skills/execute-work/references/design.md +271 -0
  176. package/skills/execute-work/references/diagnose.md +117 -0
  177. package/skills/execute-work/references/migration.md +108 -0
  178. package/skills/execute-work/references/simplify.md +65 -0
  179. package/skills/execute-work/scripts/self-review.mjs +422 -0
  180. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  181. package/skills/review-work/SKILL.md +89 -65
  182. package/skills/review-work/references/adjudication.md +94 -0
  183. package/skills/review-work/references/artifact.md +198 -0
  184. package/skills/review-work/references/evals-and-judges.md +66 -0
  185. package/skills/review-work/references/work.md +177 -0
  186. package/skills/review-work/scripts/check-brief.mjs +128 -0
  187. package/skills/review-work/scripts/review-packet.mjs +233 -0
  188. package/skills/setup-freeflow/SKILL.md +68 -17
  189. package/skills/track-work/SKILL.md +52 -20
  190. package/skills/track-work/references/handoff.md +190 -0
  191. package/skills/track-work/references/projects.md +51 -0
  192. package/skills/track-work/references/working-record-format.md +36 -18
  193. package/skills/track-work/scripts/lib/document.mjs +17 -13
  194. package/skills/track-work/scripts/lib/format.mjs +2 -0
  195. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  196. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  197. package/skills/track-work/scripts/lib/search.mjs +40 -0
  198. package/skills/track-work/scripts/lib/store.mjs +46 -15
  199. package/skills/track-work/scripts/lib/views.mjs +50 -1
  200. package/skills/track-work/scripts/working-record.mjs +232 -13
  201. package/skills/verify-work/SKILL.md +45 -37
  202. package/skills/verify-work/references/feature-map.md +120 -0
  203. package/skills/verify-work/references/runtime-evidence.md +203 -0
  204. package/skills/verify-work/references/test-design.md +25 -6
  205. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  206. package/skills/write-docs/SKILL.md +113 -25
  207. package/skills/write-docs/references/documentation-shapes.md +68 -3
  208. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  209. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  210. package/skills/write-docs/scripts/docs-check.mjs +96 -6
  211. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  212. package/skills/write-skill/SKILL.md +92 -52
  213. package/skills/write-skill/references/activation-and-wording.md +143 -0
  214. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  215. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  216. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  217. package/skills/write-skill/scripts/skill-author.mjs +51 -8
  218. package/capabilities/cognitive-routing/references/both-mode.md +0 -36
  219. package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
  220. package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
  221. package/plugin.json +0 -33
  222. package/skills/action-selection/SKILL.md +0 -92
  223. package/skills/action-selection/references/trajectory-stalls.md +0 -73
  224. package/skills/bypass/SKILL.md +0 -153
  225. package/skills/commit-work/SKILL.md +0 -93
  226. package/skills/commit-work/references/git-state-edges.md +0 -94
  227. package/skills/commit-work/references/simple-push.md +0 -94
  228. package/skills/commit-work/references/staging-decisions.md +0 -106
  229. package/skills/decision-gate/SKILL.md +0 -87
  230. package/skills/design-for-depth/SKILL.md +0 -108
  231. package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
  232. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  233. package/skills/design-for-depth/references/interface-design-loop.md +0 -105
  234. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
  235. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
  236. package/skills/diagnose-failure/SKILL.md +0 -140
  237. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
  238. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  239. package/skills/discuss/SKILL.md +0 -140
  240. package/skills/evaluate-skill/SKILL.md +0 -188
  241. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
  242. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  243. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
  244. package/skills/evaluate-skill/references/review-and-revision.md +0 -94
  245. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
  246. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  247. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  248. package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
  249. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
  250. package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
  251. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
  252. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
  253. package/skills/execute-work/references/execute-work-edges.md +0 -78
  254. package/skills/finish-branch/SKILL.md +0 -117
  255. package/skills/finish-branch/references/integration-options.md +0 -80
  256. package/skills/handoff/SKILL.md +0 -82
  257. package/skills/handoff/references/handoff-templates.md +0 -150
  258. package/skills/launch-work/SKILL.md +0 -116
  259. package/skills/launch-work/references/launch-readiness.md +0 -85
  260. package/skills/migration-work/SKILL.md +0 -115
  261. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  262. package/skills/release-work/SKILL.md +0 -118
  263. package/skills/release-work/references/release-evidence.md +0 -78
  264. package/skills/review-artifact/SKILL.md +0 -143
  265. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  266. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  267. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  268. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  269. package/skills/review-work/references/security-risk-lens.md +0 -67
  270. package/skills/setup-freeflow/references/activation-contract.md +0 -103
  271. package/skills/setup-freeflow/references/host-setup.md +0 -67
  272. package/skills/simplify-code/SKILL.md +0 -85
  273. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  274. package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
  275. package/skills/verify-work/references/integration-evidence.md +0 -49
  276. package/skills/verify-work/references/performance-evidence.md +0 -59
  277. package/skills/workflow/SKILL.md +0 -171
  278. package/skills/workflow/references/domain-skill-composition.md +0 -45
  279. package/skills/write-plan/SKILL.md +0 -92
  280. package/skills/write-plan/references/plan-shapes.md +0 -156
  281. package/skills/write-skill/references/activation-boundaries.md +0 -106
  282. package/skills/write-skill/references/agent-first-instructions.md +0 -133
  283. package/skills/write-skill/references/progressive-disclosure.md +0 -129
  284. package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
  285. package/skills/write-spec/SKILL.md +0 -85
  286. package/skills/write-spec/references/artifact-standards.md +0 -86
  287. package/skills/write-spec/references/decision-records.md +0 -80
  288. package/skills/write-spec/references/spec-shapes.md +0 -143
  289. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  290. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -1,196 +0,0 @@
1
- import { randomInt } from "node:crypto";
2
- import { mkdir, readFile, rm } from "node:fs/promises";
3
- import path from "node:path";
4
-
5
- import { sha256, writeJson, writeText } from "./evidence.mjs";
6
- import { baseProcessEnvironment, runPiProcess } from "./pi.mjs";
7
- import { resolveResult } from "./view.mjs";
8
-
9
- const ANSWERS = new Set(["yes", "no", "unclear"]);
10
- const RESPONSE_LIMIT = 8000;
11
- const ARGUMENT_LIMIT = 300;
12
-
13
- /**
14
- * Answers each group's review questions with a separate model that sees two unlabeled runs.
15
- * The result is advisory: it is written beside, never into, canonical run and grade evidence.
16
- *
17
- * @param {string} target
18
- * @param {{group: string | null}} selectors
19
- * @param {{root?: string, signal?: AbortSignal, model: string, thinking?: string | null}} options
20
- */
21
- export async function reviewResult(target, selectors, { root = process.cwd(), signal, model, thinking = null }) {
22
- const resultDirectory = await resolveResult(target, root);
23
- const summary = JSON.parse(await readFile(path.join(resultDirectory, "summary.json"), "utf8"));
24
- const groups = selectGroups(summary, selectors.group);
25
- const outcomes = [];
26
- for (const group of groups) {
27
- if (signal?.aborted) break;
28
- outcomes.push(
29
- await reviewGroup(path.join(resultDirectory, "groups", group.id), group.id, { model, thinking, signal }),
30
- );
31
- }
32
- return { resultDirectory, groups: outcomes };
33
- }
34
-
35
- function selectGroups(summary, selector) {
36
- const groups = Array.isArray(summary.groups) ? summary.groups : [];
37
- if (selector === null) return groups;
38
- if (summary.definitionKind === "group") throw new Error("--group cannot be used with a direct group result");
39
- const position = /^\d+$/.test(selector) ? Number(selector) : null;
40
- const selected = groups.filter((group) => (position === null ? group.id === selector : group.position === position));
41
- if (selected.length === 0) throw new Error(`Unknown group selector: ${selector}`);
42
- return selected;
43
- }
44
-
45
- async function reviewGroup(groupDirectory, id, { model, thinking, signal }) {
46
- const definition = JSON.parse(await readFile(path.join(groupDirectory, "definition.json"), "utf8"));
47
- const questions = Array.isArray(definition.review_questions) ? definition.review_questions : [];
48
- if (questions.length === 0) return { id, state: "skipped", reason: "no review questions" };
49
- const runs = {};
50
- for (const variant of ["baseline", "candidate"]) {
51
- runs[variant] = JSON.parse(await readFile(path.join(groupDirectory, variant, "run.json"), "utf8"));
52
- if (runs[variant].state !== "complete")
53
- return { id, state: "skipped", reason: `${variant} run is ${runs[variant].state}` };
54
- }
55
-
56
- const labels = randomInt(2) === 0 ? { A: "baseline", B: "candidate" } : { A: "candidate", B: "baseline" };
57
- const prompt = reviewPrompt(definition, questions, { A: runs[labels.A], B: runs[labels.B] });
58
- const reviewDirectory = path.join(groupDirectory, "review");
59
- await rm(reviewDirectory, { recursive: true, force: true });
60
- await mkdir(reviewDirectory, { recursive: true });
61
- await writeText(path.join(reviewDirectory, "prompt.md"), prompt);
62
-
63
- const args = [
64
- "--mode",
65
- "json",
66
- "-p",
67
- "--no-session",
68
- "--no-extensions",
69
- "--no-skills",
70
- "--no-prompt-templates",
71
- "--no-themes",
72
- "--no-context-files",
73
- "--no-approve",
74
- "--no-tools",
75
- "--model",
76
- model,
77
- ];
78
- if (thinking !== null) args.push("--thinking", thinking);
79
- args.push(prompt);
80
- const observation = await runPiProcess({
81
- command: "pi",
82
- args,
83
- cwd: reviewDirectory,
84
- eventsFile: path.join(reviewDirectory, "events.jsonl"),
85
- stderrFile: path.join(reviewDirectory, "stderr.log"),
86
- signal,
87
- environment: { ...baseProcessEnvironment(), PWD: reviewDirectory, PI_SKIP_VERSION_CHECK: "1", PI_TELEMETRY: "0" },
88
- });
89
- await writeText(path.join(reviewDirectory, "final.md"), `${observation.response}\n`);
90
-
91
- const errors = [];
92
- if (observation.exitCode !== 0 || !observation.settled || observation.assistantError !== null) {
93
- errors.push(
94
- `reviewer did not complete: exit=${observation.exitCode} settled=${observation.settled} error=${observation.assistantError}`,
95
- );
96
- }
97
- const answers = errors.length === 0 ? parseAnswers(observation.response, questions, labels, errors) : [];
98
- const state = errors.length === 0 ? "complete" : "review-error";
99
- await writeJson(path.join(groupDirectory, "semantic-grade.json"), {
100
- schema_version: 1,
101
- kind: "advisory-review",
102
- advisory: true,
103
- state,
104
- model,
105
- thinking,
106
- labels,
107
- prompt: { file: "review/prompt.md", sha256: sha256(prompt) },
108
- answers,
109
- errors,
110
- usage: observation.usage,
111
- artifacts: { events: "review/events.jsonl", final: "review/final.md", stderr: "review/stderr.log" },
112
- completedAt: new Date().toISOString(),
113
- });
114
- return { id, state, artifact: "semantic-grade.json" };
115
- }
116
-
117
- function reviewPrompt(definition, questions, runs) {
118
- const task =
119
- typeof definition.input?.prompt === "string" ? [definition.input.prompt] : (definition.input?.turns ?? []);
120
- const lines = [
121
- "You are reviewing two anonymous runs, Run A and Run B, of the same agent task.",
122
- "Judge only the evidence below. Answer every question for both runs with yes, no, or unclear.",
123
- "",
124
- "Task turns:",
125
- ...task.map((turn, index) => `${index + 1}. ${turn}`),
126
- "",
127
- "Questions:",
128
- ...questions.map((question, index) => `${index + 1}. ${question}`),
129
- ];
130
- for (const label of ["A", "B"]) lines.push("", `## Run ${label}`, ...runEvidence(runs[label]));
131
- lines.push(
132
- "",
133
- "Reply with only JSON in this shape:",
134
- '{"answers":[{"question":1,"A":"yes|no|unclear","B":"yes|no|unclear","rationale":"one sentence"}]}',
135
- );
136
- return lines.join("\n");
137
- }
138
-
139
- function runEvidence(run) {
140
- const turns = Array.isArray(run.turns)
141
- ? run.turns
142
- : [{ turn: 1, response: run.response, toolActivity: run.toolActivity }];
143
- const lines = [];
144
- for (const turn of turns) {
145
- lines.push(`Turn ${turn.turn ?? 1} response:`, truncate(String(turn.response ?? ""), RESPONSE_LIMIT));
146
- for (const call of turn.toolActivity ?? []) {
147
- const status = call.completed ? (call.isError ? "failed" : "succeeded") : "incomplete";
148
- lines.push(`- tool ${call.toolName} ${status}: ${truncate(JSON.stringify(call.args ?? null), ARGUMENT_LIMIT)}`);
149
- }
150
- }
151
- const changes = run.effects?.changes;
152
- if (changes) {
153
- for (const kind of ["created", "modified", "deleted"]) {
154
- lines.push(`Files ${kind}: ${(changes[kind] ?? []).join(", ") || "none"}`);
155
- }
156
- }
157
- return lines;
158
- }
159
-
160
- function truncate(value, limit) {
161
- return value.length <= limit ? value : `${value.slice(0, limit)}… [truncated ${value.length - limit} characters]`;
162
- }
163
-
164
- function parseAnswers(response, questions, labels, errors) {
165
- const text = response
166
- .trim()
167
- .replace(/^```(?:json)?\s*/i, "")
168
- .replace(/\s*```$/, "");
169
- let parsed;
170
- try {
171
- parsed = JSON.parse(text);
172
- } catch {
173
- errors.push("reviewer response is not JSON");
174
- return [];
175
- }
176
- const answers = Array.isArray(parsed?.answers) ? parsed.answers : null;
177
- if (answers === null || answers.length !== questions.length) {
178
- errors.push(`reviewer must answer exactly ${questions.length} question(s)`);
179
- return [];
180
- }
181
- const mapped = [];
182
- for (const [index, question] of questions.entries()) {
183
- const answer = answers.find((entry) => entry?.question === index + 1);
184
- if (!answer || !ANSWERS.has(answer.A) || !ANSWERS.has(answer.B) || typeof answer.rationale !== "string") {
185
- errors.push(`reviewer answer for question ${index + 1} is missing or invalid`);
186
- continue;
187
- }
188
- mapped.push({
189
- question,
190
- [labels.A]: answer.A,
191
- [labels.B]: answer.B,
192
- rationale: answer.rationale,
193
- });
194
- }
195
- return errors.length === 0 ? mapped : [];
196
- }
@@ -1,405 +0,0 @@
1
- import { mkdir, realpath } from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- import { COMMAND_TOOL, loadDefinition, selectDefinition } from "./definitions.mjs";
5
- import { createInvocationId, fileIdentity, writeJson, writeText } from "./evidence.mjs";
6
- import { gradeDeterministic } from "./grade.mjs";
7
- import { runBody, runDescription, VariantSetupError } from "./pi.mjs";
8
- import { prepareFixture, prepareRuntime } from "./sandbox.mjs";
9
-
10
- const VARIANTS = ["baseline", "candidate"];
11
- const DESCRIPTION_TOOLS = new Set(["read"]);
12
- const BODY_TOOLS = new Set(["read", "write", "edit"]);
13
- // Native tools must be separated from extension tools before CLI tool selection is built.
14
- const NATIVE_TOOLS = new Set(["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"]);
15
- const DEFAULT_RUNTIME = {
16
- host: "pi",
17
- session: false,
18
- prompt: "isolated",
19
- extensions: [],
20
- environment: { literal: {}, inherit: [] },
21
- };
22
-
23
- /**
24
- * @param {string} definitionFile
25
- * @param {any} selectors
26
- * @param {{root?: string, signal?: AbortSignal, budget?: {maxCost: number, spent: number, exhausted: boolean} | null}} options
27
- */
28
- export async function runEvaluation(
29
- definitionFile,
30
- selectors = {},
31
- { root = process.cwd(), signal, budget = null } = {},
32
- ) {
33
- const definitionPath = await realpath(path.resolve(root, definitionFile));
34
- const definition = await loadDefinition(definitionPath, { root });
35
- const selection = selectDefinition(definition, selectors);
36
- assertSupportedSelection(selection);
37
- const invocationId = createInvocationId();
38
- const runsDirectory = path.join(root, ".skill-eval", "runs");
39
- const resultDirectory = path.join(runsDirectory, invocationId);
40
- await mkdir(runsDirectory, { recursive: true });
41
- await mkdir(resultDirectory);
42
- await writeJson(path.join(resultDirectory, "invocation.json"), {
43
- schema_version: 1,
44
- id: invocationId,
45
- definition: definitionPath,
46
- selection: {
47
- group: selectors.group ?? null,
48
- variant: selectors.variant ?? null,
49
- groups: selection.groups.map((entry) => ({
50
- id: entry.group.id,
51
- position: entry.position,
52
- variants: entry.variants,
53
- })),
54
- },
55
- startedAt: new Date().toISOString(),
56
- });
57
-
58
- // Budget exhaustion cancels only queued subjects; a subject already running completes and is charged.
59
- const controller = new AbortController();
60
- const onParentAbort = () => controller.abort();
61
- if (signal?.aborted) controller.abort();
62
- else signal?.addEventListener("abort", onParentAbort, { once: true });
63
- const charge = (run) => {
64
- if (budget === null) return;
65
- budget.spent += run.usage?.cost?.total ?? 0;
66
- if (budget.spent >= budget.maxCost) {
67
- budget.exhausted = true;
68
- controller.abort();
69
- }
70
- };
71
-
72
- const groups = [];
73
- try {
74
- for (const selected of selection.groups) {
75
- groups.push(await runGroup({ selected, root, resultDirectory, signal: controller.signal, charge }));
76
- }
77
- } finally {
78
- signal?.removeEventListener("abort", onParentAbort);
79
- }
80
- const state = batchState(groups);
81
- const summary = {
82
- schema_version: 1,
83
- id: invocationId,
84
- state,
85
- definitionKind: selection.definitionKind,
86
- groups,
87
- completedAt: new Date().toISOString(),
88
- };
89
- if (budget !== null) summary.budget = budgetEvidence(budget);
90
- await writeJson(path.join(resultDirectory, "summary.json"), summary);
91
- return { id: invocationId, path: resultDirectory, state, summary };
92
- }
93
-
94
- function assertSupportedSelection(selection) {
95
- for (const selected of selection.groups) {
96
- const { group } = selected;
97
- if (group.type !== "description" && group.type !== "body") {
98
- throw new Error("skill-eval run currently supports description and body groups only");
99
- }
100
- if (group.expectations.some((expectation) => expectation.kind === "command")) {
101
- throw new Error("command expectations are not supported");
102
- }
103
- const supportedTools = group.type === "body" ? BODY_TOOLS : DESCRIPTION_TOOLS;
104
- const unsupportedNativeTools = group.tools.filter((tool) => NATIVE_TOOLS.has(tool) && !supportedTools.has(tool));
105
- if (unsupportedNativeTools.length > 0) {
106
- throw new Error(`${group.type} execution does not support native tools: ${unsupportedNativeTools.join(", ")}`);
107
- }
108
- // The evaluator supplies run_command itself when the definition declares commands.
109
- const customTools = group.tools.filter(
110
- (tool) => !NATIVE_TOOLS.has(tool) && !supportedTools.has(tool) && tool !== COMMAND_TOOL,
111
- );
112
- if (group.type === "description" && customTools.length > 0) {
113
- throw new Error(`${group.type} execution does not support custom tools: ${customTools.join(", ")}`);
114
- }
115
- if (customTools.length > 0 && (group.runtime?.extensions?.length ?? 0) === 0) {
116
- throw new Error(
117
- `${group.type} execution requires declared extensions for custom tools: ${customTools.join(", ")}`,
118
- );
119
- }
120
- }
121
- }
122
-
123
- export function budgetEvidence(budget) {
124
- return { maxCost: budget.maxCost, spent: Number(budget.spent.toFixed(10)), exhausted: budget.exhausted };
125
- }
126
-
127
- async function runGroup({ selected, root, resultDirectory, signal, charge }) {
128
- const { group } = selected;
129
- const groupDirectory = path.join(resultDirectory, "groups", group.id);
130
- await mkdir(groupDirectory, { recursive: true });
131
- await writeJson(path.join(groupDirectory, "definition.json"), group);
132
-
133
- let fixture = null;
134
- let runtime = null;
135
- let resourceError = null;
136
- if (!signal?.aborted) {
137
- try {
138
- fixture = await prepareFixture({ declaredPath: group.fixture, root, groupDirectory });
139
- runtime = await prepareRuntime({
140
- runtime: group.runtime ?? DEFAULT_RUNTIME,
141
- root,
142
- groupDirectory,
143
- });
144
- } catch (error) {
145
- resourceError = error instanceof Error ? error.message : String(error);
146
- }
147
- }
148
-
149
- const runs = {};
150
- const runFiles = {};
151
- for (const variant of VARIANTS) {
152
- const variantDirectory = path.join(groupDirectory, variant);
153
- await mkdir(variantDirectory, { recursive: true });
154
- if (selected.variants[variant] === "not-selected") {
155
- runs[variant] = notSelectedRun(group, variant);
156
- } else if (signal?.aborted) {
157
- runs[variant] = cancelledRun(group, variant);
158
- } else if (resourceError === null) {
159
- runs[variant] = await executeVariant({ group, variant, root, variantDirectory, fixture, runtime, signal });
160
- charge(runs[variant]);
161
- } else {
162
- runs[variant] = invalidRun(group, variant, resourceError);
163
- }
164
- const persisted = await persistRunOrFailure(variantDirectory, runs[variant]);
165
- runs[variant] = persisted.run;
166
- runFiles[variant] = persisted.file;
167
- }
168
-
169
- const evidence = {
170
- baseline: await fileIdentity(runFiles.baseline),
171
- candidate: await fileIdentity(runFiles.candidate),
172
- };
173
- const grade = await createGrade(group, runs, evidence);
174
- const persistedGrade = await persistGrade(groupDirectory, grade);
175
- const selectedRuns = VARIANTS.filter((variant) => selected.variants[variant] === "selected").map(
176
- (variant) => runs[variant],
177
- );
178
- const selectedState = selectedGroupState(selectedRuns, persistedGrade.grade);
179
- let groupState = selectedState;
180
- if (selectedState !== "cancelled" && resourceError !== null) groupState = "invalid";
181
- const groupResult = {
182
- id: group.id,
183
- position: selected.position,
184
- state: groupState,
185
- variants: Object.fromEntries(VARIANTS.map((variant) => [variant, runs[variant].state])),
186
- grade: persistedGrade.grade.state,
187
- artifacts: { grade: persistedGrade.artifact, group: "group.json" },
188
- errors: persistedGrade.errors,
189
- };
190
- return persistGroupResult(groupDirectory, groupResult);
191
- }
192
-
193
- function batchState(groups) {
194
- if (groups.some((group) => group.state === "cancelled")) return "cancelled";
195
- if (groups.every((group) => group.state === "complete")) return "complete";
196
- return "partially-complete";
197
- }
198
-
199
- function selectedGroupState(runs, grade) {
200
- if (runs.some((run) => run.state === "cancelled")) return "cancelled";
201
- if (runs.every((run) => run.state === "complete") && grade.state !== "grade-error") return "complete";
202
- return "partially-complete";
203
- }
204
-
205
- async function createGrade(group, runs, evidence) {
206
- try {
207
- return await gradeDeterministic(group, runs, evidence);
208
- } catch (error) {
209
- return gradeErrorReport(evidence, `deterministic grading failed: ${errorMessage(error)}`);
210
- }
211
- }
212
-
213
- async function persistGrade(groupDirectory, grade) {
214
- const artifact = "deterministic-grade.json";
215
- try {
216
- await writeJson(path.join(groupDirectory, artifact), grade);
217
- return { grade, artifact, errors: [] };
218
- } catch (error) {
219
- const reason = `failed to persist ${artifact}: ${errorMessage(error)}`;
220
- const failed = gradeErrorReport(grade.evidence, reason, grade);
221
- const fallback = "deterministic-grade-error.json";
222
- await writeJson(path.join(groupDirectory, fallback), failed);
223
- return {
224
- grade: failed,
225
- artifact: fallback,
226
- errors: [{ kind: "persistence", artifact, message: reason }],
227
- };
228
- }
229
- }
230
-
231
- async function persistGroupResult(groupDirectory, groupResult) {
232
- const artifact = "group.json";
233
- try {
234
- await writeJson(path.join(groupDirectory, artifact), groupResult);
235
- return groupResult;
236
- } catch (error) {
237
- const message = `failed to persist ${artifact}: ${errorMessage(error)}`;
238
- const fallback = "group-error.json";
239
- const failed = {
240
- ...groupResult,
241
- state: groupResult.state === "complete" ? "partially-complete" : groupResult.state,
242
- artifacts: { ...groupResult.artifacts, group: fallback },
243
- errors: [...groupResult.errors, { kind: "persistence", artifact, message }],
244
- };
245
- await writeJson(path.join(groupDirectory, fallback), failed);
246
- return failed;
247
- }
248
- }
249
-
250
- function gradeErrorReport(evidence, reason, grade = null) {
251
- return {
252
- schema_version: 1,
253
- state: "grade-error",
254
- evidence,
255
- checks: grade?.checks ?? [],
256
- comparisons: grade?.comparisons ?? [],
257
- errors: [...(grade?.errors ?? []), { id: null, reason }],
258
- };
259
- }
260
-
261
- function errorMessage(error) {
262
- return error instanceof Error ? error.message : String(error);
263
- }
264
-
265
- async function executeVariant({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
266
- try {
267
- const run = group.type === "body" ? runBody : runDescription;
268
- return await run({ group, variant, root, variantDirectory, fixture, runtime, signal });
269
- } catch (error) {
270
- if (error instanceof VariantSetupError) return invalidRun(group, variant, error.message);
271
- return infrastructureFailedRun(group, variant, error);
272
- }
273
- }
274
-
275
- async function persistRunOrFailure(variantDirectory, run) {
276
- try {
277
- return { run, file: await persistRun(variantDirectory, run) };
278
- } catch (error) {
279
- const failed = persistenceFailedRun(run, error);
280
- const runFile = path.join(variantDirectory, "run.json");
281
- await writeJson(runFile, {
282
- ...failed,
283
- transcript: failed.transcript ?? [],
284
- artifacts: {
285
- events: failed.process === undefined ? null : "events.jsonl",
286
- final: null,
287
- stderr: failed.process === undefined ? null : "stderr.log",
288
- },
289
- });
290
- return { run: failed, file: runFile };
291
- }
292
- }
293
-
294
- async function persistRun(variantDirectory, run) {
295
- const transcript = run.transcript ?? [];
296
- const response = run.response ?? "";
297
- await writeJson(path.join(variantDirectory, "transcript.json"), transcript);
298
- await writeText(path.join(variantDirectory, "final.md"), response === "" ? "" : `${response}\n`);
299
- const runFile = path.join(variantDirectory, "run.json");
300
- const hasProcessEvidence = run.process !== undefined;
301
- const persisted = {
302
- ...run,
303
- transcript: "transcript.json",
304
- artifacts: {
305
- events: hasProcessEvidence ? "events.jsonl" : null,
306
- final: "final.md",
307
- stderr: hasProcessEvidence ? "stderr.log" : null,
308
- context: run.contextObservationArtifact ? "context-observations.jsonl" : null,
309
- },
310
- };
311
- if (Array.isArray(run.turns)) persisted.turns = externalizeTurnTranscripts(run.turns);
312
- await writeJson(runFile, persisted);
313
- return runFile;
314
- }
315
-
316
- function externalizeTurnTranscripts(turns) {
317
- let offset = 0;
318
- return turns.map((turn) => {
319
- const length = Array.isArray(turn.transcript) ? turn.transcript.length : 0;
320
- const transcript = { file: "transcript.json", start: offset, end: offset + length };
321
- offset += length;
322
- return { ...turn, transcript };
323
- });
324
- }
325
-
326
- function notSelectedRun(group, variant) {
327
- return {
328
- schema_version: 1,
329
- variant,
330
- state: "not-selected",
331
- evaluationType: group.type,
332
- prompt: group.input.prompt ?? null,
333
- prompts: group.input.turns ?? null,
334
- activation: unavailableActivation(),
335
- response: "",
336
- transcript: [],
337
- error: null,
338
- };
339
- }
340
-
341
- function cancelledRun(group, variant) {
342
- return {
343
- schema_version: 1,
344
- variant,
345
- state: "cancelled",
346
- evaluationType: group.type,
347
- prompt: group.input.prompt ?? null,
348
- prompts: group.input.turns ?? null,
349
- activation: unavailableActivation(),
350
- response: "",
351
- transcript: [],
352
- error: { kind: "cancellation", message: "Cancelled before subject execution" },
353
- };
354
- }
355
-
356
- function invalidRun(group, variant, message) {
357
- return {
358
- schema_version: 1,
359
- variant,
360
- state: "invalid",
361
- evaluationType: group.type,
362
- prompt: group.input.prompt ?? null,
363
- prompts: group.input.turns ?? null,
364
- activation: unavailableActivation(),
365
- response: "",
366
- transcript: [],
367
- error: { kind: "setup", message },
368
- };
369
- }
370
-
371
- function persistenceFailedRun(run, error) {
372
- return {
373
- ...run,
374
- state: "infrastructure-failed",
375
- error: {
376
- kind: "persistence",
377
- message: error instanceof Error ? error.message : String(error),
378
- cause: run.error ?? null,
379
- },
380
- };
381
- }
382
-
383
- function infrastructureFailedRun(group, variant, error) {
384
- return {
385
- schema_version: 1,
386
- variant,
387
- state: "infrastructure-failed",
388
- evaluationType: group.type,
389
- prompt: group.input.prompt ?? null,
390
- prompts: group.input.turns ?? null,
391
- activation: unavailableActivation(),
392
- response: "",
393
- transcript: [],
394
- error: { kind: "infrastructure", message: error instanceof Error ? error.message : String(error) },
395
- };
396
- }
397
-
398
- function unavailableActivation() {
399
- return {
400
- targetRead: null,
401
- firstReadTurn: null,
402
- readTurns: [],
403
- successfulReadPaths: [],
404
- };
405
- }
@@ -1,103 +0,0 @@
1
- import { mkdir, readFile } from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- import { createInvocationId, writeJson } from "./evidence.mjs";
5
- import { budgetEvidence, runEvaluation } from "./runner.mjs";
6
-
7
- const CHECK_STATES = ["pass", "fail", "unavailable"];
8
-
9
- /**
10
- * Runs the selection as independent complete invocations and records an aggregate beside them.
11
- * Each trial remains an ordinary result; the aggregate only counts their deterministic grades.
12
- *
13
- * @param {string} definitionFile
14
- * @param {any} selectors
15
- * @param {{root?: string, signal?: AbortSignal, trials: number, maxCost: number | null, onTrial?: (result: any) => void}} options
16
- */
17
- export async function runTrials(definitionFile, selectors, { root = process.cwd(), signal, trials, maxCost, onTrial }) {
18
- const budget = maxCost === null ? null : { maxCost, spent: 0, exhausted: false };
19
- const results = [];
20
- for (let trial = 0; trial < trials; trial += 1) {
21
- if (signal?.aborted || budget?.exhausted) break;
22
- const result = await runEvaluation(definitionFile, selectors, { root, signal, budget });
23
- results.push(result);
24
- onTrial?.(result);
25
- }
26
-
27
- const id = createInvocationId();
28
- const directory = path.join(root, ".skill-eval", "runs", id);
29
- await mkdir(directory, { recursive: true });
30
- const aggregate = {
31
- schema_version: 1,
32
- kind: "aggregate",
33
- id,
34
- state: aggregateState(results, trials),
35
- requestedTrials: trials,
36
- trials: results.map((result) => ({ id: result.id, state: result.state })),
37
- groups: await aggregateGroups(results),
38
- completedAt: new Date().toISOString(),
39
- };
40
- if (budget !== null) aggregate.budget = budgetEvidence(budget);
41
- await writeJson(path.join(directory, "aggregate.json"), aggregate);
42
- return { id, path: directory, state: aggregate.state, aggregate };
43
- }
44
-
45
- function aggregateState(results, requested) {
46
- if (results.length < requested || results.some((result) => result.state === "cancelled")) return "cancelled";
47
- if (results.every((result) => result.state === "complete")) return "complete";
48
- return "partially-complete";
49
- }
50
-
51
- async function aggregateGroups(results) {
52
- /** @type {Map<string, any>} */
53
- const groups = new Map();
54
- for (const result of results) {
55
- for (const group of result.summary.groups) {
56
- const entry = groups.get(group.id) ?? { id: group.id, trials: 0, comparisons: new Map(), checks: new Map() };
57
- groups.set(group.id, entry);
58
- const gradeFile = path.join(
59
- result.path,
60
- "groups",
61
- group.id,
62
- group.artifacts?.grade ?? "deterministic-grade.json",
63
- );
64
- const grade = JSON.parse(await readFile(gradeFile, "utf8"));
65
- entry.trials += 1;
66
- for (const check of grade.checks ?? []) {
67
- const counts = entry.checks.get(check.id) ?? {
68
- id: check.id,
69
- variant: check.variant,
70
- kind: check.kind,
71
- ...zeroCounts(),
72
- };
73
- entry.checks.set(check.id, counts);
74
- if (CHECK_STATES.includes(check.state)) counts[check.state] += 1;
75
- }
76
- for (const comparison of grade.comparisons ?? []) {
77
- const counts = entry.comparisons.get(comparison.id) ?? {
78
- id: comparison.id,
79
- kind: comparison.kind,
80
- baseline: zeroCounts(),
81
- candidate: zeroCounts(),
82
- transitions: {},
83
- };
84
- entry.comparisons.set(comparison.id, counts);
85
- for (const variant of ["baseline", "candidate"]) {
86
- const state = comparison[variant]?.state;
87
- if (CHECK_STATES.includes(state)) counts[variant][state] += 1;
88
- }
89
- counts.transitions[comparison.transition] = (counts.transitions[comparison.transition] ?? 0) + 1;
90
- }
91
- }
92
- }
93
- return [...groups.values()].map((entry) => ({
94
- id: entry.id,
95
- trials: entry.trials,
96
- comparisons: [...entry.comparisons.values()],
97
- checks: [...entry.checks.values()],
98
- }));
99
- }
100
-
101
- function zeroCounts() {
102
- return { pass: 0, fail: 0, unavailable: 0 };
103
- }