@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,233 @@
1
+ import { autoApproveGroup } from "./config.js";
2
+ import { renderTaskCall, renderTaskResult } from "./render.js";
3
+ import { confirmWithUser } from "../ask-user/ask.js";
4
+ export const TASK_MEMORY_TOOL = "task_memory";
5
+ const DESCRIPTION =
6
+ "Task memory: the Working Records (Track Work) this session works on, and their projects. Read the task-memory skill before your first proposal; Track Work decides what records say. Reading needs no permission: projects lists every project; list lists this repository's records, or a project's tasks; view reads a record's resume view or one entry by task ID; search finds lines in the focused task, its tree, or its project. Attention: focus switches to an attached record; detach stops working on one. Proposals, each confirmed by the user in a dialog before anything happens: attach a record; create a task in this repository's project and attach it; child or fanout (a child with its own Git worktree) of the focused task for a parallel session; project_new or project_link for this repository; import a repository record into a project; confirm a held record; env to change a task's directories; remove a task with its children, or project_remove a whole project, which moves their records to the trash. Only the user empties the trash, with /freeflow trash empty: when asked to, tell the user to run it. A decline is the user's answer: do not retry it or work around it.";
7
+ const ACTIONS = [
8
+ "projects",
9
+ "list",
10
+ "view",
11
+ "search",
12
+ "attach",
13
+ "focus",
14
+ "detach",
15
+ "create",
16
+ "child",
17
+ "fanout",
18
+ "project_new",
19
+ "project_link",
20
+ "import",
21
+ "confirm",
22
+ "env",
23
+ "remove",
24
+ "project_remove",
25
+ ];
26
+ // Actions that change which records this session holds; the Runtime State follows them.
27
+ const CHANGES_STATE = new Set(["attach", "focus", "detach", "create", "import", "confirm", "remove", "project_remove"]);
28
+ /** Actions any profile may take: they only read. `env` reads too, unless it changes a directory. */
29
+ const READS = new Set(["projects", "list", "view", "search"]);
30
+ const reads = (params) =>
31
+ READS.has(params?.action) ||
32
+ (params?.action === "env" &&
33
+ !params?.primary?.trim?.() &&
34
+ !params?.add_dir?.trim?.() &&
35
+ !params?.remove_dir?.trim?.());
36
+ /** Registered once, declared only while task memory is effective (see applyTaskMemoryTools). */
37
+ export function registerTaskMemoryTool(pi, host) {
38
+ pi.registerTool({
39
+ name: TASK_MEMORY_TOOL,
40
+ label: "Task memory",
41
+ description: DESCRIPTION,
42
+ parameters: {
43
+ type: "object",
44
+ additionalProperties: false,
45
+ properties: {
46
+ action: { type: "string", enum: ACTIONS },
47
+ record: {
48
+ type: "string",
49
+ description: "A task ID such as my-project/task-003, or a record.md path; remove takes a task ID",
50
+ },
51
+ project: { type: "string", description: "list: a project's name; import: the project to move the record into" },
52
+ name: { type: "string", description: "project_new, project_link, project_remove: the project's name" },
53
+ worktrees: { type: "string", description: "project_new, project_link: where children's worktrees go" },
54
+ title: { type: "string", description: "create, child, fanout: the new task's title" },
55
+ goal: {
56
+ type: "string",
57
+ description:
58
+ "create, child, fanout: its goal, one line; omitted, the title is the goal (the dialog shows it)",
59
+ },
60
+ entry: { type: "string", description: "view: one entry ID such as S-004" },
61
+ query: { type: "string", description: "search: text to find" },
62
+ scope: { type: "string", enum: ["task", "tree", "project"], description: "search: where to look" },
63
+ primary: { type: "string", description: "env: the task's new primary directory" },
64
+ add_dir: { type: "string", description: "env: a directory to add" },
65
+ remove_dir: { type: "string", description: "env: a directory to remove" },
66
+ },
67
+ required: ["action"],
68
+ },
69
+ // It asks the user, so scripts must not call it (Pi's guidance for such tools); called directly, its own
70
+ // transcript views apply, which a codemode script's nested call would bypass.
71
+ exposure: "model-only",
72
+ defaultActive: false,
73
+ executionMode: "sequential",
74
+ renderCall: (args, theme) => renderTaskCall(args, theme),
75
+ renderResult: (result, options, theme, context) => renderTaskResult(result, options, theme, context),
76
+ async execute(_id, params, _signal, _update, ctx) {
77
+ const refused = reads(params) ? undefined : host.refusal?.();
78
+ const group = reads(params) ? undefined : autoApproveGroup(params?.action);
79
+ const approved = group !== undefined && host.autoApproved?.(group) === true;
80
+ const result = refused ? { ok: false, text: refused } : await run(host, params, ctx, approved);
81
+ if (approved && result.ok)
82
+ result.text = `Auto-approved by the user's taskMemory.autoApprove setting (${group}); no dialog was shown.\n\n${result.text}`;
83
+ if (result.ok && CHANGES_STATE.has(params?.action)) host.changed(ctx);
84
+ let next;
85
+ if (result.ok && result.snapshot) {
86
+ const brief = await host.memory.brief(result.snapshot);
87
+ result.headline ??= `${params.action === "focus" ? "Focus on" : "Attached"} ${brief.label} · ${brief.state}`;
88
+ next = brief.next || undefined;
89
+ }
90
+ return {
91
+ content: [{ type: "text", text: result.text }],
92
+ details: {
93
+ action: params?.action,
94
+ ...(result.snapshot ? { snapshot: result.snapshot } : {}),
95
+ ...(result.headline ? { headline: result.headline } : {}),
96
+ ...(next ? { next } : {}),
97
+ ...(result.command ? { command: result.command } : {}),
98
+ ...(result.id ? { id: result.id } : {}),
99
+ },
100
+ ...(result.ok ? {} : { isError: true }),
101
+ };
102
+ },
103
+ });
104
+ }
105
+ /** Ask the user before an action taken on their authority; without a UI the proposal cannot be confirmed. */
106
+ async function confirmProposal(ctx, title, message) {
107
+ if (!ctx.hasUI) return { ok: false, text: `${title} needs the user's confirmation, and no UI is available.` };
108
+ if (await confirmWithUser(ctx, title, message)) return undefined;
109
+ return { ok: false, headline: "Declined by the user", text: "The user declined. Do not retry or work around it." };
110
+ }
111
+ const show = (value) => (typeof value === "string" ? value.trim() : "");
112
+ async function run(host, params, ctx, approved = false) {
113
+ const { memory } = host;
114
+ // An action the user's auto-approve setting covers runs without asking.
115
+ const ask = (context, title, message) =>
116
+ approved ? Promise.resolve(undefined) : confirmProposal(context, title, message);
117
+ const cwd = ctx.cwd;
118
+ switch (params?.action) {
119
+ case "projects":
120
+ return memory.projects(cwd);
121
+ case "list":
122
+ return { ok: true, text: await memory.list(cwd, params.project) };
123
+ case "view":
124
+ return memory.view(params.record, params.entry, cwd);
125
+ case "search":
126
+ return memory.search(params.query, params.scope, cwd);
127
+ case "focus":
128
+ return memory.focus(params.record, cwd);
129
+ case "detach":
130
+ return memory.detach(params.record, cwd);
131
+ case "attach":
132
+ return (
133
+ (await ask(
134
+ ctx,
135
+ "Attach task record?",
136
+ `The agent proposes working on ${show(params.record)} in this session.`,
137
+ )) ?? memory.attach(params.record, cwd)
138
+ );
139
+ case "create": {
140
+ const declined = await ask(
141
+ ctx,
142
+ "Create task?",
143
+ `Create "${show(params.title)}" in this repository's project and attach it to this session? Goal: ${show(params.goal) || show(params.title)}`,
144
+ );
145
+ if (declined) return declined;
146
+ const created = await memory.create(params.title, params.goal, cwd);
147
+ if (!created.ok || !created.id) return created;
148
+ const attached = await memory.attach(created.id, cwd);
149
+ return attached.ok
150
+ ? {
151
+ ...attached,
152
+ headline: `Created and attached ${created.id} — ${show(params.title)}`,
153
+ text: `Created ${created.id} and attached it to this session.\n\n${attached.text}`,
154
+ }
155
+ : { ...attached, text: `Created ${created.id}, but it was not attached: ${attached.text}` };
156
+ }
157
+ case "child":
158
+ return (
159
+ (await ask(
160
+ ctx,
161
+ "Create child task?",
162
+ `Create "${show(params.title)}" as a child of the focused task, for a parallel session you start?`,
163
+ )) ?? memory.child(params.title, params.goal)
164
+ );
165
+ case "fanout":
166
+ return (
167
+ (await ask(
168
+ ctx,
169
+ "Fan out a child task?",
170
+ `Create child "${show(params.title)}" of the focused task with its own Git worktree and branch? You start its session in a new terminal.`,
171
+ )) ?? memory.fanout(params.title, params.goal)
172
+ );
173
+ case "project_new":
174
+ case "project_link": {
175
+ const operation = params.action === "project_new" ? "new" : "link";
176
+ const declined = await ask(
177
+ ctx,
178
+ operation === "new" ? "Create project?" : "Link repository?",
179
+ operation === "new"
180
+ ? `Create project "${show(params.name)}" and link this repository (${cwd}) to it?${show(params.worktrees) ? ` Child worktrees go in ${show(params.worktrees)}.` : ""}`
181
+ : `Link this repository (${cwd}) to project "${show(params.name)}"?`,
182
+ );
183
+ return declined ?? memory.project(operation, params.name, params.worktrees, cwd);
184
+ }
185
+ case "import":
186
+ return (
187
+ (await ask(
188
+ ctx,
189
+ "Import record?",
190
+ `Move ${show(params.record)} into the ${show(params.project)} project's store, with a task ID?`,
191
+ )) ?? memory.importRecord(params.record, params.project, cwd)
192
+ );
193
+ case "confirm":
194
+ return (
195
+ (await ask(
196
+ ctx,
197
+ "Make held record writable?",
198
+ `Let this session write ${show(params.record) || "its held records"}, taking ownership where another session owns one?`,
199
+ )) ?? memory.confirm(params.record, cwd)
200
+ );
201
+ case "env": {
202
+ const change = { primary: show(params.primary), add: show(params.add_dir), remove: show(params.remove_dir) };
203
+ if (change.primary || change.add || change.remove) {
204
+ const declined = await ask(
205
+ ctx,
206
+ "Change task directories?",
207
+ `For ${show(params.record)}: ${[change.primary && `primary ${change.primary}`, change.add && `add ${change.add}`, change.remove && `remove ${change.remove}`].filter(Boolean).join(", ")}?`,
208
+ );
209
+ if (declined) return declined;
210
+ }
211
+ return memory.environment(params.record, change, cwd);
212
+ }
213
+ case "remove":
214
+ case "project_remove": {
215
+ // Removal always asks: no auto-approve setting covers it (Task 006).
216
+ const target = params.action === "remove" ? { task: params.record } : { project: params.name };
217
+ const { question, error } = await memory.removalQuestion(target);
218
+ if (!question) return { ok: false, text: error ?? "Nothing to remove." };
219
+ const title = params.action === "remove" ? "Remove task?" : "Remove project?";
220
+ return (await confirmProposal(ctx, title, question)) ?? memory.remove(target);
221
+ }
222
+ default:
223
+ return { ok: false, text: `action must be one of ${ACTIONS.join(", ")}.` };
224
+ }
225
+ }
226
+ /** Declare or withdraw task_memory when task memory's effective state changes, keeping other tools in place. */
227
+ export function applyTaskMemoryTools(pi, effective) {
228
+ if (typeof pi.getActiveTools !== "function" || typeof pi.setActiveTools !== "function") return;
229
+ const current = pi.getActiveTools();
230
+ const present = current.includes(TASK_MEMORY_TOOL);
231
+ if (effective && !present) pi.setActiveTools([...current, TASK_MEMORY_TOOL]);
232
+ if (!effective && present) pi.setActiveTools(current.filter((name) => name !== TASK_MEMORY_TOOL));
233
+ }
@@ -0,0 +1,23 @@
1
+ // The apply_patch Lark grammar from OpenAI Codex (codex-rs/core/assets/tools/apply_patch.lark at bcd6d9ab),
2
+ // Copyright 2025 OpenAI, licensed under the Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0).
3
+ // GPT models are trained on this exact grammar; keep it byte-identical to upstream.
4
+ export const APPLY_PATCH_LARK_GRAMMAR = `start: begin_patch hunk+ end_patch
5
+ begin_patch: "*** Begin Patch" LF
6
+ end_patch: "*** End Patch" LF?
7
+
8
+ hunk: add_hunk | delete_hunk | update_hunk
9
+ add_hunk: "*** Add File: " filename LF add_line+
10
+ delete_hunk: "*** Delete File: " filename LF
11
+ update_hunk: "*** Update File: " filename LF change_move? change?
12
+
13
+ filename: /(.+)/
14
+ add_line: "+" /(.*)/ LF -> line
15
+
16
+ change_move: "*** Move to: " filename LF
17
+ change: (change_context | change_line)+ eof_line?
18
+ change_context: ("@@" | "@@ " /(.+)/) LF
19
+ change_line: ("+" | "-" | " ") /(.*)/ LF
20
+ eof_line: "*** End of File" LF
21
+
22
+ %import common.LF
23
+ `;
@@ -0,0 +1,38 @@
1
+ // Locating a hunk's lines in a file, with the four passes of OpenAI Codex's seek_sequence
2
+ // (codex-rs/apply-patch/src/seek_sequence.rs at bcd6d9ab, Apache-2.0): exact, then ignoring trailing whitespace,
3
+ // then ignoring surrounding whitespace, then after normalizing Unicode punctuation and spaces.
4
+ function normalise(line) {
5
+ return line
6
+ .trim()
7
+ .replace(/[‐‑‒–—―−]/g, "-")
8
+ .replace(/[‘’‚‛]/g, "'")
9
+ .replace(/[“”„‟]/g, '"')
10
+ .replace(/[  -    ]/g, " ");
11
+ }
12
+ const PASSES = [(line) => line, (line) => line.trimEnd(), (line) => line.trim(), normalise];
13
+ function matchesAt(lines, pattern, at, pass) {
14
+ const compare = PASSES[pass];
15
+ for (let i = 0; i < pattern.length; i++) if (compare(lines[at + i]) !== compare(pattern[i])) return false;
16
+ return true;
17
+ }
18
+ /**
19
+ * The first index at or after `start` where `pattern` matches, and the pass that matched. With `endOfFile`, the
20
+ * search starts where the pattern would end the file (falling back to `start` when that is earlier than the file).
21
+ */
22
+ export function seekSequence(lines, pattern, start, endOfFile) {
23
+ if (pattern.length === 0) return { index: start, pass: 0 };
24
+ if (pattern.length > lines.length) return undefined;
25
+ const from = endOfFile ? Math.max(lines.length - pattern.length, start) : start;
26
+ for (let pass = 0; pass < PASSES.length; pass++)
27
+ for (let i = from; i <= lines.length - pattern.length; i++)
28
+ if (matchesAt(lines, pattern, i, pass)) return { index: i, pass };
29
+ return undefined;
30
+ }
31
+ /** Other places in the whole file where the pattern matches with the same pass: a visible sign of ambiguity. */
32
+ export function otherMatches(lines, pattern, index, pass) {
33
+ if (pattern.length === 0) return [];
34
+ const found = [];
35
+ for (let i = 0; i <= lines.length - pattern.length; i++)
36
+ if (i !== index && matchesAt(lines, pattern, i, pass)) found.push(i);
37
+ return found;
38
+ }
@@ -0,0 +1,177 @@
1
+ // A port of OpenAI Codex's apply_patch parser (codex-rs/apply-patch/src/{parser,streaming_parser}.rs at bcd6d9ab,
2
+ // Apache-2.0), including its lenient forms, so patches GPT models write for Codex parse the same way here.
3
+ export class PatchParseError extends Error {
4
+ line;
5
+ constructor(message, line) {
6
+ super(line === undefined ? message : `${message} (patch line ${line})`);
7
+ this.line = line;
8
+ }
9
+ }
10
+ const BEGIN = "*** Begin Patch";
11
+ const END = "*** End Patch";
12
+ const ADD = "*** Add File: ";
13
+ const DELETE = "*** Delete File: ";
14
+ const UPDATE = "*** Update File: ";
15
+ const MOVE = "*** Move to: ";
16
+ const EOF_MARKER = "*** End of File";
17
+ const ENVIRONMENT = "*** Environment ID: ";
18
+ const HEADERS = "Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'";
19
+ const UNEXPECTED = (line) =>
20
+ `Unexpected line found in update hunk: '${line}'. Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)`;
21
+ function boundaries(lines) {
22
+ const strict = (candidate) => {
23
+ const first = candidate[0]?.trim(),
24
+ last = candidate.at(-1)?.trim();
25
+ if (first !== BEGIN) throw new PatchParseError(`The first line of the patch must be '${BEGIN}'`);
26
+ if (last !== END) throw new PatchParseError(`The last line of the patch must be '${END}'`);
27
+ return candidate;
28
+ };
29
+ try {
30
+ return strict(lines);
31
+ } catch (error) {
32
+ // Codex's lenient mode: a heredoc wrapper, as GPT models write for a shell invocation. The optional
33
+ // `apply_patch` prefix covers the same wrapper copied from a shell command.
34
+ const first = lines[0]?.replace(/^apply_patch\s+/, "");
35
+ if (["<<EOF", "<<'EOF'", '<<"EOF"'].includes(first ?? "") && lines.at(-1)?.endsWith("EOF") && lines.length >= 4)
36
+ return strict(lines.slice(1, -1));
37
+ throw error;
38
+ }
39
+ }
40
+ export function parsePatch(text) {
41
+ const lines = boundaries(text.trim().split(/\r?\n/));
42
+ const hunks = [];
43
+ let mode = "started";
44
+ let updateHeaderLine = 0;
45
+ let environment = false;
46
+ const lastUpdate = () => {
47
+ const hunk = hunks.at(-1);
48
+ return hunk?.kind === "update" ? hunk : undefined;
49
+ };
50
+ const ensureUpdateNotEmpty = (line, number) => {
51
+ const update = mode === "update" ? lastUpdate() : undefined;
52
+ if (!update) return;
53
+ if (update.chunks.length === 0)
54
+ throw new PatchParseError(`Update file hunk for path '${update.path}' is empty`, updateHeaderLine);
55
+ const chunk = update.chunks.at(-1);
56
+ if (chunk.oldLines.length === 0 && chunk.newLines.length === 0)
57
+ throw new PatchParseError(line === END ? "Update hunk does not contain any lines" : UNEXPECTED(line), number);
58
+ };
59
+ const header = (trimmed, number) => {
60
+ if (mode === "started" && trimmed.startsWith(ENVIRONMENT) && hunks.length === 0) {
61
+ if (environment) throw new PatchParseError("apply_patch environment_id cannot be specified more than once");
62
+ if (!trimmed.slice(ENVIRONMENT.length).trim())
63
+ throw new PatchParseError("apply_patch environment_id cannot be empty");
64
+ environment = true;
65
+ return true;
66
+ }
67
+ for (const [marker, kind] of [
68
+ [END, "end"],
69
+ [ADD, "add"],
70
+ [DELETE, "delete"],
71
+ [UPDATE, "update"],
72
+ ]) {
73
+ if (kind === "end" ? trimmed !== END : !trimmed.startsWith(marker)) continue;
74
+ ensureUpdateNotEmpty(trimmed, number);
75
+ const path = trimmed.slice(marker.length);
76
+ if (kind === "end") mode = "ended";
77
+ else if (kind === "add") {
78
+ hunks.push({ kind: "add", path, contents: "" });
79
+ mode = "add";
80
+ } else if (kind === "delete") {
81
+ hunks.push({ kind: "delete", path });
82
+ mode = "delete";
83
+ } else {
84
+ hunks.push({ kind: "update", path, chunks: [] });
85
+ mode = "update";
86
+ updateHeaderLine = number;
87
+ }
88
+ return true;
89
+ }
90
+ return false;
91
+ };
92
+ const chunk = (update) => {
93
+ if (update.chunks.length === 0) update.chunks.push({ oldLines: [], newLines: [], context: [], endOfFile: false });
94
+ return update.chunks.at(-1);
95
+ };
96
+ lines.slice(1).forEach((line, offset) => {
97
+ const number = offset + 2;
98
+ const trimmed = line.trim();
99
+ if (mode === "ended") {
100
+ if (trimmed) throw new PatchParseError(`The last line of the patch must be '${END}'`, number);
101
+ return;
102
+ }
103
+ if (mode === "started") {
104
+ if (!header(trimmed, number))
105
+ throw new PatchParseError(`'${trimmed}' is not a valid hunk header. ${HEADERS}`, number);
106
+ return;
107
+ }
108
+ if (mode === "add") {
109
+ if (header(trimmed, number)) return;
110
+ const add = hunks.at(-1);
111
+ if (line.startsWith("+") && add?.kind === "add") {
112
+ add.contents += `${line.slice(1)}\n`;
113
+ return;
114
+ }
115
+ throw new PatchParseError(`'${trimmed}' is not a valid hunk header. ${HEADERS}`, number);
116
+ }
117
+ if (mode === "delete") {
118
+ if (header(trimmed, number)) return;
119
+ throw new PatchParseError(`'${trimmed}' is not a valid hunk header. ${HEADERS}`, number);
120
+ }
121
+ // Update file.
122
+ const updateLine = line.trimEnd();
123
+ if (header(updateLine, number)) return;
124
+ const update = lastUpdate();
125
+ const current = update.chunks.at(-1);
126
+ if (current?.endOfFile) {
127
+ if (!updateLine) return;
128
+ if (updateLine !== "@@" && !updateLine.startsWith("@@ "))
129
+ throw new PatchParseError(`Expected update hunk to start with a @@ context marker, got: '${line}'`, number);
130
+ }
131
+ if (update.chunks.length === 0 && update.moveTo === undefined && updateLine.startsWith(MOVE)) {
132
+ update.moveTo = updateLine.slice(MOVE.length);
133
+ return;
134
+ }
135
+ const isAnchor = updateLine === "@@" || updateLine.startsWith("@@ ");
136
+ if (isAnchor && current && current.oldLines.length === 0 && current.newLines.length === 0)
137
+ throw new PatchParseError(UNEXPECTED(line), number);
138
+ if (isAnchor) {
139
+ update.chunks.push({
140
+ ...(updateLine === "@@" ? {} : { anchor: updateLine.slice(3) }),
141
+ oldLines: [],
142
+ newLines: [],
143
+ context: [],
144
+ endOfFile: false,
145
+ });
146
+ return;
147
+ }
148
+ if (updateLine === EOF_MARKER) {
149
+ if (!current || (current.oldLines.length === 0 && current.newLines.length === 0))
150
+ throw new PatchParseError("Update hunk does not contain any lines", number);
151
+ current.endOfFile = true;
152
+ return;
153
+ }
154
+ if (line === "" || line.startsWith(" ")) {
155
+ const target = chunk(update);
156
+ target.context.push([target.oldLines.length, target.newLines.length]);
157
+ const text = line === "" ? "" : line.slice(1);
158
+ target.oldLines.push(text);
159
+ target.newLines.push(text);
160
+ return;
161
+ }
162
+ if (line.startsWith("+")) {
163
+ chunk(update).newLines.push(line.slice(1));
164
+ return;
165
+ }
166
+ if (line.startsWith("-")) {
167
+ chunk(update).oldLines.push(line.slice(1));
168
+ return;
169
+ }
170
+ if (current && (current.oldLines.length > 0 || current.newLines.length > 0))
171
+ throw new PatchParseError(`Expected update hunk to start with a @@ context marker, got: '${line}'`, number);
172
+ throw new PatchParseError(UNEXPECTED(line), number);
173
+ });
174
+ // `mode` changes inside the callback above, which the compiler cannot see.
175
+ if (mode !== "ended") throw new PatchParseError(`The last line of the patch must be '${END}'`);
176
+ return hunks;
177
+ }
@@ -0,0 +1,201 @@
1
+ import { mkdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
4
+ import { resolveToolPath } from "../file-state.js";
5
+ import { closestMatch } from "../edit-messages.js";
6
+ import { parsePatch } from "./parser.js";
7
+ import { otherMatches, seekSequence } from "./match.js";
8
+ class PlanError extends Error {}
9
+ async function isFile(path) {
10
+ try {
11
+ return (await stat(path)).isFile();
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+ const MAX_SHOWN_LINES = 20;
17
+ function notFound(path, hunk, oldLines, lines) {
18
+ const subject = `${path} hunk ${hunk}`;
19
+ const match = closestMatch(lines.join("\n"), oldLines.join("\n"));
20
+ if (!match)
21
+ return new PlanError(
22
+ `${subject}: context not found and no similar lines exist in the current file. Read the region you meant, then retry.`,
23
+ );
24
+ return new PlanError(
25
+ [
26
+ `${subject}: context not found. Closest match, lines ${match.start}–${match.end} (current file contents):`,
27
+ ...lines.slice(match.start - 1, Math.min(match.end, match.start - 1 + MAX_SHOWN_LINES)),
28
+ "If these are the lines you meant, retry with context copied exactly from them. If not, read the region you meant, then retry.",
29
+ ].join("\n"),
30
+ );
31
+ }
32
+ /** Split text as it was stored so it can be written back the same way: BOM, line ending, final newline. */
33
+ function splitText(raw) {
34
+ const bom = raw.startsWith("") ? "" : "";
35
+ const body = raw.slice(bom.length);
36
+ const firstLf = body.indexOf("\n");
37
+ const crlf = firstLf > 0 && body[firstLf - 1] === "\r";
38
+ const lf = body.replace(/\r\n/g, "\n");
39
+ const finalNewline = lf === "" || lf.endsWith("\n");
40
+ const lines = lf === "" ? [] : lf.split("\n");
41
+ if (lf.endsWith("\n")) lines.pop();
42
+ return { bom, crlf, finalNewline, lines };
43
+ }
44
+ function joinText(parts, lines) {
45
+ const text = lines.join("\n") + (parts.finalNewline && lines.length > 0 ? "\n" : "");
46
+ return parts.bom + (parts.crlf ? text.replace(/\n/g, "\r\n") : text);
47
+ }
48
+ /** Codex's compute_replacements, keeping the file's own bytes on context lines. */
49
+ function updateLines(path, lines, chunks, notes) {
50
+ const replacements = [];
51
+ let cursor = 0;
52
+ chunks.forEach((chunk, index) => {
53
+ const hunk = index + 1;
54
+ if (chunk.anchor !== undefined) {
55
+ const anchor = seekSequence(lines, [chunk.anchor], cursor, false);
56
+ if (!anchor)
57
+ throw new PlanError(
58
+ `${path} hunk ${hunk}: @@ line not found: '${chunk.anchor}'. Use a line that exists in the file after the previous hunk, or leave the @@ line empty.`,
59
+ );
60
+ cursor = anchor.index + 1;
61
+ }
62
+ if (chunk.oldLines.length === 0) {
63
+ replacements.push({ start: lines.length, length: 0, lines: [...chunk.newLines] });
64
+ return;
65
+ }
66
+ let pattern = chunk.oldLines;
67
+ let replacement = chunk.newLines;
68
+ let found = seekSequence(lines, pattern, cursor, chunk.endOfFile);
69
+ if (!found && pattern.at(-1) === "") {
70
+ // A trailing empty line stands for the file's final newline, which the line list does not hold.
71
+ pattern = pattern.slice(0, -1);
72
+ if (replacement.at(-1) === "") replacement = replacement.slice(0, -1);
73
+ found = seekSequence(lines, pattern, cursor, chunk.endOfFile);
74
+ }
75
+ if (!found) throw notFound(path, hunk, chunk.oldLines, lines);
76
+ const resolved = [...replacement];
77
+ for (const [oldIndex, newIndex] of chunk.context)
78
+ if (oldIndex < pattern.length && newIndex < resolved.length) resolved[newIndex] = lines[found.index + oldIndex];
79
+ const others = otherMatches(lines, pattern, found.index, found.pass);
80
+ if (others.length > 0)
81
+ notes.push(
82
+ `${path} hunk ${hunk}: its lines also match at line ${others.map((i) => i + 1).join(", ")}; it was applied at line ${found.index + 1}.`,
83
+ );
84
+ replacements.push({ start: found.index, length: pattern.length, lines: resolved });
85
+ cursor = found.index + pattern.length;
86
+ });
87
+ const next = [...lines];
88
+ for (const { start, length, lines: added } of [...replacements].sort((a, b) => b.start - a.start))
89
+ next.splice(start, length, ...added);
90
+ return next;
91
+ }
92
+ async function plan(hunks, cwd, files, notes) {
93
+ if (hunks.length === 0) throw new PlanError("The patch changes no files. Add at least one file section, then retry.");
94
+ // Later sections see earlier sections' results, as they would if applied one by one.
95
+ const virtual = new Map();
96
+ const current = async (abs) => {
97
+ if (virtual.has(abs)) return virtual.get(abs);
98
+ if (!(await isFile(abs))) return null;
99
+ const bytes = await readFile(abs);
100
+ const text = bytes.toString("utf8");
101
+ return Buffer.from(text, "utf8").equals(bytes) ? text : "\u0000not-utf8";
102
+ };
103
+ const steps = [];
104
+ for (const hunk of hunks) {
105
+ const abs = resolveToolPath(hunk.path, cwd);
106
+ const existing = await current(abs);
107
+ if (hunk.kind === "add") {
108
+ if (existing !== null && !virtual.has(abs) && !files.wasRead(hunk.path, cwd))
109
+ throw new PlanError(
110
+ `${hunk.path} exists and has not been read in this session. Read ${hunk.path}, then retry the patch.`,
111
+ );
112
+ virtual.set(abs, hunk.contents);
113
+ steps.push({ path: hunk.path, abs, operation: "add", content: hunk.contents });
114
+ continue;
115
+ }
116
+ if (existing === null)
117
+ throw new PlanError(
118
+ hunk.kind === "delete"
119
+ ? `${hunk.path}: file not found; nothing to delete.`
120
+ : `${hunk.path}: file not found. Use *** Add File: ${hunk.path} to create it.`,
121
+ );
122
+ if (existing === "\u0000not-utf8")
123
+ throw new PlanError(`${hunk.path}: not UTF-8 text; apply_patch edits text files only.`);
124
+ if (!virtual.has(abs) && (await files.changedSinceObserved(hunk.path, cwd)))
125
+ notes.push(`${hunk.path} changed since you read it; the patch applied to the current text.`);
126
+ if (hunk.kind === "delete") {
127
+ virtual.set(abs, null);
128
+ steps.push({ path: hunk.path, abs, operation: "delete" });
129
+ continue;
130
+ }
131
+ const parts = splitText(existing);
132
+ const content = joinText(parts, updateLines(hunk.path, parts.lines, hunk.chunks, notes));
133
+ if (hunk.moveTo !== undefined) {
134
+ const toAbs = resolveToolPath(hunk.moveTo, cwd);
135
+ if ((await current(toAbs)) !== null)
136
+ throw new PlanError(`${hunk.moveTo} already exists. Delete or rename it first, or move to another path.`);
137
+ virtual.set(abs, null);
138
+ virtual.set(toAbs, content);
139
+ steps.push({ path: hunk.path, abs, operation: "move", to: hunk.moveTo, toAbs, content });
140
+ continue;
141
+ }
142
+ virtual.set(abs, content);
143
+ steps.push({ path: hunk.path, abs, operation: "update", content });
144
+ }
145
+ return steps;
146
+ }
147
+ async function withLocks(paths, fn) {
148
+ // Sorted, so two patches touching the same files never wait on each other in opposite order.
149
+ const [first, ...rest] = [...new Set(paths)].sort();
150
+ return first === undefined ? fn() : withFileMutationQueue(first, () => withLocks(rest, fn));
151
+ }
152
+ async function write(step) {
153
+ if (step.operation === "delete") return unlink(step.abs);
154
+ const target = step.toAbs ?? step.abs;
155
+ await mkdir(dirname(target), { recursive: true });
156
+ await writeFile(target, step.content, "utf8");
157
+ if (step.operation === "move") await unlink(step.abs);
158
+ }
159
+ /** Plan the whole patch against current files, then write it; a planning failure writes nothing. */
160
+ export async function applyPatch(text, cwd, files) {
161
+ const notes = [];
162
+ let hunks;
163
+ try {
164
+ hunks = parsePatch(text);
165
+ } catch (error) {
166
+ const reason = error instanceof Error ? error.message : String(error);
167
+ return { status: "not_applied", files: [], notes, error: `Invalid patch: ${reason}. Fix the patch, then retry.` };
168
+ }
169
+ const paths = hunks.flatMap((hunk) => [
170
+ resolveToolPath(hunk.path, cwd),
171
+ ...(hunk.kind === "update" && hunk.moveTo !== undefined ? [resolveToolPath(hunk.moveTo, cwd)] : []),
172
+ ]);
173
+ return withLocks(paths, async () => {
174
+ let steps;
175
+ try {
176
+ steps = await plan(hunks, cwd, files, notes);
177
+ } catch (error) {
178
+ if (!(error instanceof PlanError)) throw error;
179
+ return { status: "not_applied", files: [], notes, error: error.message };
180
+ }
181
+ const changes = steps.map((step) => ({
182
+ path: step.path,
183
+ operation: step.operation,
184
+ ...(step.to === undefined ? {} : { to: step.to }),
185
+ outcome: "not_written",
186
+ }));
187
+ for (const [index, step] of steps.entries()) {
188
+ try {
189
+ await write(step);
190
+ changes[index].outcome = "written";
191
+ } catch (error) {
192
+ changes[index].outcome = "failed";
193
+ changes[index].error = error instanceof Error ? error.message : String(error);
194
+ // A failed write may already have changed its target, so this is never reported as "not applied".
195
+ // Nothing after it is attempted.
196
+ return { status: "partial", files: changes, notes };
197
+ }
198
+ }
199
+ return { status: "applied", files: changes, notes };
200
+ });
201
+ }