@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
@@ -30,7 +30,14 @@ export async function prepareFixture({ declaredPath, root, groupDirectory }) {
30
30
  const files = await fingerprintDirectory(sourcePath);
31
31
  const snapshotPath = path.join(groupDirectory, "resources", "fixture");
32
32
  await mkdir(path.dirname(snapshotPath), { recursive: true });
33
- await cp(sourcePath, snapshotPath, { recursive: true, dereference: false, errorOnExist: true, force: false });
33
+ // Links are copied as they are: Node otherwise rewrites a relative link into an absolute one into the source.
34
+ await cp(sourcePath, snapshotPath, {
35
+ recursive: true,
36
+ dereference: false,
37
+ verbatimSymlinks: true,
38
+ errorOnExist: true,
39
+ force: false,
40
+ });
34
41
  const snapshotFiles = await fingerprintDirectory(await realpath(snapshotPath));
35
42
  const sourceFilesAfter = await fingerprintDirectory(sourcePath);
36
43
  if (
@@ -57,11 +64,14 @@ export async function materializeFixture({ fixture, workspace }) {
57
64
  await cp(fixture.snapshotPath, workspace, {
58
65
  recursive: true,
59
66
  dereference: false,
67
+ verbatimSymlinks: true,
60
68
  errorOnExist: true,
61
69
  force: false,
62
70
  });
63
71
  const materializedPath = await realpath(workspace);
64
- const materializedFiles = await fingerprintDirectory(materializedPath);
72
+ // A workspace may hold links a subject made (an earlier stage's workspace), recorded rather than followed; a
73
+ // declared fixture's links were already checked when it was snapshotted.
74
+ const materializedFiles = await fingerprintDirectory(materializedPath, { links: "record" });
65
75
  if (JSON.stringify(materializedFiles) !== JSON.stringify(fixture.files)) {
66
76
  throw new Error(`declared fixture changed while it was being copied: ${fixture.declaredPath}`);
67
77
  }
@@ -110,6 +120,7 @@ export async function prepareRuntime({ runtime, root, groupDirectory }) {
110
120
  return {
111
121
  host: runtime.host,
112
122
  session: runtime.session,
123
+ prompt: runtime.prompt ?? "isolated",
113
124
  environment: { ...runtime.environment },
114
125
  extensions: bundles,
115
126
  };
@@ -125,6 +136,7 @@ export async function materializeRuntime({ runtime, variantDirectory }) {
125
136
  await cp(bundle.root, destination, {
126
137
  recursive: true,
127
138
  dereference: false,
139
+ verbatimSymlinks: true,
128
140
  errorOnExist: true,
129
141
  force: false,
130
142
  });
@@ -145,6 +157,7 @@ export async function materializeRuntime({ runtime, variantDirectory }) {
145
157
  return {
146
158
  host: runtime.host,
147
159
  session: runtime.session,
160
+ prompt: runtime.prompt ?? "isolated",
148
161
  environment: { ...runtime.environment },
149
162
  extensions: bundles,
150
163
  };
@@ -226,13 +239,84 @@ export async function materializeEnvironment({ environment, root, variantDirecto
226
239
  };
227
240
  }
228
241
 
242
+ /**
243
+ * Snapshots each of the group's two arms' target skill into `destinationRoot/<arm>` and checks that the two differ
244
+ * only in the frontmatter description: every other file, and SKILL.md outside its description, must be
245
+ * byte-identical. Throws with the first difference found.
246
+ */
247
+ export async function verifyDescriptionOnlyRevision({ variants, root, destinationRoot }) {
248
+ const canonicalRoot = await realpath(root);
249
+ const snapshots = {};
250
+ const [reference, revised] = Object.keys(variants);
251
+ for (const variant of [reference, revised]) {
252
+ const environment = variants[variant];
253
+ const declaredPath = environment.skills[environment.target];
254
+ const destination = path.join(destinationRoot, variant);
255
+ if (environment.source.kind === "working-tree") {
256
+ snapshots[variant] = await snapshotWorkingTreeResource({
257
+ canonicalRoot,
258
+ declaredPath,
259
+ destination,
260
+ requireDirectory: true,
261
+ label: "skill",
262
+ });
263
+ } else {
264
+ const git = await prepareGitSource(canonicalRoot, environment.source.ref);
265
+ snapshots[variant] = await snapshotGitResource({
266
+ ...git,
267
+ declaredPath,
268
+ destination,
269
+ requireDirectory: true,
270
+ label: "skill",
271
+ });
272
+ }
273
+ }
274
+ const baselineFiles = new Map(snapshots[reference].files.map((file) => [file.path, file]));
275
+ const candidateFiles = new Map(snapshots[revised].files.map((file) => [file.path, file]));
276
+ for (const filePath of new Set([...baselineFiles.keys(), ...candidateFiles.keys()])) {
277
+ if (filePath === "SKILL.md") continue;
278
+ const baselineFile = baselineFiles.get(filePath);
279
+ const candidateFile = candidateFiles.get(filePath);
280
+ if (!baselineFile || !candidateFile || baselineFile.sha256 !== candidateFile.sha256) {
281
+ throw new Error(`description-only revision changes ${filePath} outside SKILL.md`);
282
+ }
283
+ }
284
+ const [baselineSkill, candidateSkill] = await Promise.all(
285
+ [snapshots[reference], snapshots[revised]].map((snapshot) =>
286
+ readFile(path.join(snapshot.path, "SKILL.md"), "utf8"),
287
+ ),
288
+ );
289
+ if (withoutDescription(baselineSkill) !== withoutDescription(candidateSkill)) {
290
+ throw new Error("description-only revision changes SKILL.md outside its frontmatter description");
291
+ }
292
+ }
293
+
294
+ // Removes the frontmatter `description` entry (one line, or a block scalar with its indented continuation).
295
+ function withoutDescription(text) {
296
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/.exec(text);
297
+ if (!match) return text;
298
+ const kept = [];
299
+ let skipping = false;
300
+ for (const line of match[1].split(/\r?\n/)) {
301
+ if (/^description\s*:/.test(line)) {
302
+ skipping = true;
303
+ continue;
304
+ }
305
+ if (skipping && /^\s+\S/.test(line)) continue;
306
+ skipping = false;
307
+ kept.push(line);
308
+ }
309
+ return `---\n${kept.join("\n")}\n---${match[2]}${text.slice(match[0].length)}`;
310
+ }
311
+
229
312
  export async function verifyEnvironment(environment) {
230
313
  for (const [label, resources] of [
231
314
  ["skill", environment.skills],
232
315
  ["context", environment.context],
233
316
  ]) {
234
317
  for (const resource of resources) {
235
- const current = await fingerprintDirectory(resource.path);
318
+ // An earlier stage's delivered work may hold links its subject made; it is checked the way it was recorded.
319
+ const current = await fingerprintDirectory(resource.path, { links: resource.links ?? "strict" });
236
320
  if (JSON.stringify(current) !== JSON.stringify(resource.files)) {
237
321
  throw new Error(`declared ${label} changed during subject execution: ${resource.declaredPath}`);
238
322
  }
@@ -251,9 +335,12 @@ export async function snapshotWorkspace({ workspace, variantDirectory, turn, bef
251
335
  await rm(snapshotPath, { recursive: true, force: true });
252
336
  await mkdir(path.dirname(snapshotPath), { recursive: true });
253
337
 
254
- const sourceBefore = await fingerprintDirectory(workspace);
338
+ const sourceBefore = await fingerprintDirectory(workspace, WORKSPACE);
255
339
  await cp(workspace, snapshotPath, { recursive: true, force: false, errorOnExist: true, verbatimSymlinks: true });
256
- const [sourceAfter, files] = await Promise.all([fingerprintDirectory(workspace), fingerprintDirectory(snapshotPath)]);
340
+ const [sourceAfter, files] = await Promise.all([
341
+ fingerprintDirectory(workspace, WORKSPACE),
342
+ fingerprintDirectory(snapshotPath, WORKSPACE),
343
+ ]);
257
344
  if (
258
345
  JSON.stringify(sourceBefore) !== JSON.stringify(sourceAfter) ||
259
346
  JSON.stringify(sourceAfter) !== JSON.stringify(files)
@@ -263,10 +350,22 @@ export async function snapshotWorkspace({ workspace, variantDirectory, turn, bef
263
350
  return { path: snapshotPath, files, changes: workspaceChanges(before, files) };
264
351
  }
265
352
 
266
- export async function fingerprintDirectory(root) {
353
+ /** How a subject's workspace is fingerprinted: its links are the subject's own work, recorded and never followed. */
354
+ export const WORKSPACE = Object.freeze({ links: "record" });
355
+
356
+ /**
357
+ * Every entry under `root`, sorted: files and links with a SHA-256, directories, and each link's target. A declared
358
+ * resource (`links: "strict"`, the default) may hold only relative links that stay inside it. A subject's workspace
359
+ * (`links: "record"`) keeps any link the subject made, such as one an installer pointed at the container's
360
+ * workdir: it is recorded with its target, as a link, and nothing outside is ever read through it.
361
+ *
362
+ * @param {string} root
363
+ * @param {{ links?: "strict" | "record" }} [options]
364
+ */
365
+ export async function fingerprintDirectory(root, { links = "strict" } = {}) {
267
366
  const canonicalRoot = await realpath(root);
268
367
  const files = [];
269
- await walk(canonicalRoot, canonicalRoot, files);
368
+ await walk(canonicalRoot, canonicalRoot, files, links);
270
369
  return files.sort((left, right) => comparePaths(left.path, right.path));
271
370
  }
272
371
 
@@ -302,7 +401,13 @@ async function snapshotWorkingTreeResource({ canonicalRoot, declaredPath, destin
302
401
  : [{ path: path.basename(sourcePath), type: "file", sha256: sha256(await readFile(sourcePath)) }];
303
402
  if (sourceStat.isDirectory()) {
304
403
  await mkdir(path.dirname(destination), { recursive: true });
305
- await cp(sourcePath, destination, { recursive: true, dereference: false, errorOnExist: true, force: false });
404
+ await cp(sourcePath, destination, {
405
+ recursive: true,
406
+ dereference: false,
407
+ verbatimSymlinks: true,
408
+ errorOnExist: true,
409
+ force: false,
410
+ });
306
411
  } else {
307
412
  await mkdir(destination, { recursive: true });
308
413
  await copyFile(sourcePath, path.join(destination, path.basename(sourcePath)));
@@ -460,7 +565,7 @@ async function contextFiles(root, fingerprints) {
460
565
  return files;
461
566
  }
462
567
 
463
- async function walk(root, directory, files) {
568
+ async function walk(root, directory, files, links) {
464
569
  const entries = await readdir(directory, { withFileTypes: true });
465
570
  entries.sort((left, right) => comparePaths(left.name, right.name));
466
571
  for (const entry of entries) {
@@ -469,14 +574,16 @@ async function walk(root, directory, files) {
469
574
  const entryStat = await lstat(entryPath);
470
575
  if (entryStat.isSymbolicLink()) {
471
576
  const target = await readlink(entryPath);
472
- const canonicalTarget = await realpath(entryPath).catch(() => null);
473
- if (path.isAbsolute(target) || canonicalTarget === null || !isContained(root, canonicalTarget)) {
474
- throw new Error(`resource symlink escapes its declared root: ${relativePath}`);
577
+ if (links !== "record") {
578
+ const canonicalTarget = await realpath(entryPath).catch(() => null);
579
+ if (path.isAbsolute(target) || canonicalTarget === null || !isContained(root, canonicalTarget)) {
580
+ throw new Error(`resource symlink escapes its declared root: ${relativePath}`);
581
+ }
475
582
  }
476
- files.push({ path: relativePath, type: "symlink", sha256: sha256(`symlink\0${target}`) });
583
+ files.push({ path: relativePath, type: "symlink", sha256: sha256(`symlink\0${target}`), target });
477
584
  } else if (entryStat.isDirectory()) {
478
585
  files.push({ path: relativePath, type: "directory", sha256: sha256("directory") });
479
- await walk(root, entryPath, files);
586
+ await walk(root, entryPath, files, links);
480
587
  } else if (entryStat.isFile()) {
481
588
  files.push({ path: relativePath, type: "file", sha256: sha256(await readFile(entryPath)) });
482
589
  } else {
@@ -0,0 +1,457 @@
1
+ // The transcript scan's reading of a bash command: its simple commands, each as its words, and the network targets
2
+ // they reach for. It is a heuristic over shell text, not a shell: it follows quotes, `bash -c` strings, command
3
+ // substitutions and heredocs fed to a shell, and expands variables assigned a literal earlier in the same command.
4
+ // A target it cannot resolve (a variable read from a file) is not shown to be external, so it is not reported.
5
+
6
+ const SHELLS = new Set(["bash", "sh", "zsh", "dash", "ksh"]);
7
+ // Programs that only run the program after them.
8
+ const WRAPPERS = new Set(["sudo", "nohup", "command", "exec", "time", "nice", "stdbuf", "caffeinate"]);
9
+ const MAX_DEPTH = 4;
10
+
11
+ /**
12
+ * The simple commands of a shell command line, each `{ words, bodies }`: its words with quotes removed, and the
13
+ * text of each heredoc fed to it. A string run by a shell (`bash -c`), a command substitution and a heredoc fed to a
14
+ * shell are parsed as commands too; a heredoc fed to anything else (a script for `node`, a JSON payload) is data.
15
+ *
16
+ * @param {string} text
17
+ * @returns {{ words: string[], bodies: string[] }[]}
18
+ */
19
+ export function simpleCommands(text, depth = 0) {
20
+ const commands = [];
21
+ const nested = [];
22
+ let current = { words: [], bodies: [] };
23
+ let word = null;
24
+ let heredocs = [];
25
+ const endWord = () => {
26
+ if (word !== null) current.words.push(word);
27
+ word = null;
28
+ };
29
+ const endCommand = () => {
30
+ endWord();
31
+ if (current.words.length > 0) commands.push(current);
32
+ current = { words: [], bodies: [] };
33
+ };
34
+ let index = 0;
35
+ while (index < text.length) {
36
+ const char = text[index];
37
+ if (char === "\\") {
38
+ if (text[index + 1] !== "\n") word = (word ?? "") + (text[index + 1] ?? "");
39
+ index += 2;
40
+ } else if (char === "'") {
41
+ const end = text.indexOf("'", index + 1);
42
+ const stop = end === -1 ? text.length : end;
43
+ word = (word ?? "") + text.slice(index + 1, stop);
44
+ index = stop + 1;
45
+ } else if (char === '"') {
46
+ let cursor = index + 1;
47
+ let value = "";
48
+ while (cursor < text.length && text[cursor] !== '"') {
49
+ if (text[cursor] === "\\" && cursor + 1 < text.length) {
50
+ value += text[cursor + 1];
51
+ cursor += 2;
52
+ } else {
53
+ value += text[cursor];
54
+ cursor += 1;
55
+ }
56
+ }
57
+ word = (word ?? "") + value;
58
+ index = cursor + 1;
59
+ } else if (char === "$" && text[index + 1] === "(" && text[index + 2] !== "(") {
60
+ const end = closing(text, index + 1);
61
+ nested.push(text.slice(index + 2, end));
62
+ word = (word ?? "") + text.slice(index, end + 1);
63
+ index = end + 1;
64
+ } else if (char === "`") {
65
+ const end = text.indexOf("`", index + 1);
66
+ const stop = end === -1 ? text.length : end;
67
+ nested.push(text.slice(index + 1, stop));
68
+ word = (word ?? "") + text.slice(index, stop + 1);
69
+ index = stop + 1;
70
+ } else if (char === "#" && word === null) {
71
+ const end = text.indexOf("\n", index);
72
+ index = end === -1 ? text.length : end;
73
+ } else if (char === "<" && text[index + 1] === "<" && text[index + 2] !== "<") {
74
+ endWord();
75
+ let cursor = index + 2;
76
+ const strip = text[cursor] === "-";
77
+ if (strip) cursor += 1;
78
+ while (text[cursor] === " " || text[cursor] === "\t") cursor += 1;
79
+ let delimiter = "";
80
+ while (cursor < text.length && !/[\s;&|<>()]/.test(text[cursor])) {
81
+ if (text[cursor] !== "'" && text[cursor] !== '"' && text[cursor] !== "\\") delimiter += text[cursor];
82
+ cursor += 1;
83
+ }
84
+ heredocs.push({ delimiter, strip, owner: current });
85
+ index = cursor;
86
+ } else if (char === "<" || char === ">") {
87
+ endWord();
88
+ index += 1;
89
+ // `2>&1`, `>&2`: a descriptor, not a command separator.
90
+ if (text[index] === "&") index += 1;
91
+ while (text[index] === ">" || text[index] === "|") index += 1;
92
+ } else if (char === "\n") {
93
+ endCommand();
94
+ index += 1;
95
+ for (const heredoc of heredocs) {
96
+ const body = [];
97
+ while (index < text.length) {
98
+ const end = text.indexOf("\n", index);
99
+ const line = text.slice(index, end === -1 ? text.length : end);
100
+ index = end === -1 ? text.length : end + 1;
101
+ if ((heredoc.strip ? line.replace(/^\t+/, "") : line) === heredoc.delimiter) break;
102
+ body.push(line);
103
+ }
104
+ heredoc.owner.bodies.push(body.join("\n"));
105
+ }
106
+ heredocs = [];
107
+ } else if (";&|()".includes(char)) {
108
+ endCommand();
109
+ index += 1;
110
+ } else if (char === " " || char === "\t" || char === "\r") {
111
+ endWord();
112
+ index += 1;
113
+ } else {
114
+ word = (word ?? "") + char;
115
+ index += 1;
116
+ }
117
+ }
118
+ endCommand();
119
+ if (depth >= MAX_DEPTH) return commands;
120
+ const all = [];
121
+ for (const command of commands) {
122
+ all.push(command);
123
+ const { name, args } = program(command.words);
124
+ if (SHELLS.has(name)) {
125
+ const flag = args.findIndex((arg) => /^-[a-z]*c[a-z]*$/.test(arg));
126
+ if (flag !== -1 && typeof args[flag + 1] === "string") all.push(...simpleCommands(args[flag + 1], depth + 1));
127
+ for (const body of command.bodies) all.push(...simpleCommands(body, depth + 1));
128
+ }
129
+ }
130
+ for (const inner of nested) all.push(...simpleCommands(inner, depth + 1));
131
+ return all;
132
+ }
133
+
134
+ /** The index of the parenthesis that closes the one at `open`, or the text's end. */
135
+ function closing(text, open) {
136
+ let depth = 0;
137
+ for (let index = open; index < text.length; index += 1) {
138
+ if (text[index] === "(") depth += 1;
139
+ else if (text[index] === ")") {
140
+ depth -= 1;
141
+ if (depth === 0) return index;
142
+ }
143
+ }
144
+ return text.length;
145
+ }
146
+
147
+ const ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
148
+
149
+ /** The program a simple command runs and its arguments: leading assignments and wrappers (`sudo`, `env`) skipped. */
150
+ export function program(words) {
151
+ let index = 0;
152
+ while (index < words.length) {
153
+ const word = words[index];
154
+ if (ASSIGNMENT.test(word)) {
155
+ index += 1;
156
+ } else if (WRAPPERS.has(word)) {
157
+ index += 1;
158
+ while (index < words.length && words[index].startsWith("-")) index += 1;
159
+ } else if (word === "env") {
160
+ index += 1;
161
+ while (index < words.length && (words[index].startsWith("-") || ASSIGNMENT.test(words[index]))) index += 1;
162
+ } else if (word === "timeout") {
163
+ index += 1;
164
+ while (index < words.length && words[index].startsWith("-")) index += 1;
165
+ index += 1;
166
+ } else {
167
+ break;
168
+ }
169
+ }
170
+ const name = (words[index] ?? "").split("/").at(-1);
171
+ return { name, args: words.slice(index + 1) };
172
+ }
173
+
174
+ /** Variables a command line assigns a literal, in order: `NAME=value` and `export NAME=value`. */
175
+ export function assignments(commands) {
176
+ const values = new Map();
177
+ for (const { words } of commands) {
178
+ for (const word of words[0] === "export" || words[0] === "local" ? words.slice(1) : words) {
179
+ const match = ASSIGNMENT.exec(word);
180
+ if (match === null) break;
181
+ if (!match[2].includes("$(") && !match[2].includes("`")) values.set(match[1], expand(match[2], values));
182
+ }
183
+ }
184
+ return values;
185
+ }
186
+
187
+ /** `$NAME` and `${NAME}` replaced by their literal values; unknown variables are left as they are. */
188
+ export function expand(word, values) {
189
+ return word.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (whole, braced, bare) => {
190
+ const value = values.get(braced ?? bare);
191
+ return value === undefined ? whole : value;
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Whether a host is this machine or the container's own network: loopback, `0.0.0.0`, `*.localhost`, or a name with
197
+ * no dot (a container's service name, which a sealed run cannot resolve beyond its own network anyway).
198
+ */
199
+ export function isInternalHost(host) {
200
+ const name = host.toLowerCase().replace(/^\[|\]$/g, "");
201
+ if (name === "localhost" || name.endsWith(".localhost") || name === "0.0.0.0" || name === "::1") return true;
202
+ if (/^127(?:\.\d{1,3}){3}$/.test(name)) return true;
203
+ return !name.includes(".") && !name.includes(":");
204
+ }
205
+
206
+ const SCHEME_URL = /^([a-z][a-z0-9+.-]*):\/\/(?:[^@/?#\s]*@)?(\[[^\]]+\]|[^:/?#\s]+)/i;
207
+ const BARE_HOST = /^(?:[^@/\s]+@)?([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)(?::\d+)?(?:[/?#].*)?$/s;
208
+
209
+ // A bare word ending like a file name is a file, not a host (`out.json`, `notes.md`).
210
+ const FILE_ENDING = /\.(?:json|md|txt|log|js|mjs|cjs|ts|html|css|csv|xml|ya?ml|sh|py|out|body|tmp|lock|tgz|gz|zip)$/i;
211
+
212
+ /** The host a URL (or, with `bare`, a `host[:port]/path`) names; null when it is neither or still has a variable. */
213
+ function hostOf(word, { bare = true } = {}) {
214
+ if (word.includes("$")) return null;
215
+ const url = SCHEME_URL.exec(word);
216
+ if (url !== null) return url[1].toLowerCase() === "file" ? null : url[2];
217
+ if (!bare) return null;
218
+ const host = BARE_HOST.exec(word)?.[1] ?? null;
219
+ if (host === null || FILE_ENDING.test(host)) return null;
220
+ // A version or other number (1.2.3) is no host; a dotted IPv4 address is.
221
+ if (/^\d+(?:\.\d+)*$/.test(host) && !/^\d+\.\d+\.\d+\.\d+$/.test(host)) return null;
222
+ return host;
223
+ }
224
+
225
+ // Options of an HTTP client that take a value: the value is a header, payload, file or number, never the target.
226
+ const HTTP_VALUE_OPTIONS = new Set([
227
+ "-A",
228
+ "-b",
229
+ "-c",
230
+ "-C",
231
+ "-d",
232
+ "-D",
233
+ "-e",
234
+ "-E",
235
+ "-F",
236
+ "-H",
237
+ "-K",
238
+ "-m",
239
+ "-o",
240
+ "-O",
241
+ "-P",
242
+ "-r",
243
+ "-T",
244
+ "-t",
245
+ "-u",
246
+ "-U",
247
+ "-w",
248
+ "-X",
249
+ "-a",
250
+ "-i",
251
+ "--cacert",
252
+ "--cert",
253
+ "--config",
254
+ "--connect-timeout",
255
+ "--cookie",
256
+ "--cookie-jar",
257
+ "--data",
258
+ "--data-ascii",
259
+ "--data-binary",
260
+ "--data-raw",
261
+ "--data-urlencode",
262
+ "--dump-header",
263
+ "--form",
264
+ "--header",
265
+ "--key",
266
+ "--max-time",
267
+ "--output",
268
+ "--output-document",
269
+ "--post-data",
270
+ "--post-file",
271
+ "--referer",
272
+ "--request",
273
+ "--retry",
274
+ "--timeout",
275
+ "--tries",
276
+ "--upload-file",
277
+ "--user",
278
+ "--user-agent",
279
+ "--write-out",
280
+ ]);
281
+ const SOCKET_VALUE_OPTIONS = new Set(["-w", "-p", "-s", "-x", "-X", "-i", "-q", "-O", "-I", "-T", "-V"]);
282
+ const SSH_VALUE_OPTIONS = new Set([
283
+ "-p",
284
+ "-P",
285
+ "-i",
286
+ "-l",
287
+ "-o",
288
+ "-F",
289
+ "-J",
290
+ "-b",
291
+ "-c",
292
+ "-D",
293
+ "-E",
294
+ "-e",
295
+ "-L",
296
+ "-m",
297
+ "-O",
298
+ "-Q",
299
+ "-R",
300
+ "-S",
301
+ "-W",
302
+ "-w",
303
+ "-B",
304
+ ]);
305
+
306
+ /** Positional arguments, with the values of the given options skipped. */
307
+ function positionals(args, valueOptions) {
308
+ const found = [];
309
+ for (let index = 0; index < args.length; index += 1) {
310
+ const arg = args[index];
311
+ if (arg === "--url") {
312
+ if (args[index + 1] !== undefined) found.push(args[index + 1]);
313
+ index += 1;
314
+ } else if (valueOptions.has(arg)) {
315
+ index += 1;
316
+ } else if (!arg.startsWith("-") || arg === "-") {
317
+ if (arg !== "-") found.push(arg);
318
+ }
319
+ }
320
+ return found;
321
+ }
322
+
323
+ const PACKAGE_MANAGERS = {
324
+ npm: ["install", "i", "add", "update", "up", "upgrade"],
325
+ pnpm: ["install", "i", "add", "update", "up"],
326
+ yarn: ["add", "upgrade", "up"],
327
+ bun: ["add", "install", "i"],
328
+ pip: ["install", "download"],
329
+ pip3: ["install", "download"],
330
+ gem: ["install"],
331
+ cargo: ["install", "add", "fetch"],
332
+ go: ["get", "install"],
333
+ };
334
+ const SYSTEM_PACKAGES = {
335
+ apt: ["install", "update", "upgrade", "dist-upgrade"],
336
+ "apt-get": ["install", "update", "upgrade", "dist-upgrade"],
337
+ apk: ["add", "update", "upgrade"],
338
+ brew: ["install", "update", "upgrade", "tap"],
339
+ yum: ["install", "update", "upgrade"],
340
+ dnf: ["install", "update", "upgrade"],
341
+ };
342
+ // Options of a package manager that take a value that is not a package.
343
+ const PACKAGE_VALUE_OPTIONS = new Set([
344
+ "--prefix",
345
+ "-C",
346
+ "--cache",
347
+ "--registry",
348
+ "--workspace",
349
+ "-w",
350
+ "--dir",
351
+ "--cwd",
352
+ "-t",
353
+ "--target",
354
+ "-r",
355
+ "--requirement",
356
+ "-c",
357
+ "--constraint",
358
+ "-i",
359
+ "--index-url",
360
+ "--extra-index-url",
361
+ "--root",
362
+ ]);
363
+
364
+ /** Whether a package spec names something local (a path, a file: or link: spec, a tarball beside it). */
365
+ function localSpec(spec) {
366
+ return /^(?:\.|\/|~|file:|link:|workspace:|portal:)/.test(spec) || /\.(?:tgz|tar\.gz|whl|gem)$/.test(spec);
367
+ }
368
+
369
+ /**
370
+ * The network attempts in one bash command: each `{ program, target }`. An HTTP client, socket tool, remote shell or
371
+ * Git remote counts only with an external target; a package install counts only when `sealed` (the run's network
372
+ * was "none") and it names a package from a registry, since installing the project's own dependencies or a local
373
+ * path is ordinary work. Code in the command (a `node -e` script, a heredoc) counts when it fetches an external URL.
374
+ *
375
+ * @param {string} command
376
+ * @param {{ sealed?: boolean }} [options]
377
+ */
378
+ export function networkAttempts(command, { sealed = false } = {}) {
379
+ const commands = simpleCommands(command);
380
+ const values = assignments(commands);
381
+ const attempts = [];
382
+ const external = (name, words, options) => {
383
+ for (const word of words) {
384
+ const host = hostOf(expand(word, values), options);
385
+ if (host !== null && !isInternalHost(host)) attempts.push({ program: name, target: expand(word, values) });
386
+ }
387
+ };
388
+ for (const { words } of commands) {
389
+ const { name, args } = program(words);
390
+ if (["curl", "wget", "http", "https", "xh"].includes(name)) {
391
+ external(name, positionals(args, HTTP_VALUE_OPTIONS));
392
+ } else if (["nc", "ncat", "netcat", "telnet"].includes(name)) {
393
+ external(name, positionals(args, SOCKET_VALUE_OPTIONS).slice(0, 1), { bare: true });
394
+ } else if (name === "ssh") {
395
+ // A remote shell's host may be an alias with no dot, so only a loopback name is this machine.
396
+ const [host] = positionals(args, SSH_VALUE_OPTIONS);
397
+ const target = host === undefined ? null : expand(host, values).replace(/^[^@]*@/, "");
398
+ if (target !== null && !target.includes("$") && !isLoopbackName(target)) attempts.push({ program: name, target });
399
+ } else if (["scp", "sftp", "rsync"].includes(name)) {
400
+ for (const arg of positionals(args, SSH_VALUE_OPTIONS)) {
401
+ const value = expand(arg, values);
402
+ const remote = /^(?:[^@/\s]+@)?([^:/\s]+):/.exec(value);
403
+ const host =
404
+ SCHEME_URL.exec(value)?.[2] ?? (value.startsWith("/") || value.startsWith(".") ? null : remote?.[1]);
405
+ if (host && !host.includes("$") && !isLoopbackName(host)) attempts.push({ program: name, target: value });
406
+ }
407
+ } else if (name === "git") {
408
+ const rest = args.filter((arg, index) => !(args[index - 1] === "-C" || args[index - 1] === "-c"));
409
+ const subcommand = rest.find((arg) => !arg.startsWith("-") && !ASSIGNMENT.test(arg));
410
+ if (["clone", "fetch", "pull", "push", "ls-remote"].includes(subcommand)) {
411
+ const after = rest.slice(rest.indexOf(subcommand) + 1).filter((arg) => !arg.startsWith("-"));
412
+ const remotes = after
413
+ .map((arg) => expand(arg, values))
414
+ .map((arg) => SCHEME_URL.exec(arg)?.[2] ?? /^[^@/\s]+@([^:/\s]+):/.exec(arg)?.[1] ?? null);
415
+ const named = remotes.filter((host) => host !== null);
416
+ if (named.some((host) => !isLoopbackName(host))) {
417
+ attempts.push({ program: `git ${subcommand}`, target: after.join(" ") });
418
+ } else if (named.length === 0 && subcommand !== "clone" && after.every((arg) => !/^[./~]/.test(arg))) {
419
+ // A remote by name (origin): whatever it points at is reached.
420
+ attempts.push({ program: `git ${subcommand}`, target: after[0] ?? "the default remote" });
421
+ }
422
+ }
423
+ } else if (sealed && (name in PACKAGE_MANAGERS || name in SYSTEM_PACKAGES || isPythonPip(name, args))) {
424
+ const pip = isPythonPip(name, args);
425
+ const effective = pip ? args.slice(args.indexOf("pip") + 1) : args;
426
+ const manager = pip ? "pip" : name;
427
+ const subcommand = effective.find((arg) => !arg.startsWith("-"));
428
+ if (effective.includes("--offline")) continue;
429
+ if (name in SYSTEM_PACKAGES) {
430
+ if (SYSTEM_PACKAGES[name].includes(subcommand)) attempts.push({ program: `${name} ${subcommand}`, target: "" });
431
+ continue;
432
+ }
433
+ if (!PACKAGE_MANAGERS[manager].includes(subcommand)) continue;
434
+ const specs = positionals(effective.slice(effective.indexOf(subcommand) + 1), PACKAGE_VALUE_OPTIONS)
435
+ .map((spec) => expand(spec, values))
436
+ .filter((spec) => !spec.includes("$") && !localSpec(spec));
437
+ if (specs.length > 0) attempts.push({ program: `${manager} ${subcommand}`, target: specs.join(" ") });
438
+ }
439
+ }
440
+ // Code the command runs that fetches a literal URL: fetch(), http.get(), requests.get(), urlopen(), WebSocket.
441
+ const code =
442
+ /\b(?:fetch|https?\.(?:get|request)|axios(?:\.(?:get|post|put|request))?|got|requests\.(?:get|post|put|head)|urlopen|WebSocket)\(\s*(?:new URL\(\s*)?(["'`])([^"'`]*)\1/g;
443
+ for (const match of command.matchAll(code)) {
444
+ const host = hostOf(match[2].replace(/\$\{[^}]*\}/g, "x"), { bare: false });
445
+ if (host !== null && !isInternalHost(host)) attempts.push({ program: "code", target: match[2] });
446
+ }
447
+ return attempts;
448
+ }
449
+
450
+ function isLoopbackName(host) {
451
+ const name = host.toLowerCase().replace(/^\[|\]$/g, "");
452
+ return name === "localhost" || name.endsWith(".localhost") || name === "::1" || /^127(?:\.\d{1,3}){3}$/.test(name);
453
+ }
454
+
455
+ function isPythonPip(name, args) {
456
+ return /^python(?:3(?:\.\d+)?)?$/.test(name) && args[0] === "-m" && args[1] === "pip";
457
+ }