@hassangameryt/freeflow 0.7.3 → 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 (426) 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/NOTICE +14 -0
  7. package/README.md +101 -159
  8. package/capabilities/cognitive-routing/SKILL.md +47 -210
  9. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  10. package/capabilities/cognitive-routing/references/worker.md +38 -0
  11. package/capabilities/compaction/SKILL.md +49 -0
  12. package/capabilities/compaction/references/summary-format.md +250 -0
  13. package/capabilities/messaging/SKILL.md +49 -0
  14. package/capabilities/task-memory/SKILL.md +87 -0
  15. package/command-surface.json +8 -52
  16. package/gemini-extension.json +1 -1
  17. package/hooks/claude/hooks.json +13 -3
  18. package/hooks/codex/hooks.json +2 -1
  19. package/hooks/shared/runtime-context.mjs +73 -83
  20. package/licenses/Apache-2.0.txt +55 -0
  21. package/package.json +42 -12
  22. package/pi-extension/README.md +41 -0
  23. package/pi-extension/dist/ask-user/ask.js +108 -0
  24. package/pi-extension/dist/ask-user/form.js +250 -0
  25. package/pi-extension/dist/ask-user/questions.js +66 -0
  26. package/pi-extension/dist/ask-user/tool.js +124 -0
  27. package/pi-extension/dist/cognitive-routing/assembler.js +417 -0
  28. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  29. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +8 -6
  30. package/pi-extension/dist/cognitive-routing/economics.js +45 -0
  31. package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
  32. package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +107 -41
  33. package/pi-extension/dist/cognitive-routing/gate.js +256 -0
  34. package/pi-extension/dist/cognitive-routing/handlers.js +901 -0
  35. package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
  36. package/pi-extension/dist/cognitive-routing/model-control.js +476 -0
  37. package/pi-extension/dist/cognitive-routing/projection.js +520 -0
  38. package/pi-extension/dist/cognitive-routing/provenance.js +83 -0
  39. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +15 -15
  40. package/pi-extension/dist/cognitive-routing/runtime.js +398 -0
  41. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +47 -11
  42. package/pi-extension/dist/cognitive-routing/session.js +223 -0
  43. package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +126 -87
  44. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +55 -386
  45. package/pi-extension/dist/cognitive-routing/status.js +81 -0
  46. package/pi-extension/dist/cognitive-routing/tools.js +74 -0
  47. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +14 -11
  48. package/pi-extension/dist/compaction/carry.js +205 -0
  49. package/pi-extension/dist/compaction/config.js +24 -0
  50. package/pi-extension/dist/compaction/controller.js +494 -0
  51. package/pi-extension/dist/compaction/harness.js +93 -0
  52. package/pi-extension/dist/compaction/results.js +159 -0
  53. package/pi-extension/dist/compaction/thresholds.js +35 -0
  54. package/pi-extension/dist/compaction/tool.js +57 -0
  55. package/pi-extension/dist/host/branch.js +13 -0
  56. package/pi-extension/dist/host/canonical.js +12 -0
  57. package/pi-extension/dist/host/catalog.js +62 -0
  58. package/pi-extension/dist/host/config-validation.js +233 -0
  59. package/pi-extension/dist/host/config.js +361 -0
  60. package/pi-extension/dist/host/inbound.js +142 -0
  61. package/pi-extension/dist/host/projection-tags.js +60 -0
  62. package/pi-extension/dist/host/prompts.js +93 -0
  63. package/pi-extension/dist/{session-sources → host}/read-only-session.js +117 -1
  64. package/pi-extension/dist/{runtime → host}/request-history.js +125 -39
  65. package/pi-extension/dist/host/rows.js +25 -0
  66. package/pi-extension/dist/host/runtime-state.js +204 -0
  67. package/pi-extension/dist/host/section-keeper.js +85 -0
  68. package/pi-extension/dist/host/settings/freeflow-command.js +2323 -0
  69. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +205 -73
  70. package/pi-extension/dist/host/staging.js +28 -0
  71. package/pi-extension/dist/host/status.js +115 -0
  72. package/pi-extension/dist/index.js +1004 -188
  73. package/pi-extension/dist/messaging/config.js +27 -0
  74. package/pi-extension/dist/messaging/messaging.js +398 -0
  75. package/pi-extension/dist/messaging/registry.js +57 -0
  76. package/pi-extension/dist/messaging/render.js +90 -0
  77. package/pi-extension/dist/messaging/tools.js +109 -0
  78. package/pi-extension/dist/messaging/transport.js +101 -0
  79. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  80. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  81. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  82. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  83. package/pi-extension/dist/provider-support/cache/keepalive.js +222 -0
  84. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  85. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  86. package/pi-extension/dist/provider-support/effort.js +13 -0
  87. package/pi-extension/dist/provider-support/index.js +44 -3
  88. package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
  89. package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
  90. package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
  91. package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
  92. package/pi-extension/dist/provider-support/routes.js +17 -0
  93. package/pi-extension/dist/task-memory/config.js +49 -0
  94. package/pi-extension/dist/task-memory/registry.js +119 -0
  95. package/pi-extension/dist/task-memory/render.js +107 -0
  96. package/pi-extension/dist/task-memory/runtime.js +994 -0
  97. package/pi-extension/dist/task-memory/tool.js +233 -0
  98. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  99. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  100. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  101. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  102. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  103. package/pi-extension/dist/tool-execution/background.js +430 -0
  104. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  105. package/pi-extension/dist/tool-execution/config.js +33 -0
  106. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  107. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  108. package/pi-extension/dist/tool-execution/file-tracking.js +163 -0
  109. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  110. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  111. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  112. package/pi-extension/dist/tool-execution/tools.js +16 -0
  113. package/pi-extension/freeflow/README.md +9 -0
  114. package/runtime/prompts/cognitive-routing.md +4 -4
  115. package/runtime/prompts/core.md +29 -72
  116. package/runtime/prompts/interaction-contract.md +18 -8
  117. package/runtime/prompts/tool-execution.md +25 -0
  118. package/runtime/prompts/working-method.md +25 -0
  119. package/skills/deliver-work/SKILL.md +76 -0
  120. package/skills/deliver-work/references/branch-closeout.md +134 -0
  121. package/skills/deliver-work/references/commit.md +83 -0
  122. package/skills/deliver-work/references/launch.md +94 -0
  123. package/skills/deliver-work/references/release.md +85 -0
  124. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  125. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  126. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  127. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  128. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  129. package/skills/discuss-work/SKILL.md +148 -0
  130. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +13 -50
  131. package/skills/discuss-work/references/learning-action.md +36 -0
  132. package/skills/discuss-work/scripts/choices.mjs +101 -0
  133. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  134. package/skills/evaluate-behavior/SKILL.md +107 -0
  135. package/skills/evaluate-behavior/references/authoring.md +381 -0
  136. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  137. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  138. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  139. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  140. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  141. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  142. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  143. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  144. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  145. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  146. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  147. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  148. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  149. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  150. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  151. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  152. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  153. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  154. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  156. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  157. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  158. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  159. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  160. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  161. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  162. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  163. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  164. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  165. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  166. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  167. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  168. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  169. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  170. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  171. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  172. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  173. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  174. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  175. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  176. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  177. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  178. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  179. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  180. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  181. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  182. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  183. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  184. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  185. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  186. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  187. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +121 -14
  188. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  189. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  190. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  191. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  192. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  193. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  194. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +239 -262
  195. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  196. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  197. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  198. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  199. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +218 -11
  200. package/skills/evaluate-behavior/scripts/pi-command-tool.mjs +159 -0
  201. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  202. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  203. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  204. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  205. package/skills/execute-work/SKILL.md +96 -72
  206. package/skills/execute-work/references/code-practices.md +27 -49
  207. package/skills/execute-work/references/design.md +271 -0
  208. package/skills/execute-work/references/diagnose.md +117 -0
  209. package/skills/execute-work/references/migration.md +108 -0
  210. package/skills/execute-work/references/simplify.md +65 -0
  211. package/skills/execute-work/scripts/self-review.mjs +422 -0
  212. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  213. package/skills/review-work/SKILL.md +94 -64
  214. package/skills/review-work/references/adjudication.md +94 -0
  215. package/skills/review-work/references/artifact.md +198 -0
  216. package/skills/review-work/references/evals-and-judges.md +66 -0
  217. package/skills/review-work/references/work.md +177 -0
  218. package/skills/review-work/scripts/check-brief.mjs +128 -0
  219. package/skills/review-work/scripts/review-packet.mjs +233 -0
  220. package/skills/setup-freeflow/SKILL.md +69 -16
  221. package/skills/track-work/SKILL.md +135 -151
  222. package/skills/track-work/references/handoff.md +190 -0
  223. package/skills/track-work/references/projects.md +51 -0
  224. package/skills/track-work/references/working-record-format.md +201 -344
  225. package/skills/track-work/scripts/lib/document.mjs +79 -19
  226. package/skills/track-work/scripts/lib/format.mjs +18 -1
  227. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  228. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  229. package/skills/track-work/scripts/lib/search.mjs +40 -0
  230. package/skills/track-work/scripts/lib/store.mjs +53 -21
  231. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  232. package/skills/track-work/scripts/lib/views.mjs +50 -1
  233. package/skills/track-work/scripts/working-record.mjs +263 -22
  234. package/skills/verify-work/SKILL.md +64 -47
  235. package/skills/verify-work/references/feature-map.md +120 -0
  236. package/skills/verify-work/references/runtime-evidence.md +203 -0
  237. package/skills/verify-work/references/test-design.md +27 -6
  238. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  239. package/skills/write-docs/SKILL.md +171 -0
  240. package/skills/write-docs/references/documentation-shapes.md +184 -0
  241. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  242. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  243. package/skills/write-docs/scripts/docs-check.mjs +229 -0
  244. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  245. package/skills/write-skill/SKILL.md +165 -165
  246. package/skills/write-skill/references/activation-and-wording.md +143 -0
  247. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  248. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  249. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  250. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  251. package/skills/write-skill/scripts/skill-author.mjs +67 -8
  252. package/capabilities/context-virtualization/SKILL.md +0 -103
  253. package/capabilities/conversation-history/SKILL.md +0 -53
  254. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  255. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  256. package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -330
  257. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
  258. package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -60
  259. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  260. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  261. package/pi-extension/dist/context-virtualization/runtime.js +0 -378
  262. package/pi-extension/dist/context-virtualization/state.js +0 -57
  263. package/pi-extension/dist/context-virtualization/types.js +0 -10
  264. package/pi-extension/dist/conversation-history/passages.js +0 -66
  265. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  266. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  267. package/pi-extension/dist/conversation-history/search.js +0 -56
  268. package/pi-extension/dist/conversation-history/types.js +0 -1
  269. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  270. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  271. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  272. package/pi-extension/dist/freeflow-context/tool.js +0 -314
  273. package/pi-extension/dist/freeflow-context/types.js +0 -12
  274. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  275. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  276. package/pi-extension/dist/runtime/runtime-context.js +0 -754
  277. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  278. package/pi-extension/dist/session-sources/provenance.js +0 -32
  279. package/pi-extension/dist/settings/settings-ui.js +0 -1360
  280. package/pi-extension/dist/ui/text-component.js +0 -57
  281. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  282. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
  283. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
  284. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
  285. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
  286. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
  287. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
  288. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
  289. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
  290. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  291. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  292. package/pi-extension/src/context-virtualization/runtime.ts +0 -428
  293. package/pi-extension/src/context-virtualization/state.ts +0 -79
  294. package/pi-extension/src/context-virtualization/types.ts +0 -92
  295. package/pi-extension/src/conversation-history/passages.ts +0 -81
  296. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  297. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  298. package/pi-extension/src/conversation-history/search.ts +0 -79
  299. package/pi-extension/src/conversation-history/types.ts +0 -31
  300. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  301. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  302. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  303. package/pi-extension/src/freeflow-context/tool.ts +0 -366
  304. package/pi-extension/src/freeflow-context/types.ts +0 -70
  305. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  306. package/pi-extension/src/index.ts +0 -390
  307. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  308. package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
  309. package/pi-extension/src/provider-support/astra/history.ts +0 -145
  310. package/pi-extension/src/provider-support/astra/index.ts +0 -12
  311. package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
  312. package/pi-extension/src/provider-support/index.ts +0 -5
  313. package/pi-extension/src/runtime/request-history.ts +0 -243
  314. package/pi-extension/src/runtime/runtime-context.ts +0 -860
  315. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  316. package/pi-extension/src/session-sources/events.ts +0 -230
  317. package/pi-extension/src/session-sources/provenance.ts +0 -39
  318. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  319. package/pi-extension/src/session-sources/sources.ts +0 -334
  320. package/pi-extension/src/settings/settings-tui.ts +0 -501
  321. package/pi-extension/src/settings/settings-ui.ts +0 -1652
  322. package/pi-extension/src/ui/text-component.ts +0 -66
  323. package/pi-extension/tests/cache-reuse/history.test.js +0 -117
  324. package/pi-extension/tests/cache-reuse/native.test.js +0 -91
  325. package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
  326. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
  327. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  328. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
  329. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
  330. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  331. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
  332. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  333. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
  334. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
  335. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
  336. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  337. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
  338. package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
  339. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  340. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
  341. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  342. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  343. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  344. package/pi-extension/tests/fixtures/routing-native.js +0 -191
  345. package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
  346. package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
  347. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
  348. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  349. package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
  350. package/pi-extension/tests/provider-support/astra.test.js +0 -306
  351. package/pi-extension/tests/provider-support/cli.test.js +0 -92
  352. package/pi-extension/tests/provider-support/native.test.js +0 -183
  353. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  354. package/pi-extension/tsconfig.json +0 -16
  355. package/plugin.json +0 -33
  356. package/runtime/prompts/context-virtualization.md +0 -3
  357. package/runtime/prompts/conversation-history.md +0 -3
  358. package/skills/action-selection/SKILL.md +0 -100
  359. package/skills/action-selection/references/trajectory-stalls.md +0 -71
  360. package/skills/bypass/SKILL.md +0 -153
  361. package/skills/commit-work/SKILL.md +0 -145
  362. package/skills/commit-work/references/git-state-edges.md +0 -94
  363. package/skills/commit-work/references/simple-push.md +0 -92
  364. package/skills/commit-work/references/staging-decisions.md +0 -106
  365. package/skills/decision-gate/SKILL.md +0 -106
  366. package/skills/design-for-depth/SKILL.md +0 -117
  367. package/skills/design-for-depth/references/design-pressure-signals.md +0 -182
  368. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  369. package/skills/design-for-depth/references/interface-design-loop.md +0 -136
  370. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -147
  371. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  372. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -136
  373. package/skills/diagnose-failure/SKILL.md +0 -125
  374. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -48
  375. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  376. package/skills/discuss/SKILL.md +0 -116
  377. package/skills/evaluate-skill/SKILL.md +0 -219
  378. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -385
  379. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  380. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -90
  381. package/skills/evaluate-skill/references/review-and-revision.md +0 -92
  382. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -407
  383. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  384. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  385. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -368
  386. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -164
  387. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -102
  388. package/skills/execute-work/references/execute-work-edges.md +0 -68
  389. package/skills/finish-branch/SKILL.md +0 -117
  390. package/skills/finish-branch/references/integration-options.md +0 -80
  391. package/skills/handoff/SKILL.md +0 -82
  392. package/skills/handoff/references/handoff-templates.md +0 -150
  393. package/skills/launch-work/SKILL.md +0 -116
  394. package/skills/launch-work/references/launch-readiness.md +0 -85
  395. package/skills/migration-work/SKILL.md +0 -115
  396. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  397. package/skills/release-work/SKILL.md +0 -118
  398. package/skills/release-work/references/release-evidence.md +0 -78
  399. package/skills/review-artifact/SKILL.md +0 -142
  400. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  401. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  402. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  403. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  404. package/skills/review-work/references/security-risk-lens.md +0 -69
  405. package/skills/setup-freeflow/references/activation-contract.md +0 -109
  406. package/skills/setup-freeflow/references/host-setup.md +0 -67
  407. package/skills/simplify-code/SKILL.md +0 -85
  408. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  409. package/skills/verify-work/references/browser-runtime-evidence.md +0 -55
  410. package/skills/verify-work/references/integration-evidence.md +0 -47
  411. package/skills/verify-work/references/performance-evidence.md +0 -57
  412. package/skills/workflow/SKILL.md +0 -127
  413. package/skills/workflow/references/domain-skill-composition.md +0 -43
  414. package/skills/write-plan/SKILL.md +0 -92
  415. package/skills/write-plan/references/plan-shapes.md +0 -156
  416. package/skills/write-skill/references/activation-boundaries.md +0 -104
  417. package/skills/write-skill/references/agent-first-instructions.md +0 -121
  418. package/skills/write-skill/references/progressive-disclosure.md +0 -127
  419. package/skills/write-skill/references/skill-dependency-graphs.md +0 -190
  420. package/skills/write-spec/SKILL.md +0 -92
  421. package/skills/write-spec/references/artifact-standards.md +0 -86
  422. package/skills/write-spec/references/decision-records.md +0 -78
  423. package/skills/write-spec/references/spec-shapes.md +0 -143
  424. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
  425. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  426. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -0,0 +1,560 @@
1
+ // The shell tier: a body group may give its subject `bash` only through a declared isolation extension, and the
2
+ // evaluator never trusts the declaration. Before an arm's first subject it checks the isolation's prerequisites and
3
+ // runs the subject's own Pi, with the arm's extensions and a local scripted model, through probes that a confined
4
+ // shell must fail. Any probe that does not hold makes the group invalid. The evaluator picks no sandbox itself.
5
+
6
+ import { randomBytes } from "node:crypto";
7
+ import { access, constants, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
8
+ import http from "node:http";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+ import { execFile } from "node:child_process";
12
+ import { promisify } from "node:util";
13
+
14
+ import { SHELL_TOOLS } from "../guard-rules.mjs";
15
+ import { containerShell, containerTarget, missingContainerPrerequisites, startContainer } from "./container.mjs";
16
+ import { hostAdapter } from "./hosts/index.mjs";
17
+
18
+ const run = promisify(execFile);
19
+ const PROBE_TIMEOUT_MS = 10 * 60 * 1000;
20
+ const OUTPUT_TAIL = 2000;
21
+ // Planted in Pi's own environment; a confined shell must not see it.
22
+ export const PROBE_VARIABLE = "CONFINEMENT_PROBE";
23
+ const PUBLIC_URL_VARIABLE = "SKILL_EVAL_PROBE_PUBLIC_URL";
24
+ const DEFAULT_PUBLIC_URL = "https://example.com/";
25
+
26
+ /**
27
+ * Prerequisites an isolation may name in `shell.requires`. Each is a program on PATH (any of `programs`), or a
28
+ * minimum Node version for the `node` Pi runs on.
29
+ */
30
+ const PREREQUISITES = {
31
+ qemu: {
32
+ name: "QEMU",
33
+ programs: ["qemu-system-x86_64", "qemu-system-aarch64"],
34
+ install: {
35
+ macos: "brew install qemu",
36
+ apt: process.arch === "arm64" ? "sudo apt-get install qemu-system-arm" : "sudo apt-get install qemu-system-x86",
37
+ dnf: process.arch === "arm64" ? "sudo dnf install qemu-system-aarch64" : "sudo dnf install qemu-system-x86",
38
+ },
39
+ },
40
+ "node-23.6": {
41
+ name: "Node.js 23.6 or newer",
42
+ node: "23.6.0",
43
+ install: {
44
+ macos: "brew install node",
45
+ apt: "install Node.js 23.6 or newer from https://nodejs.org (distribution packages are usually older), or with nvm: nvm install 24",
46
+ dnf: "install Node.js 23.6 or newer from https://nodejs.org (distribution packages are usually older), or with nvm: nvm install 24",
47
+ },
48
+ },
49
+ };
50
+
51
+ /** Named sets of prerequisites, for isolations the evaluator knows. */
52
+ const PROFILES = {
53
+ gondolin: {
54
+ requires: ["qemu", "node-23.6"],
55
+ reason: "Gondolin runs each shell command in a local QEMU micro-VM, and its host library needs Node.js 23.6+",
56
+ download: "Gondolin downloads its guest image on first start (size not verified)",
57
+ },
58
+ };
59
+
60
+ function isPlainObject(value) {
61
+ return value !== null && typeof value === "object" && !Array.isArray(value);
62
+ }
63
+
64
+ /**
65
+ * Validates a configuration's `shell`: `{ isolation, network: "none", workspace?, requires? }`.
66
+ *
67
+ * @param {any} value
68
+ * @param {string} label
69
+ * @param {(code: string, message: string) => never} fail
70
+ */
71
+ export function validateShell(value, label, fail) {
72
+ if (!isPlainObject(value)) fail("invalid-definition", `${label} must be an object`);
73
+ for (const key of Object.keys(value)) {
74
+ if (!["isolation", "network", "workspace", "requires"].includes(key)) {
75
+ fail("invalid-definition", `${label} contains unsupported field ${key}`);
76
+ }
77
+ }
78
+ if (typeof value.isolation !== "string" || value.isolation.trim() === "") {
79
+ fail("invalid-definition", `${label}.isolation must name one of the arm's host.extensions`);
80
+ }
81
+ if (value.network !== "none") fail("invalid-definition", `${label}.network must be "none"`);
82
+ if (Object.hasOwn(value, "workspace")) {
83
+ const workspace = value.workspace;
84
+ if (
85
+ typeof workspace !== "string" ||
86
+ !workspace.startsWith("/") ||
87
+ workspace === "/" ||
88
+ workspace.endsWith("/") ||
89
+ workspace
90
+ .split("/")
91
+ .slice(1)
92
+ .some((part) => part === "" || part === "." || part === "..")
93
+ ) {
94
+ fail("invalid-definition", `${label}.workspace must be an absolute path such as /workspace`);
95
+ }
96
+ }
97
+ if (Object.hasOwn(value, "requires")) {
98
+ if (!Array.isArray(value.requires)) fail("invalid-definition", `${label}.requires must be an array`);
99
+ for (const [index, entry] of value.requires.entries()) {
100
+ if (typeof entry === "string") {
101
+ if (!Object.hasOwn(PROFILES, entry) && !Object.hasOwn(PREREQUISITES, entry)) {
102
+ fail(
103
+ "invalid-definition",
104
+ `${label}.requires[${index}] names an unknown prerequisite ${entry}; known: ${[...Object.keys(PROFILES), ...Object.keys(PREREQUISITES)].join(", ")}`,
105
+ );
106
+ }
107
+ continue;
108
+ }
109
+ validateCustomPrerequisite(entry, `${label}.requires[${index}]`, fail);
110
+ }
111
+ }
112
+ }
113
+
114
+ function validateCustomPrerequisite(entry, label, fail) {
115
+ if (!isPlainObject(entry)) fail("invalid-definition", `${label} must be a known name or an object`);
116
+ for (const key of Object.keys(entry)) {
117
+ if (!["name", "programs", "reason", "install", "download"].includes(key)) {
118
+ fail("invalid-definition", `${label} contains unsupported field ${key}`);
119
+ }
120
+ }
121
+ if (typeof entry.name !== "string" || entry.name === "") fail("invalid-definition", `${label}.name must be a string`);
122
+ if (
123
+ !Array.isArray(entry.programs) ||
124
+ entry.programs.length === 0 ||
125
+ !entry.programs.every((program) => typeof program === "string" && /^[A-Za-z0-9._+-]+$/.test(program))
126
+ ) {
127
+ fail("invalid-definition", `${label}.programs must list program names, any one of which satisfies it`);
128
+ }
129
+ for (const key of ["reason", "download"]) {
130
+ if (Object.hasOwn(entry, key) && typeof entry[key] !== "string") {
131
+ fail("invalid-definition", `${label}.${key} must be a string`);
132
+ }
133
+ }
134
+ if (Object.hasOwn(entry, "install")) {
135
+ if (!isPlainObject(entry.install)) fail("invalid-definition", `${label}.install must be an object`);
136
+ for (const [key, command] of Object.entries(entry.install)) {
137
+ if (!["macos", "apt", "dnf"].includes(key) || typeof command !== "string") {
138
+ fail("invalid-definition", `${label}.install may give macos, apt and dnf commands as strings`);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ /** A host extension entry as `shell.isolation` names it: the package name, or the source or working-tree path. */
145
+ export function isolationLabel(entry) {
146
+ if (typeof entry?.package === "string") return entry.package;
147
+ if (isPlainObject(entry?.source)) return entry.source.path;
148
+ return entry?.path;
149
+ }
150
+
151
+ /**
152
+ * Group-level rules once any arm declares `shell`: `bash` in the tools, a body group, and every arm's shell isolated
153
+ * by one of that arm's host extensions. A group that declares no shell keeps the run-time refusal of `bash`.
154
+ *
155
+ * @param {{ group: any, names: string[], configurations: Record<string, any>, fail: (code: string, message: string) => never }} options
156
+ */
157
+ export function validateShellGroup({ group, names, configurations, fail }) {
158
+ for (const arm of names) {
159
+ if (configurations[arm].shell !== undefined && configurations[arm].environment !== undefined) {
160
+ fail(
161
+ "invalid-definition",
162
+ `arm ${arm} declares both shell and a container environment, which provides the shell`,
163
+ );
164
+ }
165
+ }
166
+ const containers = names.filter((arm) => configurations[arm].environment !== undefined);
167
+ if (containers.length > 0 && group.type !== "body") {
168
+ fail("invalid-definition", "a container environment is supported only in body groups");
169
+ }
170
+ const declared = names.filter((arm) => configurations[arm].shell !== undefined);
171
+ if (declared.length === 0 && !(group.tools.includes("bash") && containers.length > 0)) return;
172
+ if (!group.tools.includes("bash")) {
173
+ fail("invalid-definition", `arm ${declared[0]} declares shell, so group.tools must include bash`);
174
+ }
175
+ if (group.type !== "body") fail("invalid-definition", "bash is supported only in body groups");
176
+ for (const arm of names) {
177
+ const shell = configurations[arm].shell ?? containerShell(configurations[arm].environment);
178
+ if (shell === undefined) {
179
+ fail(
180
+ "invalid-definition",
181
+ `bash needs a confined shell: arm ${arm} must declare configuration.shell with an isolation extension, or a container environment`,
182
+ );
183
+ }
184
+ if (shell.container !== undefined) continue;
185
+ const labels = (configurations[arm].host.extensions ?? []).map(isolationLabel);
186
+ if (!labels.includes(shell.isolation)) {
187
+ fail(
188
+ "invalid-definition",
189
+ `arm ${arm}'s shell.isolation ${shell.isolation} is not among its host.extensions (${labels.join(", ") || "none"})`,
190
+ );
191
+ }
192
+ }
193
+ }
194
+
195
+ /** The prerequisites a shell declares, expanded from profiles, with the reasons and downloads they carry. */
196
+ function declaredPrerequisites(shell) {
197
+ const entries = [];
198
+ for (const entry of shell.requires ?? []) {
199
+ if (typeof entry !== "string") {
200
+ entries.push({ ...entry, reason: entry.reason ?? null, download: entry.download ?? null });
201
+ continue;
202
+ }
203
+ const profile = PROFILES[entry];
204
+ const names = profile === undefined ? [entry] : profile.requires;
205
+ for (const name of names) {
206
+ entries.push({ ...PREREQUISITES[name], reason: profile?.reason ?? null, download: profile?.download ?? null });
207
+ }
208
+ }
209
+ return entries;
210
+ }
211
+
212
+ /**
213
+ * The declared prerequisites that are missing on this machine, each with why it is needed and how to install it on
214
+ * this operating system.
215
+ *
216
+ * @returns {Promise<Array<{ name: string, need: string, found: string | null, reason: string | null, install: string | null, download: string | null }>>}
217
+ */
218
+ export async function missingPrerequisites(shell, { environment = process.env } = {}) {
219
+ const missing = [];
220
+ const manager = await packageManager(environment);
221
+ for (const prerequisite of declaredPrerequisites(shell)) {
222
+ let need;
223
+ let found = null;
224
+ let satisfied;
225
+ if (prerequisite.node !== undefined) {
226
+ need = `node ${prerequisite.node} or newer on PATH`;
227
+ found = await nodeVersion(environment);
228
+ satisfied = found !== null && compareVersions(found, prerequisite.node) >= 0;
229
+ if (found !== null) found = `node ${found}`;
230
+ } else {
231
+ need = `${prerequisite.programs.join(" or ")} on PATH`;
232
+ satisfied = (await Promise.all(prerequisite.programs.map((program) => onPath(program, environment)))).some(
233
+ Boolean,
234
+ );
235
+ }
236
+ if (satisfied) continue;
237
+ missing.push({
238
+ name: prerequisite.name,
239
+ need,
240
+ found,
241
+ reason: prerequisite.reason,
242
+ install: manager === null ? null : (prerequisite.install?.[manager] ?? null),
243
+ download: prerequisite.download,
244
+ });
245
+ }
246
+ return missing;
247
+ }
248
+
249
+ /** The refusal the agent relays to the user: what is missing, why, how to install it, and that it should ask first. */
250
+ export function prerequisiteRefusal(groupId, arm, shell, missing) {
251
+ const what =
252
+ shell.container === undefined
253
+ ? `declares a shell isolated by ${shell.isolation}`
254
+ : `runs in the container image ${shell.container.image}`;
255
+ const lines = [`group ${groupId} cannot run: arm ${arm} ${what}, and this machine is missing:`];
256
+ for (const entry of missing) {
257
+ lines.push(`- ${entry.name}: needs ${entry.need}${entry.found ? ` (found ${entry.found})` : " (not found)"}.`);
258
+ if (entry.reason) lines.push(` Why: ${entry.reason}.`);
259
+ lines.push(
260
+ ` Install: ${entry.install ?? "no install command is known for this system; see the isolation's documentation"}`,
261
+ );
262
+ if (entry.download) lines.push(` Download: ${entry.download}.`);
263
+ }
264
+ lines.push("Ask the user before installing anything, then run again.");
265
+ return lines.join("\n");
266
+ }
267
+
268
+ export async function packageManager(environment) {
269
+ if (process.platform === "darwin") return "macos";
270
+ if (process.platform !== "linux") return null;
271
+ if (await onPath("apt-get", environment)) return "apt";
272
+ if (await onPath("dnf", environment)) return "dnf";
273
+ return null;
274
+ }
275
+
276
+ async function onPath(program, environment) {
277
+ for (const directory of (environment.PATH ?? "").split(path.delimiter).filter(Boolean)) {
278
+ try {
279
+ await access(path.join(directory, program), constants.X_OK);
280
+ return true;
281
+ } catch {
282
+ // Not in this directory.
283
+ }
284
+ }
285
+ return false;
286
+ }
287
+
288
+ async function nodeVersion(environment) {
289
+ try {
290
+ const { stdout } = await run("node", ["--version"], { env: environment, timeout: 10000 });
291
+ return stdout.trim().replace(/^v/, "");
292
+ } catch {
293
+ return null;
294
+ }
295
+ }
296
+
297
+ function compareVersions(left, right) {
298
+ const parse = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
299
+ const [a, b] = [parse(left), parse(right)];
300
+ for (let index = 0; index < 3; index += 1) {
301
+ if ((a[index] ?? 0) !== (b[index] ?? 0)) return (a[index] ?? 0) - (b[index] ?? 0);
302
+ }
303
+ return 0;
304
+ }
305
+
306
+ /** A failure that makes a group invalid, with structured details for its runs' evidence. */
307
+ export class ShellError extends Error {
308
+ constructor(message, details) {
309
+ super(message);
310
+ this.name = "ShellError";
311
+ this.details = details;
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Checks prerequisites and verifies confinement for every selected arm that declares a shell, before any subject
317
+ * starts. Returns each verified arm's confinement record; throws a ShellError when a prerequisite is missing or a
318
+ * probe does not hold.
319
+ *
320
+ * @param {{ group: any, variants: Record<string, string>, hostExtensions: Record<string, any[]>, groupDirectory: string, signal?: AbortSignal }} options
321
+ */
322
+ export async function prepareShell({ group, variants, hostExtensions, groupDirectory, signal }) {
323
+ const confinement = {};
324
+ for (const [arm, state] of Object.entries(variants)) {
325
+ const shell = group.variants[arm]?.shell;
326
+ if (state !== "selected" || shell === undefined) continue;
327
+ const missing =
328
+ shell.container === undefined
329
+ ? await missingPrerequisites(shell)
330
+ : await missingContainerPrerequisites(shell.container, await packageManager(process.env), groupDirectory);
331
+ if (missing.length > 0) {
332
+ throw new ShellError(prerequisiteRefusal(group.id, arm, shell, missing), {
333
+ kind: "shell-prerequisites",
334
+ arm,
335
+ isolation: shell.isolation,
336
+ missing,
337
+ });
338
+ }
339
+ // A container without a shell tool (bash, or Freeflow's background commands, which run in the same shell) gives the
340
+ // subject no shell to probe: it hosts only declared commands and graders.
341
+ if (shell.container !== undefined && !group.tools.some((tool) => SHELL_TOOLS.has(tool))) continue;
342
+ confinement[arm] = await verifyConfinement({
343
+ arm,
344
+ host: group.host ?? "pi",
345
+ shell,
346
+ extensions: hostExtensions[arm] ?? [],
347
+ directory: path.join(groupDirectory, "confinement", arm),
348
+ groupDirectory,
349
+ signal,
350
+ });
351
+ if (!confinement[arm].verified) {
352
+ const failed = confinement[arm].probes.filter((probe) => !probe.held).map((probe) => probe.id);
353
+ throw new ShellError(
354
+ `arm ${arm}'s shell isolated by ${shell.isolation} is not confined: ${failed.join(", ")} did not hold (see confinement in the run evidence)`,
355
+ { kind: "shell-confinement", arm, confinement },
356
+ );
357
+ }
358
+ }
359
+ return confinement;
360
+ }
361
+
362
+ /**
363
+ * Runs the probes for one arm through its own host and extensions, with a local scripted model, and judges each by
364
+ * what the host can observe: files on the host, connections to a host listener, and the probe output.
365
+ */
366
+ export async function verifyConfinement({ arm, host = "pi", shell, extensions, directory, groupDirectory, signal }) {
367
+ await rm(directory, { recursive: true, force: true });
368
+ const workspace = path.join(directory, "workspace");
369
+ const outside = path.join(directory, "outside");
370
+ await mkdir(workspace, { recursive: true });
371
+ await mkdir(outside, { recursive: true });
372
+ const nonce = randomBytes(12).toString("hex");
373
+ const adapter = hostAdapter(host);
374
+ const isolation = await adapter.prepareIsolation(directory, null, { settings: adapter.effectiveSettings({}) });
375
+ // The isolated HOME's parent holds a marker the shell must not read, and so does the directory where the host keeps
376
+ // its login: a shell that can read there could read a subject's copied login.
377
+ const marker = path.join(isolation.evidence.root, "probe-marker");
378
+ await writeFile(marker, `${nonce}\n`);
379
+ const loginMarker = path.join(isolation.loginDirectory ?? isolation.evidence.root, "probe-login-marker");
380
+ await writeFile(loginMarker, `${nonce}\n`);
381
+ const written = path.join(outside, `written-${nonce}`);
382
+
383
+ const listener = await startListener(nonce);
384
+ const publicUrl = Object.hasOwn(process.env, PUBLIC_URL_VARIABLE)
385
+ ? process.env[PUBLIC_URL_VARIABLE]
386
+ : DEFAULT_PUBLIC_URL;
387
+ const urls = [listener.url, ...(publicUrl ? [publicUrl] : [])];
388
+ const probes = [
389
+ {
390
+ id: "workspace-write",
391
+ purpose: "control: the shell runs and writes inside the workspace",
392
+ command: `printf '%s' ${nonce} > confinement-control && cat confinement-control`,
393
+ },
394
+ {
395
+ id: "write-outside",
396
+ purpose: "writing a host file outside the workspace must fail",
397
+ command: `printf '%s' ${nonce} > ${quote(written)}; echo "exit=$?"`,
398
+ },
399
+ {
400
+ id: "read-home-parent",
401
+ purpose: "reading a marker beside the isolated home must fail",
402
+ command: `cat ${quote(marker)}; echo "exit=$?"`,
403
+ },
404
+ {
405
+ id: "network",
406
+ purpose: "reaching the network must fail",
407
+ command: networkCommand(urls),
408
+ },
409
+ {
410
+ id: "environment",
411
+ purpose: `the ${PROBE_VARIABLE} variable planted in ${adapter.label}'s environment must be absent`,
412
+ command: `printenv ${PROBE_VARIABLE} || echo unset`,
413
+ },
414
+ {
415
+ id: "read-login",
416
+ purpose: `reading a marker in ${adapter.label}'s login directory must fail`,
417
+ command: `cat ${quote(loginMarker)}; echo "exit=$?"`,
418
+ },
419
+ ];
420
+
421
+ // A container shell is probed in its own container, started on the probe workspace like a subject's.
422
+ let container = null;
423
+ if (shell.container !== undefined) {
424
+ try {
425
+ container = await startContainer({ container: shell.container, workspace, purpose: "probe" });
426
+ } catch (error) {
427
+ await listener.close();
428
+ throw error;
429
+ }
430
+ }
431
+ // The guard's contract, as for a subject: only bash, confined to the probe workspace.
432
+ const guard = {
433
+ PWD: workspace,
434
+ SKILL_EVAL_ALLOWED_ROOTS: JSON.stringify([workspace]),
435
+ SKILL_EVAL_WRITABLE_ROOT: workspace,
436
+ SKILL_EVAL_ALLOWED_TOOLS: JSON.stringify(["bash"]),
437
+ SKILL_EVAL_CONTEXT_MANIFEST: "",
438
+ SKILL_EVAL_HOST: host,
439
+ SKILL_EVAL_PROMPT_MODE: "isolated",
440
+ SKILL_EVAL_SHELL: JSON.stringify(shell),
441
+ [PROBE_VARIABLE]: nonce,
442
+ ...(container === null ? {} : { SKILL_EVAL_CONTAINER: containerTarget(container) }),
443
+ };
444
+ const timeout = AbortSignal.timeout(PROBE_TIMEOUT_MS);
445
+ const eventsFile = path.join(directory, "events.jsonl");
446
+ const stderrFile = path.join(directory, "stderr.log");
447
+ let probed = { observation: null, launchError: null, results: new Map() };
448
+ let launchError = null;
449
+ try {
450
+ probed = await adapter.probeShell({
451
+ probes,
452
+ workspace,
453
+ isolation,
454
+ extensions,
455
+ guard,
456
+ eventsFile,
457
+ stderrFile,
458
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
459
+ });
460
+ } catch (error) {
461
+ launchError = error instanceof Error ? error.message : String(error);
462
+ } finally {
463
+ await Promise.all([listener.close(), container?.remove()]);
464
+ }
465
+
466
+ const { observation, results } = probed;
467
+ launchError ??= probed.launchError;
468
+ const judged = probes.map((probe) => ({ ...probe, ...results.get(probe.id) }));
469
+ const control = await readFile(path.join(workspace, "confinement-control"), "utf8").catch(() => null);
470
+ const leakedWrite = await stat(written).then(
471
+ () => true,
472
+ () => false,
473
+ );
474
+ const held = {
475
+ "workspace-write": control === nonce,
476
+ "write-outside": !leakedWrite,
477
+ "read-home-parent": !includes(judged[2], nonce),
478
+ network: listener.connections === 0 && !/REACHED:(?!000)\S+/.test(judged[3].output ?? ""),
479
+ environment: !includes(judged[4], nonce),
480
+ "read-login": !includes(judged[5], nonce),
481
+ };
482
+ const record = {
483
+ verified: false,
484
+ isolation: shell.isolation,
485
+ network: shell.network,
486
+ directory: path.relative(groupDirectory, directory).split(path.sep).join("/"),
487
+ probes: judged.map((probe) => ({
488
+ id: probe.id,
489
+ purpose: probe.purpose,
490
+ command: probe.command,
491
+ ran: probe.ran === true,
492
+ held: probe.ran === true && held[probe.id],
493
+ isError: probe.isError ?? null,
494
+ output: (probe.output ?? "").slice(-OUTPUT_TAIL),
495
+ })),
496
+ networkConnections: listener.connections,
497
+ // The host process that ran the probes, recorded under the host's name.
498
+ [host]: {
499
+ exitCode: observation?.exitCode ?? null,
500
+ terminationReason: observation?.terminationReason ?? (timeout.aborted ? "timeout" : null),
501
+ error: launchError,
502
+ stderr: (await readFile(stderrFile, "utf8").catch(() => "")).slice(-OUTPUT_TAIL),
503
+ },
504
+ };
505
+ record.verified = record.probes.every((probe) => probe.held);
506
+ await writeFile(path.join(directory, "confinement.json"), `${JSON.stringify(record, null, 2)}\n`);
507
+ return record;
508
+ }
509
+
510
+ function includes(probe, nonce) {
511
+ return (probe.output ?? "").includes(nonce);
512
+ }
513
+
514
+ function quote(value) {
515
+ return `'${String(value).replaceAll("'", `'\\''`)}'`;
516
+ }
517
+
518
+ /** Tries each URL with whichever HTTP client the shell has, printing REACHED:<status> only on a response. */
519
+ function networkCommand(urls) {
520
+ const lines = urls.map(
521
+ (url) =>
522
+ `if command -v curl >/dev/null 2>&1; then curl -sS -m 8 -o /dev/null -w 'REACHED:%{http_code}\\n' ${quote(url)}; ` +
523
+ `elif command -v wget >/dev/null 2>&1; then wget -q -T 8 -O /dev/null ${quote(url)} && echo REACHED:ok; ` +
524
+ `else echo NO-HTTP-CLIENT; fi`,
525
+ );
526
+ return lines.join("; ");
527
+ }
528
+
529
+ /** An HTTP listener on every interface, reached through the host's first external IPv4 address when it has one. */
530
+ function startListener(nonce) {
531
+ return new Promise((resolve, reject) => {
532
+ const state = { connections: 0 };
533
+ const server = http.createServer((request, response) => {
534
+ response.end(nonce);
535
+ });
536
+ server.on("connection", () => {
537
+ state.connections += 1;
538
+ });
539
+ server.once("error", reject);
540
+ server.listen(0, "0.0.0.0", () => {
541
+ const address = externalAddress() ?? "127.0.0.1";
542
+ resolve({
543
+ url: `http://${address}:${server.address().port}/${nonce}`,
544
+ get connections() {
545
+ return state.connections;
546
+ },
547
+ close: () => new Promise((done) => server.close(() => done())),
548
+ });
549
+ });
550
+ });
551
+ }
552
+
553
+ function externalAddress() {
554
+ for (const entries of Object.values(os.networkInterfaces())) {
555
+ for (const entry of entries ?? []) {
556
+ if (entry.family === "IPv4" && !entry.internal) return entry.address;
557
+ }
558
+ }
559
+ return null;
560
+ }
@@ -0,0 +1,64 @@
1
+ // `--source <arm>=<commit>`: for one run only, every Git source ref an arm declares (its subject source, its
2
+ // Freeflow source, and its source-built host extensions) is replaced by a commit. The definition files never
3
+ // change; the override is recorded in invocation.json and on each run, and it reaches the run's recorded source
4
+ // identity and its reuse key, because both read the effective configuration. An arm with no Git source refuses it.
5
+
6
+ import { ARM_NAME } from "./arms.mjs";
7
+
8
+ const COMMIT = /^[0-9a-f]{7,64}$/;
9
+
10
+ /**
11
+ * Parses `<arm>=<commit>` values into `{ <arm>: <commit> }`.
12
+ *
13
+ * @param {string[]} values
14
+ */
15
+ export function parseSourceOverrides(values) {
16
+ const sources = {};
17
+ for (const value of values) {
18
+ const match = /^([^=]+)=(.+)$/.exec(value);
19
+ if (match === null || !ARM_NAME.test(match[1]) || !COMMIT.test(match[2])) {
20
+ throw new Error(`--source must be <arm>=<commit SHA>, such as candidate=3415a8a2: ${value}`);
21
+ }
22
+ if (Object.hasOwn(sources, match[1])) throw new Error(`--source ${match[1]} may be supplied only once`);
23
+ sources[match[1]] = match[2];
24
+ }
25
+ return sources;
26
+ }
27
+
28
+ /**
29
+ * Applies source overrides to a selection's groups in place, before anything runs. Every overridden arm must be
30
+ * an arm of each selected group and declare at least one Git source there.
31
+ *
32
+ * @param {{groups: Array<{group: any}>}} selection
33
+ * @param {Record<string, string> | null | undefined} sources
34
+ */
35
+ export function applySourceOverrides(selection, sources) {
36
+ if (!sources || Object.keys(sources).length === 0) return;
37
+ for (const [arm, commit] of Object.entries(sources)) {
38
+ for (const { group } of selection.groups) {
39
+ const variant = group.variants?.[arm];
40
+ if (variant === undefined) throw new Error(`--source ${arm}: ${arm} is not an arm of group ${group.id}`);
41
+ const configuration = group.configurations?.[arm];
42
+ let replaced = 0;
43
+ if (variant.source?.kind === "git") {
44
+ variant.source = { ...variant.source, ref: commit };
45
+ if (configuration?.subject?.source?.kind === "git") configuration.subject.source.ref = commit;
46
+ replaced += 1;
47
+ }
48
+ if (variant.freeflow?.source?.kind === "git") {
49
+ variant.freeflow = { ...variant.freeflow, source: { ...variant.freeflow.source, ref: commit } };
50
+ if (configuration?.freeflow?.source) configuration.freeflow.source.ref = commit;
51
+ replaced += 1;
52
+ }
53
+ variant.hostExtensions = (variant.hostExtensions ?? []).map((extension) => {
54
+ if (extension?.source?.kind !== "git") return extension;
55
+ replaced += 1;
56
+ return { ...extension, source: { ...extension.source, ref: commit } };
57
+ });
58
+ if (configuration?.host?.extensions) configuration.host.extensions = variant.hostExtensions;
59
+ if (replaced === 0) {
60
+ throw new Error(`--source ${arm}: arm ${arm} of group ${group.id} declares no Git source to override`);
61
+ }
62
+ }
63
+ }
64
+ }