@hassangameryt/freeflow 0.5.0 → 0.7.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 (301) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +4 -5
  4. package/README.md +141 -163
  5. package/capabilities/cognitive-routing/SKILL.md +387 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +6 -37
  9. package/hooks/freeflow-runtime-context.mjs +50 -541
  10. package/hooks/hooks.json +1 -26
  11. package/hooks/tests/check-runtime-context-hook.sh +114 -308
  12. package/package.json +27 -17
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +232 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +451 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +188 -0
  18. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  19. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  20. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  21. package/pi-extension/dist/cognitive-routing/tool.js +212 -0
  22. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  23. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  24. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  25. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  26. package/pi-extension/dist/context-virtualization/state.js +57 -0
  27. package/pi-extension/dist/context-virtualization/types.js +10 -0
  28. package/pi-extension/dist/conversation-history/passages.js +66 -0
  29. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  30. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  31. package/pi-extension/dist/conversation-history/search.js +56 -0
  32. package/pi-extension/dist/conversation-history/types.js +1 -0
  33. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  34. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  35. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  36. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  37. package/pi-extension/dist/freeflow-context/types.js +12 -0
  38. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  39. package/pi-extension/dist/index.js +564 -129
  40. package/pi-extension/dist/runtime/runtime-context.js +357 -470
  41. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  42. package/pi-extension/dist/settings/settings-tui.js +78 -0
  43. package/pi-extension/dist/settings/settings-ui.js +572 -826
  44. package/pi-extension/dist/ui/text-component.js +57 -0
  45. package/pi-extension/src/cognitive-routing/commands.ts +129 -0
  46. package/pi-extension/src/cognitive-routing/config.ts +303 -0
  47. package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
  48. package/pi-extension/src/cognitive-routing/history.ts +650 -0
  49. package/pi-extension/src/cognitive-routing/host.ts +266 -0
  50. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  51. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  52. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  53. package/pi-extension/src/cognitive-routing/tool.ts +273 -0
  54. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  55. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  56. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  57. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  58. package/pi-extension/src/context-virtualization/state.ts +79 -0
  59. package/pi-extension/src/context-virtualization/types.ts +92 -0
  60. package/pi-extension/src/conversation-history/passages.ts +81 -0
  61. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  62. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  63. package/pi-extension/src/conversation-history/search.ts +79 -0
  64. package/pi-extension/src/conversation-history/types.ts +31 -0
  65. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  66. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  67. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  68. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  69. package/pi-extension/src/freeflow-context/types.ts +70 -0
  70. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  71. package/pi-extension/src/index.ts +597 -135
  72. package/pi-extension/src/runtime/runtime-context.ts +373 -491
  73. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  74. package/pi-extension/src/settings/settings-tui.ts +93 -0
  75. package/pi-extension/src/settings/settings-ui.ts +679 -910
  76. package/pi-extension/src/ui/text-component.ts +66 -0
  77. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  78. package/pi-extension/tests/cognitive-routing/controller.test.js +1128 -0
  79. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -0
  80. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  81. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  82. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  83. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  84. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  85. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  86. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -0
  87. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  88. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  89. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  90. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  91. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  92. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  93. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  94. package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
  95. package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
  96. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  97. package/runtime/prompts/cognitive-routing.md +10 -0
  98. package/runtime/prompts/context-virtualization.md +3 -0
  99. package/runtime/prompts/conversation-history.md +3 -0
  100. package/runtime/prompts/core.md +84 -0
  101. package/runtime/prompts/interaction-contract.md +11 -0
  102. package/skills/action-selection/SKILL.md +140 -0
  103. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  104. package/skills/bypass/SKILL.md +109 -38
  105. package/skills/commit-work/SKILL.md +94 -55
  106. package/skills/commit-work/references/git-state-edges.md +94 -0
  107. package/skills/commit-work/references/simple-push.md +92 -0
  108. package/skills/commit-work/references/staging-decisions.md +79 -46
  109. package/skills/decision-gate/SKILL.md +80 -22
  110. package/skills/design-for-depth/SKILL.md +206 -60
  111. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  112. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  113. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  114. package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
  115. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  116. package/skills/diagnose-failure/SKILL.md +200 -48
  117. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
  118. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
  119. package/skills/discuss/SKILL.md +119 -65
  120. package/skills/discuss/references/discussion-continuity.md +123 -0
  121. package/skills/evaluate-skill/SKILL.md +166 -52
  122. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
  123. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  124. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  125. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  126. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  127. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  128. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  129. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  130. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  131. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  132. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  133. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  134. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  135. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  136. package/skills/execute-work/SKILL.md +99 -57
  137. package/skills/execute-work/references/code-practices.md +4 -4
  138. package/skills/execute-work/references/execute-work-edges.md +62 -0
  139. package/skills/finish-branch/SKILL.md +2 -2
  140. package/skills/finish-branch/references/integration-options.md +1 -1
  141. package/skills/handoff/SKILL.md +3 -3
  142. package/skills/launch-work/SKILL.md +1 -1
  143. package/skills/launch-work/references/launch-readiness.md +2 -2
  144. package/skills/migration-work/SKILL.md +2 -2
  145. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  146. package/skills/release-work/SKILL.md +1 -1
  147. package/skills/release-work/references/release-evidence.md +1 -1
  148. package/skills/review-artifact/SKILL.md +129 -66
  149. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  150. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
  151. package/skills/review-work/SKILL.md +113 -68
  152. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  153. package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
  154. package/skills/review-work/references/security-risk-lens.md +18 -16
  155. package/skills/setup-freeflow/SKILL.md +16 -17
  156. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  157. package/skills/setup-freeflow/references/host-setup.md +6 -7
  158. package/skills/simplify-code/SKILL.md +97 -39
  159. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  160. package/skills/tdd/SKILL.md +100 -53
  161. package/skills/tdd/references/test-design.md +3 -3
  162. package/skills/track-work/SKILL.md +195 -58
  163. package/skills/track-work/references/working-record-format.md +515 -0
  164. package/skills/track-work/scripts/lib/document.mjs +713 -0
  165. package/skills/track-work/scripts/lib/format.mjs +166 -0
  166. package/skills/track-work/scripts/lib/store.mjs +233 -0
  167. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  168. package/skills/track-work/scripts/lib/views.mjs +40 -0
  169. package/skills/track-work/scripts/working-record.mjs +316 -0
  170. package/skills/verify-work/SKILL.md +57 -19
  171. package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
  172. package/skills/verify-work/references/integration-evidence.md +1 -7
  173. package/skills/verify-work/references/performance-evidence.md +2 -2
  174. package/skills/workflow/SKILL.md +137 -55
  175. package/skills/workflow/references/domain-skill-composition.md +26 -40
  176. package/skills/write-plan/SKILL.md +1 -1
  177. package/skills/write-skill/SKILL.md +218 -61
  178. package/skills/write-skill/references/activation-boundaries.md +63 -21
  179. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  180. package/skills/write-skill/references/progressive-disclosure.md +97 -33
  181. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  182. package/capabilities/output-router/SKILL.md +0 -206
  183. package/capabilities/output-router/references/safety-policy.md +0 -40
  184. package/capabilities/output-router/references/setup.md +0 -110
  185. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  186. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  187. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  188. package/pi-extension/dist/output-router/renderers.js +0 -1023
  189. package/pi-extension/dist/output-router/router-tools.js +0 -554
  190. package/pi-extension/dist/output-router/schemas.js +0 -383
  191. package/pi-extension/dist/output-router/status.js +0 -744
  192. package/pi-extension/dist/output-router/utils.js +0 -502
  193. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  194. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  195. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  196. package/pi-extension/src/output-router/renderers.ts +0 -1108
  197. package/pi-extension/src/output-router/router-tools.ts +0 -579
  198. package/pi-extension/src/output-router/schemas.ts +0 -387
  199. package/pi-extension/src/output-router/status.ts +0 -791
  200. package/pi-extension/src/output-router/utils.ts +0 -554
  201. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  202. package/pi-extension/tests/output-router/search.test.js +0 -722
  203. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  204. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  205. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  206. package/router/dist/benchmarks/benchmarks.js +0 -979
  207. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  208. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  209. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  210. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  211. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  212. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  213. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  214. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  215. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  216. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  217. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  218. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  219. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  220. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  221. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  222. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  223. package/router/dist/config/config.d.ts +0 -103
  224. package/router/dist/config/config.js +0 -632
  225. package/router/dist/config/router-contract.d.ts +0 -10
  226. package/router/dist/config/router-contract.js +0 -54
  227. package/router/dist/config/schema.d.ts +0 -27
  228. package/router/dist/config/schema.js +0 -648
  229. package/router/dist/config/types.d.ts +0 -573
  230. package/router/dist/config/types.js +0 -41
  231. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  232. package/router/dist/evidence/bounded-evidence.js +0 -323
  233. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  234. package/router/dist/evidence/evidence-range-selector.js +0 -232
  235. package/router/dist/evidence/evidence-search.d.ts +0 -27
  236. package/router/dist/evidence/evidence-search.js +0 -490
  237. package/router/dist/evidence/evidence.d.ts +0 -38
  238. package/router/dist/evidence/evidence.js +0 -200
  239. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  240. package/router/dist/evidence/failure-contracts.js +0 -166
  241. package/router/dist/evidence/line-ranges.d.ts +0 -24
  242. package/router/dist/evidence/line-ranges.js +0 -22
  243. package/router/dist/experiments/local-index.d.ts +0 -64
  244. package/router/dist/experiments/local-index.js +0 -581
  245. package/router/dist/index.d.ts +0 -30
  246. package/router/dist/index.js +0 -32
  247. package/router/dist/local/local-traversal.d.ts +0 -18
  248. package/router/dist/local/local-traversal.js +0 -334
  249. package/router/dist/processing/engine.d.ts +0 -132
  250. package/router/dist/processing/engine.js +0 -533
  251. package/router/dist/processing/reducers.d.ts +0 -280
  252. package/router/dist/processing/reducers.js +0 -1673
  253. package/router/dist/processing/renderers.d.ts +0 -31
  254. package/router/dist/processing/renderers.js +0 -195
  255. package/router/dist/processing/scripts.d.ts +0 -91
  256. package/router/dist/processing/scripts.js +0 -386
  257. package/router/dist/repo/repo-traversal.d.ts +0 -18
  258. package/router/dist/repo/repo-traversal.js +0 -245
  259. package/router/dist/routing/observed-reducers.d.ts +0 -41
  260. package/router/dist/routing/observed-reducers.js +0 -661
  261. package/router/dist/routing/observed-routing.d.ts +0 -26
  262. package/router/dist/routing/observed-routing.js +0 -378
  263. package/router/dist/routing/parsers.d.ts +0 -16
  264. package/router/dist/routing/parsers.js +0 -521
  265. package/router/dist/routing/run-filters.d.ts +0 -60
  266. package/router/dist/routing/run-filters.js +0 -238
  267. package/router/dist/routing/run-reducers.d.ts +0 -49
  268. package/router/dist/routing/run-reducers.js +0 -122
  269. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  270. package/router/dist/sandbox/adapter-roots.js +0 -66
  271. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  272. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  273. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  274. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  275. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  276. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  277. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  278. package/router/dist/sandbox/script-sandbox.js +0 -337
  279. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  280. package/router/dist/setup/script-transform-adapters.js +0 -278
  281. package/router/dist/tools/batch.d.ts +0 -33
  282. package/router/dist/tools/batch.js +0 -586
  283. package/router/dist/tools/run.d.ts +0 -62
  284. package/router/dist/tools/run.js +0 -1304
  285. package/router/dist/tools/search.d.ts +0 -66
  286. package/router/dist/tools/search.js +0 -1364
  287. package/router/dist/transform/engine.d.ts +0 -168
  288. package/router/dist/transform/engine.js +0 -2072
  289. package/router/dist/vault/vault-index.d.ts +0 -125
  290. package/router/dist/vault/vault-index.js +0 -416
  291. package/router/dist/vault/vault.d.ts +0 -138
  292. package/router/dist/vault/vault.js +0 -630
  293. package/runtime/interaction-contract.md +0 -16
  294. package/skills/discuss/references/checkpoints.md +0 -66
  295. package/skills/execute-work/references/execution-loop.md +0 -77
  296. package/skills/mode-contract/SKILL.md +0 -104
  297. package/skills/review-artifact/references/reviewer-prompt.md +0 -148
  298. package/skills/review-work/references/reviewer-prompt.md +0 -134
  299. package/skills/track-work/references/working-record-schema.md +0 -378
  300. package/skills/workflow/references/workflow-loop.md +0 -92
  301. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
@@ -1,7 +1,8 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { spawn } from "node:child_process";
2
3
  import { once } from "node:events";
3
4
  import { createWriteStream } from "node:fs";
4
- import { realpath } from "node:fs/promises";
5
+ import { mkdir, readFile, realpath } from "node:fs/promises";
5
6
  import path from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
 
@@ -21,12 +22,32 @@ import {
21
22
  fingerprintDirectory,
22
23
  materializeEnvironment,
23
24
  materializeFixture,
25
+ materializeRuntime,
24
26
  snapshotWorkspace,
25
27
  verifyEnvironment,
28
+ verifyRuntime,
26
29
  workspaceChanges,
27
30
  } from "./sandbox.mjs";
28
31
 
29
32
  const guardExtension = fileURLToPath(new URL("../pi-guard.mjs", import.meta.url));
33
+ const observerExtension = fileURLToPath(new URL("../pi-observer.mjs", import.meta.url));
34
+ const HOST_COMMANDS = { pi: "pi", piflow: "piflow" };
35
+ const BASE_PROCESS_ENVIRONMENT_KEYS = [
36
+ "PATH",
37
+ "HOME",
38
+ "TMPDIR",
39
+ "TMP",
40
+ "TEMP",
41
+ "LANG",
42
+ "LC_ALL",
43
+ "XDG_CONFIG_HOME",
44
+ "XDG_DATA_HOME",
45
+ "XDG_CACHE_HOME",
46
+ "PIFLOW_CODING_AGENT_DIR",
47
+ "PIFLOW_CODING_AGENT_SESSION_DIR",
48
+ "PI_PACKAGE_DIR",
49
+ "PI_OFFLINE",
50
+ ];
30
51
  const TURN_WORKSPACE_KINDS = new Set(["path", "changed-paths", "file-text", "json"]);
31
52
 
32
53
  export class VariantSetupError extends Error {
@@ -36,19 +57,20 @@ export class VariantSetupError extends Error {
36
57
  }
37
58
  }
38
59
 
39
- export async function runDescription({ group, variant, root, variantDirectory, fixture, signal }) {
60
+ export async function runDescription({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
40
61
  if (group.input.turns !== undefined) {
41
- return runPersistentDescription({ group, variant, root, variantDirectory, fixture, signal });
62
+ return runPersistentDescription({ group, variant, root, variantDirectory, fixture, runtime, signal });
42
63
  }
43
- return runOneShotDescription({ group, variant, root, variantDirectory, fixture, signal });
64
+ return runOneShotDescription({ group, variant, root, variantDirectory, fixture, runtime, signal });
44
65
  }
45
66
 
46
- export async function runBody({ group, variant, root, variantDirectory, fixture, signal }) {
47
- const options = { group, variant, root, variantDirectory, fixture, signal };
67
+ export async function runBody({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
68
+ const options = { group, variant, root, variantDirectory, fixture, runtime, signal };
48
69
  const subject = await prepareSubject(options, "rpc");
49
70
  const before = await fingerprintDirectory(subject.workspace);
50
71
  const declaredPrompts = group.input.prompt === undefined ? [...group.input.turns] : [group.input.prompt];
51
72
  const observation = await runRpcBodySession({
73
+ command: subject.host.command,
52
74
  args: subject.args,
53
75
  cwd: subject.workspace,
54
76
  eventsFile: subject.eventsFile,
@@ -61,6 +83,11 @@ export async function runBody({ group, variant, root, variantDirectory, fixture,
61
83
  ? (turn) => snapshotWorkspace({ workspace: subject.workspace, variantDirectory, turn, before })
62
84
  : null,
63
85
  });
86
+ const contextObservation = await readContextObservations(
87
+ subject.contextObservationPath,
88
+ subject.requiresContextObservation,
89
+ );
90
+ observation.contextObservationArtifact = contextObservation.artifact;
64
91
  await verifySubjectResources(subject, observation);
65
92
  const after = await fingerprintDirectory(subject.workspace);
66
93
 
@@ -132,14 +159,17 @@ function subjectRun({ group, variant }, subject, observation) {
132
159
  source: subject.environment.source,
133
160
  skills: subject.environment.skills,
134
161
  context: subject.environment.context,
162
+ runtime: subject.runtime,
135
163
  contextDelivery: subject.environment.contextDelivery,
136
164
  targetPath: subject.environment.targetPath,
137
165
  },
166
+ contextObservationArtifact: observation.contextObservationArtifact ?? null,
138
167
  response: observation.response,
139
168
  transcript: observation.transcript,
140
169
  usage: observation.usage,
141
170
  process: {
142
- command: "pi",
171
+ command: subject.host.command,
172
+ host: subject.host.name,
143
173
  args: subject.args,
144
174
  exitCode: observation.exitCode,
145
175
  signal: observation.signal,
@@ -156,6 +186,7 @@ function subjectRun({ group, variant }, subject, observation) {
156
186
  async function runOneShotDescription(options) {
157
187
  const subject = await prepareSubject(options, "json");
158
188
  const observation = await runPiProcess({
189
+ command: subject.host.command,
159
190
  args: subject.args,
160
191
  cwd: subject.workspace,
161
192
  eventsFile: subject.eventsFile,
@@ -163,6 +194,11 @@ async function runOneShotDescription(options) {
163
194
  signal: options.signal,
164
195
  environment: subject.processEnvironment,
165
196
  });
197
+ const contextObservation = await readContextObservations(
198
+ subject.contextObservationPath,
199
+ subject.requiresContextObservation,
200
+ );
201
+ observation.contextObservationArtifact = contextObservation.artifact;
166
202
  await verifySubjectResources(subject, observation);
167
203
  const successfulReadPaths = await canonicalReadPaths(observation.successfulReads, subject.workspace);
168
204
  const targetRead =
@@ -181,6 +217,7 @@ async function runOneShotDescription(options) {
181
217
  async function runPersistentDescription(options) {
182
218
  const subject = await prepareSubject(options, "rpc");
183
219
  const observation = await runRpcDescriptionSession({
220
+ command: subject.host.command,
184
221
  args: subject.args,
185
222
  cwd: subject.workspace,
186
223
  eventsFile: subject.eventsFile,
@@ -189,6 +226,11 @@ async function runPersistentDescription(options) {
189
226
  prompts: options.group.input.turns,
190
227
  environment: subject.processEnvironment,
191
228
  });
229
+ const contextObservation = await readContextObservations(
230
+ subject.contextObservationPath,
231
+ subject.requiresContextObservation,
232
+ );
233
+ observation.contextObservationArtifact = contextObservation.artifact;
192
234
  await verifySubjectResources(subject, observation);
193
235
 
194
236
  const turns = [];
@@ -222,52 +264,130 @@ async function runPersistentDescription(options) {
222
264
  });
223
265
  }
224
266
 
225
- async function prepareSubject({ group, variant, root, variantDirectory, fixture: fixtureSnapshot }, mode) {
267
+ async function prepareSubject({ group, variant, root, variantDirectory, fixture: fixtureSnapshot, runtime }, mode) {
226
268
  const workspace = path.join(variantDirectory, "workspace");
227
269
 
228
270
  let environment;
229
271
  let fixture;
272
+ let materializedRuntime;
230
273
  try {
231
274
  environment = await materializeEnvironment({
232
275
  environment: group.variants[variant],
233
276
  root,
234
277
  variantDirectory,
235
278
  });
279
+ materializedRuntime = await materializeRuntime({ runtime, variantDirectory });
236
280
  fixture = await materializeFixture({ fixture: fixtureSnapshot, workspace });
237
281
  } catch (error) {
238
282
  if (error instanceof VariantSetupError) throw error;
239
283
  throw new VariantSetupError(error instanceof Error ? error.message : String(error));
240
284
  }
241
285
 
242
- const args = piArguments(group, environment, mode);
286
+ const host = { name: materializedRuntime.host, command: HOST_COMMANDS[materializedRuntime.host] };
287
+ const contextObservationPath = path.join(variantDirectory, "context-observations.jsonl");
288
+ const requiresContextObservation = group.expectations.some((expectation) => expectation.kind === "context-text");
289
+ const sessionDirectory = path.join(variantDirectory, "session");
290
+ if (materializedRuntime.session) await mkdir(sessionDirectory, { recursive: true });
291
+ const args = piArguments(group, environment, materializedRuntime, mode, requiresContextObservation, sessionDirectory);
243
292
  const allowedRoots = [
244
293
  await realpath(workspace),
245
294
  ...environment.skills.map((skill) => skill.path),
246
295
  ...environment.context.map((entry) => entry.path),
296
+ ...materializedRuntime.extensions.flatMap((bundle) => bundle.resources.map((resource) => resource.path)),
247
297
  ];
248
298
  return {
249
299
  args,
250
300
  environment,
301
+ runtime: materializedRuntime,
302
+ host,
251
303
  fixture,
252
304
  workspace,
305
+ contextObservationPath,
306
+ requiresContextObservation,
307
+ sessionDirectory,
253
308
  eventsFile: path.join(variantDirectory, "events.jsonl"),
254
309
  stderrFile: path.join(variantDirectory, "stderr.log"),
255
310
  startedAt: new Date().toISOString(),
256
- processEnvironment: {
257
- ...process.env,
258
- PI_SKIP_VERSION_CHECK: "1",
259
- PI_TELEMETRY: "0",
260
- SKILL_EVAL_ALLOWED_ROOTS: JSON.stringify(allowedRoots),
261
- SKILL_EVAL_WRITABLE_ROOT: await realpath(workspace),
262
- SKILL_EVAL_ALLOWED_TOOLS: JSON.stringify(group.tools),
263
- SKILL_EVAL_CONTEXT_MANIFEST: environment.contextDelivery.manifestPath ?? "",
264
- },
311
+ processEnvironment: createProcessEnvironment({
312
+ runtime: materializedRuntime,
313
+ group,
314
+ workspace,
315
+ allowedRoots,
316
+ contextManifestPath: environment.contextDelivery.manifestPath,
317
+ contextObservationPath,
318
+ requiresContextObservation,
319
+ }),
320
+ };
321
+ }
322
+
323
+ function createProcessEnvironment({
324
+ runtime,
325
+ group,
326
+ workspace,
327
+ allowedRoots,
328
+ contextManifestPath,
329
+ contextObservationPath,
330
+ requiresContextObservation,
331
+ }) {
332
+ const processEnvironment = Object.fromEntries(
333
+ BASE_PROCESS_ENVIRONMENT_KEYS.flatMap((key) => (process.env[key] === undefined ? [] : [[key, process.env[key]]])),
334
+ );
335
+ Object.assign(processEnvironment, {
336
+ PWD: workspace,
337
+ ...runtime.environment.literal,
338
+ PI_SKIP_VERSION_CHECK: "1",
339
+ PI_TELEMETRY: "0",
340
+ SKILL_EVAL_ALLOWED_ROOTS: JSON.stringify(allowedRoots),
341
+ SKILL_EVAL_WRITABLE_ROOT: workspace,
342
+ SKILL_EVAL_ALLOWED_TOOLS: JSON.stringify(group.tools),
343
+ SKILL_EVAL_CONTEXT_MANIFEST: contextManifestPath ?? "",
344
+ SKILL_EVAL_HOST: runtime.host,
345
+ });
346
+ for (const key of runtime.environment.inherit) {
347
+ if (process.env[key] === undefined) {
348
+ throw new VariantSetupError(`inherited runtime environment variable is unavailable: ${key}`);
349
+ }
350
+ processEnvironment[key] = process.env[key];
351
+ }
352
+ if (requiresContextObservation) processEnvironment.SKILL_EVAL_CONTEXT_OBSERVATION_PATH = contextObservationPath;
353
+ return processEnvironment;
354
+ }
355
+
356
+ async function readContextObservations(file, required) {
357
+ const contents = await readFile(file, "utf8").catch((error) => {
358
+ if (error?.code === "ENOENT") {
359
+ if (required) throw new Error("context observer produced no evidence");
360
+ return "";
361
+ }
362
+ throw error;
363
+ });
364
+ const observations = [];
365
+ for (const line of contents.split("\n").filter(Boolean)) {
366
+ try {
367
+ observations.push(JSON.parse(line));
368
+ } catch (error) {
369
+ throw new Error(`context observation is malformed: ${error instanceof Error ? error.message : String(error)}`);
370
+ }
371
+ }
372
+ return {
373
+ observations,
374
+ artifact:
375
+ contents === ""
376
+ ? null
377
+ : {
378
+ path: file,
379
+ sha256: createHash("sha256").update(contents, "utf8").digest("hex"),
380
+ bytes: Buffer.byteLength(contents, "utf8"),
381
+ count: observations.length,
382
+ surfaces: [...new Set(observations.map((entry) => entry.surface).filter(Boolean))],
383
+ },
265
384
  };
266
385
  }
267
386
 
268
387
  async function verifySubjectResources(subject, observation) {
269
388
  try {
270
389
  await verifyEnvironment(subject.environment);
390
+ await verifyRuntime(subject.runtime);
271
391
  } catch (error) {
272
392
  observation.protocolErrors ??= [];
273
393
  observation.protocolErrors.push({
@@ -300,20 +420,15 @@ function subjectRunState(observation) {
300
420
  return "infrastructure-failed";
301
421
  }
302
422
 
303
- function piArguments(group, environment, mode) {
423
+ function piArguments(group, environment, runtime, mode, observeContext, sessionDirectory) {
304
424
  const args = ["--mode", mode];
305
425
  if (mode === "json") args.push("-p");
306
- args.push(
307
- "--no-session",
308
- "--no-extensions",
309
- "--extension",
310
- guardExtension,
311
- "--no-skills",
312
- "--no-prompt-templates",
313
- "--no-themes",
314
- "--no-context-files",
315
- "--no-approve",
316
- );
426
+ if (runtime.session) args.push("--session-dir", sessionDirectory);
427
+ else args.push("--no-session");
428
+ args.push("--no-extensions", "--extension", guardExtension);
429
+ for (const bundle of runtime.extensions) args.push("--extension", bundle.entry);
430
+ if (observeContext) args.push("--extension", observerExtension);
431
+ args.push("--no-skills", "--no-prompt-templates", "--no-themes", "--no-context-files", "--no-approve");
317
432
  if (group.tools.length === 0) args.push("--no-tools");
318
433
  else args.push("--tools", group.tools.join(","));
319
434
  for (const skill of environment.skills) args.push("--skill", skill.path);
@@ -323,8 +438,8 @@ function piArguments(group, environment, mode) {
323
438
  return args;
324
439
  }
325
440
 
326
- async function runPiProcess({ args, cwd, eventsFile, stderrFile, signal, environment }) {
327
- const child = spawn("pi", args, {
441
+ async function runPiProcess({ command, args, cwd, eventsFile, stderrFile, signal, environment }) {
442
+ const child = spawn(command, args, {
328
443
  cwd,
329
444
  detached: process.platform !== "win32",
330
445
  env: environment,
@@ -28,8 +28,8 @@ export async function runRpcBodySession(options) {
28
28
  }
29
29
 
30
30
  class RpcSession {
31
- constructor({ args, cwd, eventsFile, stderrFile, signal, environment, afterTurn = null }) {
32
- this.child = spawn("pi", args, {
31
+ constructor({ command = "pi", args, cwd, eventsFile, stderrFile, signal, environment, afterTurn = null }) {
32
+ this.child = spawn(command, args, {
33
33
  cwd,
34
34
  detached: process.platform !== "win32",
35
35
  env: environment,
@@ -79,17 +79,17 @@ class RpcSession {
79
79
  async run(prompts, targetPath = null) {
80
80
  let deliveredPrompts = prompts;
81
81
  /** @type {any} */
82
- let delivery = { kind: "natural-prompt", turn: 1, targetPath: null };
82
+ let delivery = { kind: "unavailable", turn: 1, targetPath };
83
83
  try {
84
84
  await this.configure();
85
85
  if (targetPath !== null) {
86
- delivery = { kind: "unavailable", turn: 1, targetPath };
87
86
  const command = await this.resolveSkillCommand(targetPath);
88
87
  deliveredPrompts = [`/${command} ${prompts[0]}`, ...prompts.slice(1)];
89
88
  delivery = { kind: "explicit-skill-command", turn: 1, targetPath, command };
90
89
  }
91
90
  for (const [index, prompt] of deliveredPrompts.entries()) {
92
91
  if (this.signal?.aborted || this.terminationReason !== null) break;
92
+ if (delivery.kind === "unavailable") delivery = { kind: "natural-prompt", turn: 1, targetPath: null };
93
93
  const turn = await this.runTurn(index + 1, prompt);
94
94
  this.turns.push(turn);
95
95
  if (this.afterTurn !== null) turn.workspace = await this.afterTurn(turn.turn);
@@ -5,11 +5,14 @@ import { loadDefinition, selectDefinition } from "./definitions.mjs";
5
5
  import { createInvocationId, fileIdentity, writeJson, writeText } from "./evidence.mjs";
6
6
  import { gradeDeterministic } from "./grade.mjs";
7
7
  import { runBody, runDescription, VariantSetupError } from "./pi.mjs";
8
- import { prepareFixture } from "./sandbox.mjs";
8
+ import { prepareFixture, prepareRuntime } from "./sandbox.mjs";
9
9
 
10
10
  const VARIANTS = ["baseline", "candidate"];
11
11
  const DESCRIPTION_TOOLS = new Set(["read"]);
12
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 = { host: "pi", session: false, extensions: [], environment: { literal: {}, inherit: [] } };
13
16
 
14
17
  /**
15
18
  * @param {string} definitionFile
@@ -69,8 +72,18 @@ function assertSupportedSelection(selection) {
69
72
  throw new Error("command expectations are not supported");
70
73
  }
71
74
  const supportedTools = group.type === "body" ? BODY_TOOLS : DESCRIPTION_TOOLS;
72
- if (group.tools.some((tool) => !supportedTools.has(tool))) {
73
- throw new Error(`${group.type} execution currently supports only ${[...supportedTools].join(", ")} tools`);
75
+ const unsupportedNativeTools = group.tools.filter((tool) => NATIVE_TOOLS.has(tool) && !supportedTools.has(tool));
76
+ if (unsupportedNativeTools.length > 0) {
77
+ throw new Error(`${group.type} execution does not support native tools: ${unsupportedNativeTools.join(", ")}`);
78
+ }
79
+ const customTools = group.tools.filter((tool) => !NATIVE_TOOLS.has(tool) && !supportedTools.has(tool));
80
+ if (group.type === "description" && customTools.length > 0) {
81
+ throw new Error(`${group.type} execution does not support custom tools: ${customTools.join(", ")}`);
82
+ }
83
+ if (customTools.length > 0 && (group.runtime?.extensions?.length ?? 0) === 0) {
84
+ throw new Error(
85
+ `${group.type} execution requires declared extensions for custom tools: ${customTools.join(", ")}`,
86
+ );
74
87
  }
75
88
  }
76
89
  }
@@ -82,12 +95,18 @@ async function runGroup({ selected, root, resultDirectory, signal }) {
82
95
  await writeJson(path.join(groupDirectory, "definition.json"), group);
83
96
 
84
97
  let fixture = null;
85
- let fixtureError = null;
98
+ let runtime = null;
99
+ let resourceError = null;
86
100
  if (!signal?.aborted) {
87
101
  try {
88
102
  fixture = await prepareFixture({ declaredPath: group.fixture, root, groupDirectory });
103
+ runtime = await prepareRuntime({
104
+ runtime: group.runtime ?? DEFAULT_RUNTIME,
105
+ root,
106
+ groupDirectory,
107
+ });
89
108
  } catch (error) {
90
- fixtureError = error instanceof Error ? error.message : String(error);
109
+ resourceError = error instanceof Error ? error.message : String(error);
91
110
  }
92
111
  }
93
112
 
@@ -100,10 +119,10 @@ async function runGroup({ selected, root, resultDirectory, signal }) {
100
119
  runs[variant] = notSelectedRun(group, variant);
101
120
  } else if (signal?.aborted) {
102
121
  runs[variant] = cancelledRun(group, variant);
103
- } else if (fixtureError !== null) {
104
- runs[variant] = invalidRun(group, variant, fixtureError);
122
+ } else if (resourceError === null) {
123
+ runs[variant] = await executeVariant({ group, variant, root, variantDirectory, fixture, runtime, signal });
105
124
  } else {
106
- runs[variant] = await executeVariant({ group, variant, root, variantDirectory, fixture, signal });
125
+ runs[variant] = invalidRun(group, variant, resourceError);
107
126
  }
108
127
  const persisted = await persistRunOrFailure(variantDirectory, runs[variant]);
109
128
  runs[variant] = persisted.run;
@@ -120,7 +139,8 @@ async function runGroup({ selected, root, resultDirectory, signal }) {
120
139
  (variant) => runs[variant],
121
140
  );
122
141
  const selectedState = selectedGroupState(selectedRuns, persistedGrade.grade);
123
- const groupState = selectedState === "cancelled" ? selectedState : fixtureError === null ? selectedState : "invalid";
142
+ let groupState = selectedState;
143
+ if (selectedState !== "cancelled" && resourceError !== null) groupState = "invalid";
124
144
  const groupResult = {
125
145
  id: group.id,
126
146
  position: selected.position,
@@ -205,10 +225,10 @@ function errorMessage(error) {
205
225
  return error instanceof Error ? error.message : String(error);
206
226
  }
207
227
 
208
- async function executeVariant({ group, variant, root, variantDirectory, fixture, signal }) {
228
+ async function executeVariant({ group, variant, root, variantDirectory, fixture, runtime, signal }) {
209
229
  try {
210
230
  const run = group.type === "body" ? runBody : runDescription;
211
- return await run({ group, variant, root, variantDirectory, fixture, signal });
231
+ return await run({ group, variant, root, variantDirectory, fixture, runtime, signal });
212
232
  } catch (error) {
213
233
  if (error instanceof VariantSetupError) return invalidRun(group, variant, error.message);
214
234
  return infrastructureFailedRun(group, variant, error);
@@ -241,16 +261,18 @@ async function persistRun(variantDirectory, run) {
241
261
  await writeText(path.join(variantDirectory, "final.md"), response === "" ? "" : `${response}\n`);
242
262
  const runFile = path.join(variantDirectory, "run.json");
243
263
  const hasProcessEvidence = run.process !== undefined;
244
- await writeJson(runFile, {
264
+ const persisted = {
245
265
  ...run,
246
- ...(Array.isArray(run.turns) ? { turns: externalizeTurnTranscripts(run.turns) } : {}),
247
266
  transcript: "transcript.json",
248
267
  artifacts: {
249
268
  events: hasProcessEvidence ? "events.jsonl" : null,
250
269
  final: "final.md",
251
270
  stderr: hasProcessEvidence ? "stderr.log" : null,
271
+ context: run.contextObservationArtifact ? "context-observations.jsonl" : null,
252
272
  },
253
- });
273
+ };
274
+ if (Array.isArray(run.turns)) persisted.turns = externalizeTurnTranscripts(run.turns);
275
+ await writeJson(runFile, persisted);
254
276
  return runFile;
255
277
  }
256
278
 
@@ -69,6 +69,98 @@ export async function materializeFixture({ fixture, workspace }) {
69
69
  return { ...fixture, materializedPath };
70
70
  }
71
71
 
72
+ export async function prepareRuntime({ runtime, root, groupDirectory }) {
73
+ const canonicalRoot = await realpath(root);
74
+ const runtimeRoot = path.join(groupDirectory, "resources", "runtime", "extensions");
75
+ await mkdir(runtimeRoot, { recursive: true });
76
+ const bundles = [];
77
+
78
+ for (const [index, bundle] of runtime.extensions.entries()) {
79
+ const bundleRoot = path.join(runtimeRoot, String(index));
80
+ await mkdir(bundleRoot, { recursive: true });
81
+ const resources = [];
82
+ for (const declaredPath of bundle.resources) {
83
+ resources.push(
84
+ await snapshotWorkingTreeResource({
85
+ canonicalRoot,
86
+ declaredPath,
87
+ destination: path.join(bundleRoot, declaredPath),
88
+ requireDirectory: true,
89
+ label: "runtime resource",
90
+ }),
91
+ );
92
+ }
93
+ const root = await realpath(bundleRoot);
94
+ const entry = await realpath(path.join(root, bundle.entry)).catch(() => null);
95
+ if (entry === null || !isContained(root, entry)) {
96
+ throw new Error(`declared runtime entry is missing or escapes its bundle: ${bundle.entry}`);
97
+ }
98
+ const entryStat = await lstat(entry);
99
+ if (!entryStat.isFile()) throw new Error(`declared runtime entry is not a file: ${bundle.entry}`);
100
+ if (!resources.some((resource) => isContained(resource.path, entry))) {
101
+ throw new Error(`declared runtime entry is not contained by a resource: ${bundle.entry}`);
102
+ }
103
+ bundles.push({
104
+ entry,
105
+ root,
106
+ resources,
107
+ });
108
+ }
109
+
110
+ return {
111
+ host: runtime.host,
112
+ session: runtime.session,
113
+ environment: { ...runtime.environment },
114
+ extensions: bundles,
115
+ };
116
+ }
117
+
118
+ export async function materializeRuntime({ runtime, variantDirectory }) {
119
+ const runtimeRoot = path.join(variantDirectory, "resources", "runtime", "extensions");
120
+ await mkdir(runtimeRoot, { recursive: true });
121
+ const bundles = [];
122
+
123
+ for (const [index, bundle] of runtime.extensions.entries()) {
124
+ const destination = path.join(runtimeRoot, String(index));
125
+ await cp(bundle.root, destination, {
126
+ recursive: true,
127
+ dereference: false,
128
+ errorOnExist: true,
129
+ force: false,
130
+ });
131
+ const root = await realpath(destination);
132
+ const resources = [];
133
+ for (const resource of bundle.resources) {
134
+ const resourceRoot = await realpath(path.join(root, resource.declaredPath));
135
+ const files = await fingerprintDirectory(resourceRoot);
136
+ if (JSON.stringify(files) !== JSON.stringify(resource.files)) {
137
+ throw new Error(`declared runtime resource changed while being copied: ${resource.declaredPath}`);
138
+ }
139
+ resources.push({ ...resource, path: resourceRoot, files });
140
+ }
141
+ const entry = await realpath(path.join(root, path.relative(bundle.root, bundle.entry)));
142
+ bundles.push({ ...bundle, entry, root, resources });
143
+ }
144
+
145
+ return {
146
+ host: runtime.host,
147
+ session: runtime.session,
148
+ environment: { ...runtime.environment },
149
+ extensions: bundles,
150
+ };
151
+ }
152
+
153
+ export async function verifyRuntime(runtime) {
154
+ for (const bundle of runtime.extensions) {
155
+ for (const resource of bundle.resources) {
156
+ const current = await fingerprintDirectory(resource.path);
157
+ if (JSON.stringify(current) !== JSON.stringify(resource.files)) {
158
+ throw new Error(`declared runtime resource changed during subject execution: ${resource.declaredPath}`);
159
+ }
160
+ }
161
+ }
162
+ }
163
+
72
164
  export async function materializeEnvironment({ environment, root, variantDirectory }) {
73
165
  const canonicalRoot = await realpath(root);
74
166
  let source;
@@ -15,7 +15,7 @@ function parseStringArray(value) {
15
15
 
16
16
  function isContained(root, target) {
17
17
  const relative = path.relative(root, target);
18
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
18
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
19
19
  }
20
20
 
21
21
  function resolveWritablePath(cwd, requested) {
@@ -0,0 +1,66 @@
1
+ import { appendFileSync } from "node:fs";
2
+
3
+ const outputPath = process.env.SKILL_EVAL_CONTEXT_OBSERVATION_PATH;
4
+ let turn = 0;
5
+ let requestInTurn = 0;
6
+ let providerRequest = 0;
7
+
8
+ function textFromContent(content) {
9
+ if (typeof content === "string") return content;
10
+ if (!Array.isArray(content)) return "";
11
+ return content
12
+ .map((block) => {
13
+ if (!block || typeof block !== "object") return "";
14
+ if (block.type === "text" && typeof block.text === "string") return block.text;
15
+ if (block.type === "image" && typeof block.mimeType === "string") return `[image:${block.mimeType}]`;
16
+ return "";
17
+ })
18
+ .filter(Boolean)
19
+ .join("\n");
20
+ }
21
+
22
+ function append(event) {
23
+ if (!outputPath) return;
24
+ appendFileSync(outputPath, `${JSON.stringify({ timestamp: new Date().toISOString(), turn, ...event })}\n`, "utf8");
25
+ }
26
+
27
+ export default function skillEvaluationObserver(pi) {
28
+ pi.on("before_agent_start", (event, ctx) => {
29
+ turn += 1;
30
+ requestInTurn = 0;
31
+ const systemPrompt =
32
+ typeof event?.systemPrompt === "string" ? event.systemPrompt : String(ctx?.getSystemPrompt?.() ?? "");
33
+ append({
34
+ kind: "context",
35
+ surface: "system-prompt",
36
+ prompt: typeof event?.prompt === "string" ? event.prompt : null,
37
+ characters: systemPrompt.length,
38
+ text: systemPrompt,
39
+ systemPrompt,
40
+ });
41
+ });
42
+
43
+ pi.on("context", (event) => {
44
+ providerRequest += 1;
45
+ requestInTurn += 1;
46
+ const messages = Array.isArray(event?.messages) ? event.messages : [];
47
+ const normalized = messages.map((message) => ({
48
+ role: message?.role ?? null,
49
+ toolName: message?.toolName ?? null,
50
+ text: textFromContent(message?.content),
51
+ }));
52
+ const text = normalized
53
+ .map((message) => message.text)
54
+ .filter(Boolean)
55
+ .join("\n");
56
+ append({
57
+ kind: "context",
58
+ surface: "provider-context",
59
+ providerRequest,
60
+ requestInTurn,
61
+ characters: text.length,
62
+ text,
63
+ messages: normalized,
64
+ });
65
+ });
66
+ }
@@ -34,8 +34,9 @@ Selection:
34
34
  Current run support:
35
35
  Description and explicit-body groups with prompt or ordered turns
36
36
  Working-tree or Git-backed ordered skills/context; optional fresh fixture copies
37
- Description tools: read; body tools: read, write, edit
38
- Deterministic reads, paths, changed paths, text, JSON, and factual comparisons
37
+ Description tools: read; body tools: read, write, edit, or declared extension tools
38
+ Runtime profiles: Pi or PiFlow hosts, ordered immutable extension bundles, environment overrides
39
+ Deterministic reads, paths, changed paths, text, JSON, tool calls, context observations, and factual comparisons
39
40
  Ordered suites run serially and continue after isolated variant, group, or post-processing failures
40
41
  Grade-first views show compact criterion details, usage, and result-relative artifact paths
41
42
  `,
@@ -71,9 +72,7 @@ function parseOperation(values) {
71
72
 
72
73
  if (!command || command === "--help" || command === "-h") {
73
74
  printUsage();
74
- } else if (!commands.has(command)) {
75
- fail(`Unknown command: ${command}`);
76
- } else {
75
+ } else if (commands.has(command)) {
77
76
  try {
78
77
  const operation = parseOperation(argv.slice(1));
79
78
  if (command === "view") {
@@ -98,4 +97,6 @@ if (!command || command === "--help" || command === "-h") {
98
97
  } catch (error) {
99
98
  fail(error instanceof Error ? error.message : String(error), 1);
100
99
  }
100
+ } else {
101
+ fail(`Unknown command: ${command}`);
101
102
  }