@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,196 @@
1
+ // Claude Code's part of the shell tier's confinement probes (shell.mjs): a headless Claude Code with the evaluator's
2
+ // guard hook runs every probe as a Bash call from a local scripted Anthropic endpoint, and reports what each returned.
3
+
4
+ import { readFile, writeFile } from "node:fs/promises";
5
+ import http from "node:http";
6
+ import path from "node:path";
7
+
8
+ import { baseProcessEnvironment } from "../../process.mjs";
9
+ import { baseArguments, CLAUDE_CODE_ENVIRONMENT, settingsWithGuard } from "./launch.mjs";
10
+ import { runClaudeOneShot } from "./session.mjs";
11
+
12
+ // The stub answers every request, so no model is called; the name only keeps Claude Code on a model it knows.
13
+ const PROBE_MODEL = "claude-haiku-5-5";
14
+
15
+ function toolUseId(probe) {
16
+ return `toolu_probe_${probe.id.replaceAll("-", "_")}`;
17
+ }
18
+
19
+ /**
20
+ * Runs the probes through Claude Code. `guard` is the guard's environment for the probe workspace; `isolation` is
21
+ * the probe's prepared isolation, which carries no login.
22
+ *
23
+ * @returns {Promise<{observation: any, launchError: string | null, results: Map<string, {ran: boolean, isError: boolean, output: string}>}>}
24
+ */
25
+ export async function probeClaudeShell({ probes, workspace, isolation, guard, eventsFile, stderrFile, signal }) {
26
+ const model = await startMessagesStub(probes);
27
+ try {
28
+ const settingsFile = path.join(isolation.evidence.root, "settings.json");
29
+ await writeFile(settingsFile, `${JSON.stringify(settingsWithGuard({}), null, 2)}\n`);
30
+ const args = [
31
+ ...baseArguments({ model: PROBE_MODEL, settingsFile }),
32
+ "--tools",
33
+ "Bash",
34
+ "--system-prompt",
35
+ "Run each confinement probe with Bash, exactly as given.",
36
+ ];
37
+ const noProxy = [process.env.NO_PROXY, "127.0.0.1", "localhost"].filter(Boolean).join(",");
38
+ const environment = {
39
+ ...baseProcessEnvironment(),
40
+ ...CLAUDE_CODE_ENVIRONMENT,
41
+ ...guard,
42
+ ...isolation.environment,
43
+ ANTHROPIC_API_KEY: "probe",
44
+ ANTHROPIC_BASE_URL: model.baseUrl,
45
+ NO_PROXY: noProxy,
46
+ no_proxy: noProxy,
47
+ };
48
+ let observation = null;
49
+ let launchError = null;
50
+ try {
51
+ observation = await runClaudeOneShot({
52
+ command: "claude",
53
+ args,
54
+ cwd: workspace,
55
+ eventsFile,
56
+ stderrFile,
57
+ signal,
58
+ prompts: ["Run the confinement probes."],
59
+ environment,
60
+ });
61
+ } catch (error) {
62
+ launchError = error instanceof Error ? error.message : String(error);
63
+ }
64
+ // Whatever Claude Code reported before a launch error still counts.
65
+ return { observation, launchError, results: await toolResults(eventsFile, probes) };
66
+ } finally {
67
+ await model.close();
68
+ }
69
+ }
70
+
71
+ function resultText(content) {
72
+ if (typeof content === "string") return content;
73
+ if (!Array.isArray(content)) return "";
74
+ return content
75
+ .filter((part) => part?.type === "text")
76
+ .map((part) => part.text)
77
+ .join("");
78
+ }
79
+
80
+ /** Each probe's tool result from Claude Code's stream-json events, by probe ID. */
81
+ async function toolResults(eventsFile, probes) {
82
+ const byToolUse = new Map(probes.map((probe) => [toolUseId(probe), probe.id]));
83
+ const results = new Map();
84
+ const contents = await readFile(eventsFile, "utf8").catch(() => "");
85
+ for (const line of contents.split("\n")) {
86
+ if (line.trim() === "") continue;
87
+ let event;
88
+ try {
89
+ event = JSON.parse(line);
90
+ } catch {
91
+ continue;
92
+ }
93
+ if (event.type !== "user" || !Array.isArray(event.message?.content)) continue;
94
+ for (const block of event.message.content) {
95
+ if (block?.type !== "tool_result" || !byToolUse.has(block.tool_use_id)) continue;
96
+ results.set(byToolUse.get(block.tool_use_id), {
97
+ ran: true,
98
+ isError: block.is_error === true,
99
+ output: resultText(block.content),
100
+ });
101
+ }
102
+ }
103
+ return results;
104
+ }
105
+
106
+ /**
107
+ * A local Anthropic Messages endpoint that answers a request without tool results with every probe as a Bash
108
+ * tool call, and any request carrying tool results with a short final answer. No model is called.
109
+ */
110
+ function startMessagesStub(probes) {
111
+ return new Promise((resolve, reject) => {
112
+ const server = http.createServer((request, response) => {
113
+ let body = "";
114
+ request.setEncoding("utf8");
115
+ request.on("data", (chunk) => {
116
+ body += chunk;
117
+ });
118
+ request.on("end", () => {
119
+ if (request.method !== "POST" || !request.url.startsWith("/v1/messages")) {
120
+ response.writeHead(200, { "content-type": "application/json" });
121
+ response.end("{}");
122
+ return;
123
+ }
124
+ let parsed = {};
125
+ try {
126
+ parsed = JSON.parse(body || "{}");
127
+ } catch {
128
+ // Answered as a first request.
129
+ }
130
+ if (request.url.includes("count_tokens")) {
131
+ response.writeHead(200, { "content-type": "application/json" });
132
+ response.end(JSON.stringify({ input_tokens: 0 }));
133
+ return;
134
+ }
135
+ const followUp =
136
+ Array.isArray(parsed.messages) &&
137
+ parsed.messages.some(
138
+ (message) =>
139
+ Array.isArray(message?.content) && message.content.some((block) => block?.type === "tool_result"),
140
+ );
141
+ const content = followUp
142
+ ? [{ type: "text", text: "Probes finished." }]
143
+ : probes.map((probe) => ({
144
+ type: "tool_use",
145
+ id: toolUseId(probe),
146
+ name: "Bash",
147
+ input: { command: probe.command, description: probe.id },
148
+ }));
149
+ const stopReason = followUp ? "end_turn" : "tool_use";
150
+ const usage = { input_tokens: 0, output_tokens: 0 };
151
+ const message = {
152
+ id: "msg_probe",
153
+ type: "message",
154
+ role: "assistant",
155
+ model: parsed.model ?? PROBE_MODEL,
156
+ stop_sequence: null,
157
+ };
158
+ if (parsed.stream !== true) {
159
+ response.writeHead(200, { "content-type": "application/json" });
160
+ response.end(JSON.stringify({ ...message, content, stop_reason: stopReason, usage }));
161
+ return;
162
+ }
163
+ response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache" });
164
+ const send = (type, data) => response.write(`event: ${type}\ndata: ${JSON.stringify({ type, ...data })}\n\n`);
165
+ send("message_start", { message: { ...message, content: [], stop_reason: null, usage } });
166
+ for (const [index, block] of content.entries()) {
167
+ if (block.type === "text") {
168
+ send("content_block_start", { index, content_block: { type: "text", text: "" } });
169
+ send("content_block_delta", { index, delta: { type: "text_delta", text: block.text } });
170
+ } else {
171
+ send("content_block_start", { index, content_block: { ...block, input: {} } });
172
+ send("content_block_delta", {
173
+ index,
174
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(block.input) },
175
+ });
176
+ }
177
+ send("content_block_stop", { index });
178
+ }
179
+ send("message_delta", { delta: { stop_reason: stopReason, stop_sequence: null }, usage: { output_tokens: 0 } });
180
+ send("message_stop", {});
181
+ response.end();
182
+ });
183
+ });
184
+ server.once("error", reject);
185
+ server.listen(0, "127.0.0.1", () => {
186
+ resolve({
187
+ baseUrl: `http://127.0.0.1:${server.address().port}`,
188
+ close: () =>
189
+ new Promise((done) => {
190
+ server.closeAllConnections?.();
191
+ server.close(() => done());
192
+ }),
193
+ });
194
+ });
195
+ });
196
+ }
@@ -0,0 +1,111 @@
1
+ // What a definition may ask of the Claude Code host, checked when it loads (definitions.mjs reads this file without
2
+ // loading the adapter). Anything Claude Code cannot run the way the evaluator records it is refused here, with the
3
+ // reason, rather than approximated at run time.
4
+
5
+ /** The evaluator's tool names and the Claude Code built-in each one runs as. Claude Code has no separate ls. */
6
+ export const CLAUDE_CODE_TOOLS = Object.freeze({
7
+ read: "Read",
8
+ write: "Write",
9
+ edit: "Edit",
10
+ ls: "Glob",
11
+ find: "Glob",
12
+ grep: "Grep",
13
+ bash: "Bash",
14
+ });
15
+
16
+ /** `model.thinking` values Claude Code accepts as `--effort`. */
17
+ export const CLAUDE_CODE_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
18
+
19
+ // Settings that would replace the evaluator's guard, login, permissions or environment, or load something the
20
+ // definition did not declare. A comparison changes them through the evaluator's own fields, or not at all.
21
+ const REFUSED_SETTINGS = [
22
+ "hooks",
23
+ "disableAllHooks",
24
+ "permissions",
25
+ "env",
26
+ "apiKeyHelper",
27
+ "awsAuthRefresh",
28
+ "awsCredentialExport",
29
+ "otelHeadersHelper",
30
+ "forceLoginMethod",
31
+ "enabledPlugins",
32
+ "extraKnownMarketplaces",
33
+ "enableAllProjectMcpServers",
34
+ "enabledMcpjsonServers",
35
+ "statusLine",
36
+ "sandbox",
37
+ ];
38
+
39
+ /**
40
+ * The Claude Code model name for a declared model: `anthropic/<name>` or a bare name. Throws for another provider,
41
+ * since Claude Code calls only Anthropic's API.
42
+ */
43
+ export function claudeCodeModel(model) {
44
+ if (!model.includes("/")) return model;
45
+ const [provider, ...rest] = model.split("/");
46
+ if (provider !== "anthropic" || rest.length !== 1 || rest[0] === "") {
47
+ throw new Error(`Claude Code runs only Anthropic models (anthropic/<model>), not ${model}`);
48
+ }
49
+ return rest[0];
50
+ }
51
+
52
+ /** Problems with a host setup (model and settings) that is to run on Claude Code, or [] when there are none. */
53
+ export function claudeCodeSetupProblems({ label, model, extensions = [], settings = {} }) {
54
+ const problems = [];
55
+ if (extensions.length > 0) problems.push(`${label}.host.extensions are Pi packages; Claude Code loads none`);
56
+ for (const key of REFUSED_SETTINGS) {
57
+ if (Object.hasOwn(settings, key)) problems.push(`${label}.host.settings cannot set ${key} on Claude Code`);
58
+ }
59
+ if (model !== undefined && model !== null) {
60
+ try {
61
+ claudeCodeModel(model.model);
62
+ } catch (error) {
63
+ problems.push(`${label}.model: ${error.message}`);
64
+ }
65
+ if (model.thinking !== undefined && !CLAUDE_CODE_EFFORTS.has(model.thinking)) {
66
+ problems.push(`${label}.model.thinking must be one of ${[...CLAUDE_CODE_EFFORTS].join(", ")} on Claude Code`);
67
+ }
68
+ }
69
+ return problems;
70
+ }
71
+
72
+ /**
73
+ * Problems with a group that runs its subjects on Claude Code, or [] when there are none: tools Claude Code has,
74
+ * a model for every arm, no Pi extensions, bash only in a container environment, and none of the features the
75
+ * adapter does not support yet (declared commands, runtime extension bundles, Freeflow).
76
+ */
77
+ export function claudeCodeGroupProblems(group, configurations) {
78
+ const problems = [];
79
+ for (const tool of group.tools) {
80
+ if (!Object.hasOwn(CLAUDE_CODE_TOOLS, tool)) {
81
+ problems.push(
82
+ `group.tools ${tool} is not available on Claude Code (it has ${Object.keys(CLAUDE_CODE_TOOLS).join(", ")})`,
83
+ );
84
+ }
85
+ }
86
+ if (group.commands !== undefined) problems.push("group.commands are not supported on Claude Code yet");
87
+ for (const [arm, configuration] of Object.entries(configurations)) {
88
+ const label = `group.arms.${arm}`;
89
+ if (configuration.model === undefined) problems.push(`${label} needs a model on Claude Code`);
90
+ problems.push(
91
+ ...claudeCodeSetupProblems({
92
+ label,
93
+ model: configuration.model,
94
+ extensions: configuration.host.extensions,
95
+ settings: configuration.host.settings,
96
+ }),
97
+ );
98
+ if (configuration.shell !== undefined) {
99
+ problems.push(
100
+ `${label}.shell needs a Pi isolation extension; on Claude Code, bash runs in a container environment`,
101
+ );
102
+ }
103
+ if ((configuration.runtime?.extensions?.length ?? 0) > 0) {
104
+ problems.push(`${label}.runtime.extensions are Pi extensions; Claude Code loads none`);
105
+ }
106
+ if (configuration.freeflow !== undefined && configuration.freeflow !== null) {
107
+ problems.push(`${label}.freeflow is not supported on Claude Code yet`);
108
+ }
109
+ }
110
+ return problems;
111
+ }
@@ -0,0 +1,48 @@
1
+ // The Claude Code host adapter: subjects, judges and simulated users run in headless Claude Code (`claude -p`),
2
+ // behind the interface in ../index.mjs. Logins come only from the evaluator's environment (isolation.mjs); what a
3
+ // definition may ask of this host is in constraints.mjs.
4
+
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+
8
+ import { baseProcessEnvironment } from "../../process.mjs";
9
+ import { probeClaudeShell } from "./confinement.mjs";
10
+ import { prepareClaudeIsolation } from "./isolation.mjs";
11
+ import { prepareClaudeLaunch } from "./launch.mjs";
12
+ import { launchClaudeModel } from "./model-call.mjs";
13
+ import { runClaudeOneShot, runClaudeSession } from "./session.mjs";
14
+
15
+ const run = promisify(execFile);
16
+
17
+ export const claudeCodeHost = Object.freeze({
18
+ name: "claude-code",
19
+ label: "Claude Code",
20
+ command: "claude",
21
+
22
+ /** The installed Claude Code's version: the first word `claude --version` prints, such as 2.1.296. */
23
+ async version(environment = baseProcessEnvironment()) {
24
+ const { stdout } = await run("claude", ["--version"], { env: environment, timeout: 30000 });
25
+ return stdout.trim().split(/\s+/)[0] ?? "";
26
+ },
27
+
28
+ /** Claude Code loads no Pi packages; definitions refuse them, so any reaching here is an error. */
29
+ async resolveExtensions(declared) {
30
+ if (declared.length > 0) throw new Error("Claude Code loads no host extensions");
31
+ return [];
32
+ },
33
+
34
+ /** Logins come from the environment and are never copied, so there is nothing to refresh. */
35
+ async refreshLogins() {},
36
+
37
+ /** No evaluator defaults are settings; Claude Code's switches are environment variables (launch.mjs). */
38
+ effectiveSettings: (declared = {}) => ({ ...declared }),
39
+ prepareIsolation: prepareClaudeIsolation,
40
+ /** No credential is ever written into the isolation, so nothing is removed. */
41
+ async removeCredentials() {},
42
+
43
+ prepareLaunch: prepareClaudeLaunch,
44
+ runOneShot: runClaudeOneShot,
45
+ runSession: runClaudeSession,
46
+ modelCall: launchClaudeModel,
47
+ probeShell: probeClaudeShell,
48
+ });
@@ -0,0 +1,74 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ // Claude Code's login reaches a subject only through these variables, passed from the evaluator's own environment.
5
+ // No credential file is copied: a refresh inside a copy would rotate the token there and revoke the user's own.
6
+ // On a machine whose Claude Code login lives in the keychain, `claude setup-token` prints a token for
7
+ // CLAUDE_CODE_OAUTH_TOKEN.
8
+ export const LOGIN_VARIABLES = [
9
+ "CLAUDE_CODE_OAUTH_TOKEN",
10
+ "ANTHROPIC_API_KEY",
11
+ "ANTHROPIC_AUTH_TOKEN",
12
+ "CLAUDE_SESSION_INGRESS_TOKEN_FILE",
13
+ ];
14
+ // Where the API is and how the machine reaches the network; passed when set, so a subject connects as the user does.
15
+ const CONNECTION_VARIABLES = [
16
+ "ANTHROPIC_BASE_URL",
17
+ "HTTPS_PROXY",
18
+ "https_proxy",
19
+ "HTTP_PROXY",
20
+ "http_proxy",
21
+ "NO_PROXY",
22
+ "no_proxy",
23
+ "NODE_EXTRA_CA_CERTS",
24
+ "SSL_CERT_FILE",
25
+ ];
26
+
27
+ /**
28
+ * Creates a private home, temp and Claude Code configuration directory for one process, and the environment that
29
+ * points the process at them, with the login and connection variables of the evaluator's environment. A process
30
+ * with a model needs a login; the confinement probe, which answers from a local stub, runs with none.
31
+ *
32
+ * @param {string} directory where the isolation root is created
33
+ * @param {string | null} model the declared model, or null for a probe
34
+ * @param {{settings?: Record<string, unknown>}} [options]
35
+ * @returns {Promise<{environment: Record<string, string>, evidence: any, loginDirectory: string}>}
36
+ */
37
+ export async function prepareClaudeIsolation(directory, model, { settings = {} } = {}) {
38
+ const root = path.join(directory, "isolation");
39
+ const home = path.join(root, "home");
40
+ const config = path.join(root, "config");
41
+ const tmp = path.join(root, "tmp");
42
+ for (const entry of [home, config, tmp]) await mkdir(entry, { recursive: true });
43
+
44
+ const login = model === null ? [] : LOGIN_VARIABLES.filter((name) => process.env[name]);
45
+ if (model !== null && login.length === 0) {
46
+ throw new Error(
47
+ `Claude Code needs a login in the evaluator's environment: set one of ${LOGIN_VARIABLES.join(", ")} (on a Mac, \`claude setup-token\` prints a token for CLAUDE_CODE_OAUTH_TOKEN)`,
48
+ );
49
+ }
50
+ const passed = [...login, ...CONNECTION_VARIABLES.filter((name) => process.env[name] !== undefined)];
51
+ return {
52
+ environment: {
53
+ HOME: home,
54
+ TMPDIR: tmp,
55
+ TMP: tmp,
56
+ TEMP: tmp,
57
+ XDG_CONFIG_HOME: path.join(home, ".config"),
58
+ XDG_DATA_HOME: path.join(home, ".local", "share"),
59
+ XDG_CACHE_HOME: path.join(home, ".cache"),
60
+ CLAUDE_CONFIG_DIR: config,
61
+ ...Object.fromEntries(passed.map((name) => [name, process.env[name]])),
62
+ },
63
+ // The names of the variables passed, never their values.
64
+ evidence: {
65
+ root,
66
+ provider: model === null ? null : "anthropic",
67
+ credentials: login.map((variable) => ({ variable })),
68
+ passed,
69
+ settings,
70
+ },
71
+ // Where the host keeps its configuration and any login file: the confinement probe plants a marker here.
72
+ loginDirectory: config,
73
+ };
74
+ }
@@ -0,0 +1,123 @@
1
+ // How the evaluator launches Claude Code for a subject: headless (`claude -p`) with stream-json in and out, only the
2
+ // declared tools, no settings, instructions, plugins' skills, MCP servers or slash commands from anywhere on the
3
+ // machine, the evaluator's guard as a PreToolUse hook, and the system prompt the runtime's prompt mode asks for.
4
+
5
+ import { mkdir, writeFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ import { loadDeclaredContext, renderDeclaredContext } from "../../../guard-rules.mjs";
10
+ import { CLAUDE_CODE_TOOLS, claudeCodeModel } from "./constraints.mjs";
11
+ import { readSkill, skillListing } from "./skills.mjs";
12
+
13
+ const guardHook = fileURLToPath(new URL("../../../claude-guard.mjs", import.meta.url));
14
+
15
+ // Claude Code switches every subject gets: no automatic compaction, no updater or nonessential traffic, and no
16
+ // CLAUDE.md or memory, which the isolated config directory would not hold anyway. Retries stay at Claude Code's own
17
+ // default, which the run records with the version.
18
+ export const CLAUDE_CODE_ENVIRONMENT = Object.freeze({
19
+ DISABLE_AUTO_COMPACT: "1",
20
+ DISABLE_AUTOUPDATER: "1",
21
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
22
+ CLAUDE_CODE_DISABLE_CLAUDE_MDS: "1",
23
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
24
+ });
25
+
26
+ const TOOL_LINES = {
27
+ Read: "Read a file",
28
+ Write: "Create or overwrite a file",
29
+ Edit: "Replace exact text in a file",
30
+ Glob: "Find files by name pattern",
31
+ Grep: "Search file contents",
32
+ Bash: "Run a shell command",
33
+ };
34
+
35
+ /** The Claude Code built-ins a group's evaluator tools run as, in a stable order. */
36
+ export function hostTools(tools) {
37
+ return [...new Set(tools.map((tool) => CLAUDE_CODE_TOOLS[tool]))];
38
+ }
39
+
40
+ function shellQuote(value) {
41
+ return `'${String(value).replaceAll("'", `'\\''`)}'`;
42
+ }
43
+
44
+ /** The settings file every Claude Code process the evaluator starts reads: declared settings and the guard hook. */
45
+ export function settingsWithGuard(settings) {
46
+ return {
47
+ ...settings,
48
+ hooks: {
49
+ PreToolUse: [
50
+ {
51
+ matcher: "*",
52
+ hooks: [{ type: "command", command: `${shellQuote(process.execPath)} ${shellQuote(guardHook)}` }],
53
+ },
54
+ ],
55
+ },
56
+ };
57
+ }
58
+
59
+ /** The headless arguments every Claude Code process shares; `--tools` and the prompt flags are the caller's. */
60
+ export function baseArguments({ model, thinking = null, settingsFile, session = false }) {
61
+ const args = ["-p", "--verbose", "--output-format", "stream-json", "--input-format", "stream-json"];
62
+ args.push("--model", claudeCodeModel(model));
63
+ if (thinking !== null && thinking !== undefined) args.push("--effort", thinking);
64
+ args.push("--setting-sources", "", "--settings", settingsFile);
65
+ args.push("--strict-mcp-config", "--disable-slash-commands", "--permission-mode", "dontAsk");
66
+ if (!session) args.push("--no-session-persistence");
67
+ return args;
68
+ }
69
+
70
+ function isolatedPrompt(tools, listing, declaredContext, cwd) {
71
+ const lines = [
72
+ "You are an expert coding assistant operating inside Claude Code, a coding agent harness.",
73
+ "Help the user with the task using only the tools and resources listed below.",
74
+ "",
75
+ "Available tools:",
76
+ ...(tools.length > 0 ? tools.map((tool) => `- ${tool}: ${TOOL_LINES[tool]}`) : ["(none)"]),
77
+ "",
78
+ "Guidelines:",
79
+ "- Use Read to load a skill's SKILL.md when the task matches its description.",
80
+ "- Resolve relative skill resources from the skill directory.",
81
+ "- Be concise in your response.",
82
+ ];
83
+ if (listing !== "") lines.push("", listing);
84
+ if (declaredContext.length > 0) lines.push("", renderDeclaredContext(declaredContext));
85
+ lines.push(`Current working directory: ${cwd}`);
86
+ return lines.join("\n");
87
+ }
88
+
89
+ /**
90
+ * One subject's arguments and Claude Code's own variables. "isolated" replaces Claude Code's system prompt with the
91
+ * evaluator's; "host" keeps Claude Code's and appends the declared skills and context. Claude Code still adds its
92
+ * own identity line and an environment note to every request; the run's host-context record shows them.
93
+ */
94
+ export async function prepareClaudeLaunch({ group, environment, runtime, model, workspace, isolation }) {
95
+ const directory = path.join(isolation.root, "launch");
96
+ await mkdir(directory, { recursive: true });
97
+ const settingsFile = path.join(directory, "settings.json");
98
+ await writeFile(settingsFile, `${JSON.stringify(settingsWithGuard(isolation.settings ?? {}), null, 2)}\n`);
99
+
100
+ const skills = [];
101
+ for (const skill of environment.skills) skills.push(await readSkill(skill.path));
102
+ const declaredContext = loadDeclaredContext(environment.contextDelivery.manifestPath);
103
+ const tools = hostTools(group.tools);
104
+ const listing = skillListing(skills);
105
+ const promptFile = path.join(directory, "system-prompt.md");
106
+ const isolated = runtime.prompt !== "host";
107
+ const appended = [listing, declaredContext.length > 0 ? renderDeclaredContext(declaredContext) : ""]
108
+ .filter(Boolean)
109
+ .join("\n\n");
110
+ if (isolated) await writeFile(promptFile, isolatedPrompt(tools, listing, declaredContext, workspace));
111
+ else if (appended !== "") await writeFile(promptFile, appended);
112
+
113
+ const args = baseArguments({
114
+ model: model.model,
115
+ thinking: model.thinking ?? null,
116
+ settingsFile,
117
+ session: runtime.session,
118
+ });
119
+ args.push("--tools", tools.join(","));
120
+ if (isolated) args.push("--system-prompt-file", promptFile);
121
+ else if (appended !== "") args.push("--append-system-prompt-file", promptFile);
122
+ return { args, environment: { ...CLAUDE_CODE_ENVIRONMENT } };
123
+ }
@@ -0,0 +1,73 @@
1
+ // One model call through Claude Code that is not a subject: a judge, a reviewer, or a simulated user. It runs
2
+ // headless without tools, settings sources, MCP servers or slash commands, in its own isolated home and
3
+ // configuration directory, with Claude Code's own system prompt (as a Pi judge has Pi's).
4
+
5
+ import { mkdir, rm, writeFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ import { writeText } from "../../evidence.mjs";
9
+ import { baseProcessEnvironment } from "../../process.mjs";
10
+ import { prepareClaudeIsolation } from "./isolation.mjs";
11
+ import { baseArguments, CLAUDE_CODE_ENVIRONMENT, settingsWithGuard } from "./launch.mjs";
12
+ import { runClaudeOneShot } from "./session.mjs";
13
+
14
+ /**
15
+ * Starts one model call on a prompt in its own directory, which keeps the prompt, events, final response and stderr.
16
+ *
17
+ * @param {string} directory replaced on every call
18
+ * @param {string} prompt
19
+ * @param {{model: string, thinking: string | null, settings: object, signal?: AbortSignal, role?: string}} options
20
+ * @returns {Promise<{response: string | null, errors: string[], usage: any, isolation: any}>}
21
+ */
22
+ export async function launchClaudeModel(directory, prompt, options) {
23
+ const { model, thinking, settings = {}, signal, role = "reviewer" } = options;
24
+ await rm(directory, { recursive: true, force: true });
25
+ await mkdir(directory, { recursive: true });
26
+ await writeText(path.join(directory, "prompt.md"), prompt);
27
+
28
+ let isolation;
29
+ let args;
30
+ try {
31
+ isolation = await prepareClaudeIsolation(directory, model, { settings });
32
+ const settingsFile = path.join(isolation.evidence.root, "settings.json");
33
+ await writeFile(settingsFile, `${JSON.stringify(settingsWithGuard(settings), null, 2)}\n`);
34
+ args = [...baseArguments({ model, thinking, settingsFile }), "--tools", ""];
35
+ } catch (error) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ return { response: null, errors: [message], usage: null, isolation: isolation?.evidence ?? null };
38
+ }
39
+ const observation = await runClaudeOneShot({
40
+ command: "claude",
41
+ args,
42
+ cwd: directory,
43
+ eventsFile: path.join(directory, "events.jsonl"),
44
+ stderrFile: path.join(directory, "stderr.log"),
45
+ signal,
46
+ prompts: [prompt],
47
+ environment: {
48
+ ...baseProcessEnvironment(),
49
+ PWD: directory,
50
+ ...CLAUDE_CODE_ENVIRONMENT,
51
+ // No tools, so the guard has nothing to allow; it still refuses anything Claude Code would try.
52
+ SKILL_EVAL_ALLOWED_ROOTS: JSON.stringify([directory]),
53
+ SKILL_EVAL_WRITABLE_ROOT: directory,
54
+ SKILL_EVAL_ALLOWED_TOOLS: "[]",
55
+ ...isolation.environment,
56
+ },
57
+ });
58
+ await writeText(path.join(directory, "final.md"), `${observation.response}\n`);
59
+
60
+ const errors = [];
61
+ if (
62
+ observation.exitCode !== 0 ||
63
+ !observation.settled ||
64
+ observation.assistantError !== null ||
65
+ observation.protocolErrors.length > 0
66
+ ) {
67
+ const protocol = observation.protocolErrors.map((error) => error.reason).join(",") || "none";
68
+ errors.push(
69
+ `${role} did not complete: exit=${observation.exitCode} settled=${observation.settled} error=${observation.assistantError} protocol=${protocol}`,
70
+ );
71
+ }
72
+ return { response: observation.response, errors, usage: observation.usage, isolation: isolation.evidence };
73
+ }