@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
@@ -0,0 +1,287 @@
1
+ // Host extensions built from source: a directory at an exact commit of the definition root's repository, or a
2
+ // committed directory under the definition root, optionally built with a declared argv. Each distinct source is
3
+ // snapshotted and built once per group into the group's resources, before any subject starts; a failed build
4
+ // makes the group invalid. The resolved commit, a hash of the built directory and the build's outcome are recorded
5
+ // beside each arm's other host extensions.
6
+
7
+ import { spawn } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
+ import { cp, lstat, mkdir, realpath, stat } from "node:fs/promises";
10
+ import path from "node:path";
11
+
12
+ import { runProcess } from "./grade-command.mjs";
13
+ import { isContained, isSafeRelativePath } from "./grade-support.mjs";
14
+
15
+ const BUILD_TIMEOUT_MS = 10 * 60 * 1000;
16
+ const COMMIT_REF = /^[0-9a-f]{7,64}$/;
17
+ const OUTPUT_TAIL_CHARACTERS = 4000;
18
+
19
+ function isPlainObject(value) {
20
+ return value !== null && typeof value === "object" && !Array.isArray(value);
21
+ }
22
+
23
+ /** Whether a declared host extension is built from source rather than found among installed Pi packages. */
24
+ export function isSourceExtension(entry) {
25
+ return isPlainObject(entry) && !Object.hasOwn(entry, "package");
26
+ }
27
+
28
+ /** A declared host extension as the run plan names it. */
29
+ export function extensionLabel(entry) {
30
+ if (!isSourceExtension(entry)) return `${entry.package}@${entry.version}`;
31
+ const built = entry.build === undefined ? "" : " (built)";
32
+ return entry.source === undefined
33
+ ? `${entry.path}${built} from the working tree`
34
+ : `${entry.source.path}@${entry.source.ref}${built}`;
35
+ }
36
+
37
+ /**
38
+ * Validates one source-built host extension entry: `{ source: { kind: "git", ref, path }, build?, entry }` or
39
+ * `{ path, build?, entry }`.
40
+ *
41
+ * @param {any} entry
42
+ * @param {string} label
43
+ * @param {(code: string, message: string) => never} fail
44
+ */
45
+ export function validateSourceExtension(entry, label, fail) {
46
+ if (!isPlainObject(entry)) fail("invalid-definition", `${label} must be an object`);
47
+ const allowed = Object.hasOwn(entry, "source") ? ["source", "build", "entry"] : ["path", "build", "entry"];
48
+ for (const key of Object.keys(entry)) {
49
+ if (!allowed.includes(key)) fail("invalid-definition", `${label} contains unsupported field ${key}`);
50
+ }
51
+ if (Object.hasOwn(entry, "source")) {
52
+ const source = entry.source;
53
+ if (!isPlainObject(source)) fail("invalid-definition", `${label}.source must be an object`);
54
+ for (const key of Object.keys(source)) {
55
+ if (!["kind", "ref", "path"].includes(key)) {
56
+ fail("invalid-definition", `${label}.source contains unsupported field ${key}`);
57
+ }
58
+ }
59
+ if (source.kind !== "git") fail("invalid-definition", `${label}.source.kind must be git`);
60
+ if (typeof source.ref !== "string" || !COMMIT_REF.test(source.ref)) {
61
+ fail(
62
+ "invalid-definition",
63
+ `${label}.source.ref must be a commit SHA (7 to 64 hex characters), not a branch or tag`,
64
+ );
65
+ }
66
+ if (typeof source.path !== "string" || (source.path !== "." && !isSafeRelativePath(source.path))) {
67
+ fail("unsafe-path", `${label}.source.path must be a directory path relative to the definition root, or "."`);
68
+ }
69
+ } else if (typeof entry.path !== "string" || (entry.path !== "." && !isSafeRelativePath(entry.path))) {
70
+ fail("unsafe-path", `${label} needs package and version, source, or a path inside the definition root`);
71
+ }
72
+ if (typeof entry.entry !== "string" || !isSafeRelativePath(entry.entry)) {
73
+ fail("invalid-definition", `${label}.entry must be a file path relative to the built directory`);
74
+ }
75
+ if (
76
+ Object.hasOwn(entry, "build") &&
77
+ (!Array.isArray(entry.build) ||
78
+ entry.build.length === 0 ||
79
+ !entry.build.every((part) => typeof part === "string" && part !== ""))
80
+ ) {
81
+ fail("invalid-definition", `${label}.build must be a non-empty array of non-empty strings`);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * The working-tree directories that path-sourced host extensions read, across every arm, so the committed-input
87
+ * check covers them.
88
+ *
89
+ * @param {Array<Array<any>>} extensionLists each arm's declared host extensions
90
+ * @param {string} root the definition root
91
+ */
92
+ export function sourceExtensionInputs(extensionLists, root) {
93
+ const inputs = new Set();
94
+ for (const extensions of extensionLists) {
95
+ for (const entry of extensions ?? []) {
96
+ if (isSourceExtension(entry) && typeof entry.path === "string") inputs.add(path.resolve(root, entry.path));
97
+ }
98
+ }
99
+ return [...inputs];
100
+ }
101
+
102
+ /**
103
+ * Replaces each arm's deferred source extensions with built ones, building each distinct source and build once.
104
+ * Package extensions pass through unchanged, in their declared order.
105
+ *
106
+ * @param {{ extensions: Record<string, any[]>, root: string, groupDirectory: string, signal?: AbortSignal }} options
107
+ * @returns {Promise<Record<string, any[]>>}
108
+ */
109
+ export async function prepareSourceExtensions({ extensions, root, groupDirectory, signal }) {
110
+ const builds = new Map();
111
+ const prepared = {};
112
+ for (const [arm, list] of Object.entries(extensions ?? {})) {
113
+ prepared[arm] = [];
114
+ for (const extension of list ?? []) {
115
+ if (extension?.pending === undefined) {
116
+ prepared[arm].push(extension);
117
+ continue;
118
+ }
119
+ const declared = extension.pending;
120
+ const key = JSON.stringify([declared.source ?? { path: declared.path }, declared.build ?? null]);
121
+ if (!builds.has(key)) {
122
+ const directory = path.join(groupDirectory, "resources", "host-extensions", String(builds.size));
123
+ builds.set(key, await buildSource(declared, { root, directory, signal }));
124
+ }
125
+ const built = builds.get(key);
126
+ const entry = await realpath(path.join(built.root, declared.entry)).catch(() => null);
127
+ if (entry === null || !isContained(built.root, entry) || !(await stat(entry)).isFile()) {
128
+ throw new Error(`host extension entry is missing from its build or escapes it: ${declared.entry}`);
129
+ }
130
+ prepared[arm].push({
131
+ path: entry,
132
+ source: built.source,
133
+ entry: declared.entry,
134
+ directory: path.relative(groupDirectory, built.root).split(path.sep).join("/"),
135
+ sha256: built.sha256,
136
+ build: built.build,
137
+ });
138
+ }
139
+ }
140
+ return prepared;
141
+ }
142
+
143
+ // definitions.mjs imports this module for validation, so the host and sandbox modules load only when building.
144
+ async function fingerprintDirectory(directory) {
145
+ const sandbox = await import("./sandbox.mjs");
146
+ return sandbox.fingerprintDirectory(directory);
147
+ }
148
+
149
+ async function buildSource(declared, { root, directory, signal }) {
150
+ const canonicalRoot = await realpath(root);
151
+ const tree = path.join(directory, "source");
152
+ await mkdir(directory, { recursive: true });
153
+ let source;
154
+ if (declared.source !== undefined) {
155
+ const { commit } = await archiveCommit(canonicalRoot, declared.source, tree);
156
+ source = { kind: "git", ref: declared.source.ref, path: declared.source.path, commit };
157
+ } else {
158
+ await snapshotPath(canonicalRoot, declared.path, tree);
159
+ source = { kind: "path", path: declared.path };
160
+ }
161
+
162
+ let build = null;
163
+ if (declared.build !== undefined) {
164
+ // The build gets the subject-style isolation: its own home, temp and Pi directories, no copied login.
165
+ const { prepareIsolation } = await import("./hosts/pi/isolation.mjs");
166
+ const isolation = await prepareIsolation(directory, null);
167
+ const { baseProcessEnvironment } = await import("./process.mjs");
168
+ const environment = { ...baseProcessEnvironment(), PWD: tree, ...isolation.environment };
169
+ let result;
170
+ try {
171
+ result = await runProcess(declared.build, { cwd: tree, environment, timeoutMs: BUILD_TIMEOUT_MS, signal });
172
+ } catch (error) {
173
+ throw new Error(`building host extension ${describe(source)} could not start: ${messageOf(error)}`);
174
+ }
175
+ build = {
176
+ argv: declared.build,
177
+ exitCode: result.exitCode,
178
+ signal: result.signal,
179
+ timedOut: result.timedOut,
180
+ durationMs: result.durationMs,
181
+ stdout: result.stdout.summary().tail.slice(-OUTPUT_TAIL_CHARACTERS),
182
+ stderr: result.stderr.summary().tail.slice(-OUTPUT_TAIL_CHARACTERS),
183
+ };
184
+ if (result.exitCode !== 0 || result.timedOut) {
185
+ const outcome = result.timedOut ? "timed out" : `exit ${result.exitCode ?? result.signal}`;
186
+ const detail = (build.stderr.trim() || build.stdout.trim()).slice(-1000);
187
+ throw new Error(`building host extension ${describe(source)} failed (${outcome})${detail ? `: ${detail}` : ""}`);
188
+ }
189
+ }
190
+ const builtRoot = await realpath(tree);
191
+ const files = await fingerprintDirectory(builtRoot);
192
+ return { root: builtRoot, source, build, sha256: sha256(JSON.stringify(files)) };
193
+ }
194
+
195
+ /** Extracts one directory of an exact commit with `git archive`, from the repository holding the definition root. */
196
+ async function archiveCommit(canonicalRoot, source, destination) {
197
+ const repositoryRoot = await realpath((await git(canonicalRoot, ["rev-parse", "--show-toplevel"])).trim());
198
+ if (!isContained(repositoryRoot, canonicalRoot)) throw new Error("definition root is not inside its Git repository");
199
+ const commit = (
200
+ await git(canonicalRoot, ["rev-parse", "--verify", "--end-of-options", `${source.ref}^{commit}`])
201
+ ).trim();
202
+ if (!/^[0-9a-f]{40,64}$/.test(commit)) throw new Error(`Git ref did not resolve to one commit: ${source.ref}`);
203
+ const prefix = path.relative(repositoryRoot, canonicalRoot).split(path.sep).join("/");
204
+ const gitPath = [prefix, source.path === "." ? "" : source.path].filter(Boolean).join("/");
205
+ const type = (
206
+ await git(repositoryRoot, ["cat-file", "-t", gitPath === "" ? commit : `${commit}:${gitPath}`]).catch(() => "")
207
+ ).trim();
208
+ if (type !== "tree") throw new Error(`host extension source ${source.path} is not a directory at ${commit}`);
209
+ await mkdir(destination, { recursive: true });
210
+ await pipeArchive(repositoryRoot, gitPath === "" ? commit : `${commit}:${gitPath}`, destination);
211
+ return { commit };
212
+ }
213
+
214
+ /** `git archive --format=tar <tree> | tar -x -C <destination>`. */
215
+ function pipeArchive(repositoryRoot, tree, destination) {
216
+ return new Promise((resolve, reject) => {
217
+ const archive = spawn("git", ["-C", repositoryRoot, "archive", "--format=tar", tree], {
218
+ shell: false,
219
+ stdio: ["ignore", "pipe", "pipe"],
220
+ });
221
+ const extract = spawn("tar", ["-x", "-f", "-", "-C", destination], {
222
+ shell: false,
223
+ stdio: ["pipe", "ignore", "pipe"],
224
+ });
225
+ archive.stdout.pipe(extract.stdin);
226
+ const errors = [];
227
+ archive.stderr.on("data", (chunk) => errors.push(chunk));
228
+ extract.stderr.on("data", (chunk) => errors.push(chunk));
229
+ let pending = 2;
230
+ let failed = null;
231
+ const done = (label) => (code) => {
232
+ if (code !== 0) failed ??= `${label} exited ${code}`;
233
+ pending -= 1;
234
+ if (pending > 0) return;
235
+ if (failed === null) resolve();
236
+ else reject(new Error(`extracting ${tree} failed (${failed}): ${Buffer.concat(errors).toString("utf8").trim()}`));
237
+ };
238
+ archive.once("error", reject);
239
+ extract.once("error", reject);
240
+ archive.once("close", done("git archive"));
241
+ extract.once("close", done("tar"));
242
+ });
243
+ }
244
+
245
+ /** Copies a committed working-tree directory, refusing one that changes while it is copied. */
246
+ async function snapshotPath(canonicalRoot, declaredPath, destination) {
247
+ const sourcePath = await realpath(path.resolve(canonicalRoot, declaredPath)).catch(() => null);
248
+ if (sourcePath === null || !isContained(canonicalRoot, sourcePath)) {
249
+ throw new Error(`host extension path is missing or escapes the definition root: ${declaredPath}`);
250
+ }
251
+ if (!(await lstat(sourcePath)).isDirectory())
252
+ throw new Error(`host extension path is not a directory: ${declaredPath}`);
253
+ const before = JSON.stringify(await fingerprintDirectory(sourcePath));
254
+ await cp(sourcePath, destination, { recursive: true, dereference: false, errorOnExist: true, force: false });
255
+ const copied = JSON.stringify(await fingerprintDirectory(destination));
256
+ const after = JSON.stringify(await fingerprintDirectory(sourcePath));
257
+ if (copied !== before || after !== before) {
258
+ throw new Error(`host extension path changed while it was being snapshotted: ${declaredPath}`);
259
+ }
260
+ }
261
+
262
+ function git(cwd, args) {
263
+ return new Promise((resolve, reject) => {
264
+ const child = spawn("git", ["-C", cwd, ...args], { shell: false, stdio: ["ignore", "pipe", "pipe"] });
265
+ const stdout = [];
266
+ const stderr = [];
267
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
268
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
269
+ child.once("error", reject);
270
+ child.once("close", (code) => {
271
+ if (code === 0) resolve(Buffer.concat(stdout).toString("utf8"));
272
+ else reject(new Error(`git ${args[0]} failed: ${Buffer.concat(stderr).toString("utf8").trim()}`));
273
+ });
274
+ });
275
+ }
276
+
277
+ function describe(source) {
278
+ return source.kind === "git" ? `${source.path} at ${source.commit.slice(0, 12)}` : source.path;
279
+ }
280
+
281
+ function sha256(value) {
282
+ return createHash("sha256").update(value).digest("hex");
283
+ }
284
+
285
+ function messageOf(error) {
286
+ return error instanceof Error ? error.message : String(error);
287
+ }
@@ -0,0 +1,74 @@
1
+ // `skill-eval clear`: a person's decision that a transcript-scan flag does not taint a run. The clearance is
2
+ // recorded beside the run in `clearances.json`, with the reason; the run's own files never change. Every aggregate
3
+ // that lists the run's result is then rebuilt, so the run counts again once none of its flags is left uncleared.
4
+
5
+ import { readdir, readFile, stat } from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ import { clearFlags, unclearedFlags } from "./scan.mjs";
9
+ import { rebuildAggregate } from "./trials.mjs";
10
+ import { resolveResult } from "./view.mjs";
11
+
12
+ async function readJson(file) {
13
+ try {
14
+ return JSON.parse(await readFile(file, "utf8"));
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Clears flags of one run and rebuilds the aggregates that list its result.
22
+ *
23
+ * @param {string} target a result ID or directory (a trial, never an aggregate)
24
+ * @param {{ group?: string | null, variant?: string | null, flags: string[] | "all", reason: string, root?: string, results?: string | null }} options
25
+ */
26
+ export async function clearRun(
27
+ target,
28
+ { group = null, variant = null, flags, reason, root = process.cwd(), results = null },
29
+ ) {
30
+ const directory = await resolveResult(target, root, results);
31
+ if (await stat(path.join(directory, "aggregate.json")).catch(() => null)) {
32
+ throw new Error(
33
+ "clear names one trial's result, not an aggregate; view the aggregate to see which trial holds the run",
34
+ );
35
+ }
36
+ const summary = await readJson(path.join(directory, "summary.json"));
37
+ if (summary === null) throw new Error(`not a readable result: ${directory}`);
38
+ const groups = summary.groups ?? [];
39
+ const position = group !== null && /^\d+$/.test(group) ? Number(group) : null;
40
+ const selected =
41
+ group === null
42
+ ? groups
43
+ : groups.filter((entry) => (position === null ? entry.id === group : entry.position === position));
44
+ if (selected.length !== 1) {
45
+ throw new Error(
46
+ group === null ? "the result has several groups; name one with --group" : `no group ${group} in the result`,
47
+ );
48
+ }
49
+ const [chosen] = selected;
50
+ const arms = Object.keys(chosen.variants ?? {});
51
+ const arm = variant ?? (arms.length === 1 ? arms[0] : null);
52
+ if (arm === null || !arms.includes(arm)) {
53
+ throw new Error(
54
+ variant === null ? `name the arm with --variant: ${arms.join(", ")}` : `no arm ${arm} in group ${chosen.id}`,
55
+ );
56
+ }
57
+ const armDirectory = path.join(directory, "groups", chosen.id, arm);
58
+ const run = await readJson(path.join(armDirectory, "run.json"));
59
+ if (run === null) throw new Error(`the run of ${chosen.id} ${arm} is unreadable`);
60
+ const cleared = await clearFlags(armDirectory, run, flags, reason);
61
+ const remaining = await unclearedFlags(run, armDirectory);
62
+
63
+ // Every aggregate stored beside the result that lists it as a trial counts the run anew.
64
+ const runsDirectory = path.dirname(directory);
65
+ const rebuilt = [];
66
+ for (const entry of await readdir(runsDirectory, { withFileTypes: true })) {
67
+ if (!entry.isDirectory()) continue;
68
+ const aggregate = await readJson(path.join(runsDirectory, entry.name, "aggregate.json"));
69
+ if (aggregate?.kind !== "aggregate" || !(aggregate.trials ?? []).some((trial) => trial.id === summary.id)) continue;
70
+ await rebuildAggregate(path.join(runsDirectory, entry.name), { root, results: runsDirectory });
71
+ rebuilt.push(entry.name);
72
+ }
73
+ return { result: summary.id, group: chosen.id, arm, cleared, remaining, rebuilt };
74
+ }