@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
@@ -1,1447 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { execFile } from "node:child_process";
3
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import test from "node:test";
7
- import { promisify } from "node:util";
8
- import { SessionManager } from "@earendil-works/pi-coding-agent";
9
-
10
- import freeflowExtension from "../../dist/index.js";
11
- import { handleFreeflowCommand } from "../../dist/settings/settings-ui.js";
12
- import {
13
- readCapabilityState,
14
- readFreeflowConfigLayers,
15
- resetSessionOverrides,
16
- restoreSessionOverrides,
17
- setFreeflowStatus,
18
- setSessionCoreOverride,
19
- } from "../../dist/runtime/runtime-context.js";
20
- import { PIFLOW_HOST } from "../fixtures/pi-host.js";
21
- import { matches } from "../../dist/cognitive-routing-v2/schemas.js";
22
-
23
- const execFileAsync = promisify(execFile);
24
-
25
- function loadExtension(extension = freeflowExtension, host = PIFLOW_HOST, runtimeApi = {}) {
26
- const handlers = new Map();
27
- const tools = [];
28
- const commands = [];
29
- const shortcuts = [];
30
- const entries = [];
31
- const sentMessages = [];
32
- const sentMessageOptions = [];
33
- let activeToolNames;
34
- const pi = {
35
- host,
36
- registerTool(tool) {
37
- const index = tools.findIndex((existing) => existing.name === tool.name);
38
- if (index >= 0) tools[index] = tool;
39
- else tools.push(tool);
40
- },
41
- registerCommand(name, definition) {
42
- commands.push({ name, definition });
43
- },
44
- registerShortcut(shortcut, definition) {
45
- shortcuts.push({ shortcut, definition });
46
- },
47
- on(event, handler) {
48
- handlers.set(event, handler);
49
- },
50
- appendEntry(customType, data) {
51
- entries.push({ customType, data });
52
- },
53
- sendUserMessage(message, options) {
54
- sentMessages.push(message);
55
- sentMessageOptions.push(options);
56
- },
57
- getAllTools() {
58
- return tools.map((tool) => ({ name: tool.name, sourceInfo: { source: "extension" } }));
59
- },
60
- getActiveTools() {
61
- return activeToolNames ?? tools.map((tool) => tool.name);
62
- },
63
- setActiveTools(names) {
64
- activeToolNames = [...names];
65
- },
66
- };
67
- Object.assign(pi, runtimeApi);
68
-
69
- extension(pi);
70
- return {
71
- pi,
72
- handlers,
73
- tools,
74
- commands,
75
- shortcuts,
76
- entries,
77
- sentMessages,
78
- sentMessageOptions,
79
- activeToolNames: () => activeToolNames ?? tools.map((tool) => tool.name),
80
- };
81
- }
82
-
83
- function context(cwd = process.cwd(), sessionEntries = [], activeSessionEntries = sessionEntries) {
84
- const notifications = [];
85
- const reloads = [];
86
- const statuses = [];
87
- return {
88
- cwd,
89
- notifications,
90
- reloads,
91
- statuses,
92
- async reload() {
93
- reloads.push(true);
94
- },
95
- sessionManager: {
96
- getEntries() {
97
- return sessionEntries;
98
- },
99
- getBranch() {
100
- return activeSessionEntries;
101
- },
102
- buildContextEntries() {
103
- return activeSessionEntries;
104
- },
105
- },
106
- ui: {
107
- setStatus(name, value) {
108
- statuses.push({ name, value });
109
- },
110
- notify(message, level) {
111
- notifications.push({ message, level });
112
- },
113
- },
114
- };
115
- }
116
-
117
- const testTheme = {
118
- fg(_color, text) {
119
- return text;
120
- },
121
- bg(_color, text) {
122
- return text;
123
- },
124
- bold(text) {
125
- return text;
126
- },
127
- };
128
-
129
- const readyFreeflowContext = {
130
- corePrompt: "core",
131
- interactionContractPrompt: "interaction contract",
132
- };
133
-
134
- function renderText(component, width = 120) {
135
- return component.render(width).join("\n");
136
- }
137
-
138
- async function configuredRepo(config = {}) {
139
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-integration-"));
140
- await mkdir(join(cwd, ".freeflow"));
141
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify(config, null, 2), "utf8");
142
- return cwd;
143
- }
144
-
145
- function lastRuntimeState(messages) {
146
- return messages.findLast((message) => message.customType === "freeflow-runtime-state");
147
- }
148
-
149
- test("keeps Runtime State before the latest user message during context refreshes", async () => {
150
- const cwd = await configuredRepo();
151
- try {
152
- const { handlers } = loadExtension();
153
- const ctx = context(cwd);
154
- await handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
155
-
156
- const conversation = [
157
- { role: "user", content: "release 0.6.1" },
158
- { role: "assistant", content: "delegating" },
159
- { role: "toolResult", toolName: "freeflow_delegate", content: [] },
160
- ];
161
- const first = await handlers.get("context")({ messages: conversation }, ctx);
162
- const firstUserIndex = first.messages.findIndex((message) => message.role === "user");
163
- const firstRuntimeIndex = first.messages.findIndex((message) => message.customType === "freeflow-runtime-state");
164
- assert.equal(firstRuntimeIndex, firstUserIndex - 1);
165
- assert.equal(first.messages.at(-1).role, "toolResult");
166
-
167
- await handlers.get("session_compact")({ type: "session_compact", reason: "threshold" }, ctx);
168
- const refreshed = await handlers.get("context")({ messages: first.messages }, ctx);
169
- const refreshedUserIndex = refreshed.messages.findIndex((message) => message.role === "user");
170
- const refreshedRuntimeIndex = refreshed.messages.findIndex(
171
- (message) => message.customType === "freeflow-runtime-state",
172
- );
173
- assert.equal(refreshedRuntimeIndex, refreshedUserIndex - 1);
174
- assert.equal(refreshed.messages.at(-1).role, "toolResult");
175
-
176
- const interrupted = await handlers.get("context")(
177
- {
178
- messages: [...refreshed.messages, { role: "user", content: "stop" }],
179
- },
180
- ctx,
181
- );
182
- const latestUserIndex = interrupted.messages.findLastIndex((message) => message.role === "user");
183
- const latestRuntimeIndex = interrupted.messages.findIndex(
184
- (message) => message.customType === "freeflow-runtime-state",
185
- );
186
- assert.equal(latestRuntimeIndex, latestUserIndex - 1);
187
- assert.equal(interrupted.messages.at(-1).content, "stop");
188
- } finally {
189
- await rm(cwd, { recursive: true, force: true });
190
- }
191
- });
192
-
193
- test("new routing tools and projection state follow the configured contract", async () => {
194
- const cases = [
195
- { projection: false, expectedMode: "disabled", exposesProjection: false },
196
- // Projection can be effective while the Coordinator correctly lacks Executor-only tools.
197
- { projection: true, expectedMode: "enabled", exposesProjection: false },
198
- ];
199
-
200
- for (const scenario of cases) {
201
- const cwd = await configuredRepo({
202
- cognitiveRouting: {
203
- enabled: true,
204
- projection: scenario.projection,
205
- profiles: {
206
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
207
- executor: { provider: "test", model: "model-b", thinking: "high" },
208
- },
209
- },
210
- });
211
- try {
212
- let liveContext;
213
- const loaded = loadExtension(freeflowExtension, null, {
214
- appendEntry(customType, data) {
215
- liveContext.sessionManager.appendCustomEntry(customType, data);
216
- },
217
- async setModel(model) {
218
- liveContext.model = model;
219
- return true;
220
- },
221
- setThinkingLevel(level) {
222
- liveContext.thinkingLevel = level;
223
- },
224
- });
225
- const ctx = context(cwd);
226
- liveContext = ctx;
227
- ctx.model = { provider: "test", id: "return" };
228
- ctx.thinkingLevel = "medium";
229
- ctx.modelRegistry = cognitiveRoutingModelRegistry();
230
- ctx.isIdle = () => true;
231
- ctx.sessionManager = SessionManager.create(cwd, join(cwd, "sessions"));
232
- // Reconciliation needs genuine persisted ancestry, not a fake mutable branch array.
233
- ctx.sessionManager.appendMessage({
234
- role: "assistant",
235
- content: [{ type: "text", text: "fixture seed" }],
236
- timestamp: 1,
237
- stopReason: "stop",
238
- });
239
- await loaded.handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
240
- await loaded.handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
241
-
242
- const toolNames = loaded.tools.map((tool) => tool.name);
243
- assert.deepEqual(
244
- toolNames.filter((name) =>
245
- ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"].includes(name),
246
- ),
247
- ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"],
248
- );
249
- const delegate = loaded.tools.find((tool) => tool.name === "freeflow_delegate");
250
- const returning = loaded.tools.find((tool) => tool.name === "freeflow_return");
251
- assert.deepEqual(
252
- delegate.parameters.oneOf.map((branch) => branch.properties.operation.enum[0]),
253
- ["assign", "replace"],
254
- );
255
- assert.deepEqual(delegate.parameters.properties.worker.enum, ["helper", "executor"]);
256
- assert.equal(
257
- delegate.parameters.oneOf.every((branch) => !branch.required.includes("worker")),
258
- true,
259
- );
260
- assert.deepEqual(
261
- returning.parameters.oneOf.map((branch) => branch.properties.operation.enum[0]),
262
- ["submit", "supplement", "retry"],
263
- );
264
- assert.equal(loaded.activeToolNames().includes("freeflow_project"), true);
265
-
266
- const providerContext = await loaded.handlers.get("context")({ messages: [] }, ctx);
267
- assert.match(providerContext.messages[0].content, /Delegation: `executor`/);
268
- assert.match(providerContext.messages[0].content, new RegExp("Projection: `" + scenario.expectedMode + "`"));
269
-
270
- await loaded.commands.find((command) => command.name === "freeflow").definition.handler("profile executor", ctx);
271
- const manualContext = await loaded.handlers.get("context")({ messages: [] }, ctx);
272
- assert.match(
273
- manualContext.messages.findLast((message) => message.customType === "freeflow-runtime-state").content,
274
- new RegExp("Projection: `" + (scenario.projection ? "manual-bypass" : "disabled") + "`"),
275
- );
276
- assert.equal(loaded.activeToolNames().includes("freeflow_project"), true);
277
- } finally {
278
- await rm(cwd, { recursive: true, force: true });
279
- }
280
- }
281
- });
282
-
283
- test("Pi describes strict evidence-selection operation shapes", () => {
284
- const { tools } = loadExtension();
285
- const project = tools.find((tool) => tool.name === "freeflow_project");
286
- assert.ok(project);
287
- assert.deepEqual(
288
- project.parameters.oneOf.map((branch) => branch.properties.operation.enum[0]),
289
- ["inspect", "add", "remove"],
290
- );
291
- assert.deepEqual(Object.keys(project.parameters.oneOf[1].properties), ["operation", "refs"]);
292
- assert.deepEqual(Object.keys(project.parameters.oneOf[2].properties), ["operation", "refs", "reason"]);
293
- assert.match(project.description, /reason is remove-only/i);
294
- assert.match(project.promptGuidelines.join(" "), /reason is required only for remove and invalid for add\/inspect/i);
295
- assert.match(
296
- project.promptGuidelines.join(" "),
297
- /for add.*never add refs marked not offered.*for remove.*currently selected or unresolved/i,
298
- );
299
- assert.match(project.promptGuidelines.join(" "), /add exact eligible visible refs directly/i);
300
- assert.doesNotMatch(project.promptGuidelines.join(" "), /inspect first/i);
301
- assert.match(project.parameters.properties.reason.description, /only for remove/i);
302
- assert.match(project.parameters.oneOf[1].properties.refs.description, /never add a ref marked not offered/i);
303
- assert.match(
304
- project.parameters.oneOf[2].properties.refs.description,
305
- /currently selected or unresolved.*non-selectable ref may be removed/i,
306
- );
307
- assert.match(
308
- project.parameters.properties.refs.description,
309
- /For add.*eligible.*For remove.*currently selected or unresolved/i,
310
- );
311
-
312
- assert.equal(
313
- matches({ operation: "add", refs: ["ctx:example"], reason: "selection explanation" }, project.parameters),
314
- false,
315
- );
316
- assert.equal(matches({ operation: "add", refs: ["ctx:example"] }, project.parameters), true);
317
- assert.equal(
318
- matches({ operation: "remove", refs: ["ctx:example"], reason: "selection explanation" }, project.parameters),
319
- true,
320
- );
321
- });
322
-
323
- test("Pi registers the remaining Freeflow commands without mode controls or retired router tools", () => {
324
- const { commands, shortcuts, tools } = loadExtension();
325
- const commandNames = commands.map((command) => command.name);
326
- const toolNames = tools.map((tool) => tool.name);
327
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
328
-
329
- assert.ok(commandNames.includes("freeflow"));
330
- assert.ok(!commandNames.includes("output-router"));
331
- for (const command of ["discuss", "track-work", "execute-work"]) assert.ok(commandNames.includes(command));
332
- assert.ok(!commandNames.includes("discover"));
333
- assert.ok(!commandNames.includes("execute-plan"));
334
- assert.ok(!commandNames.includes("workflow"));
335
- assert.deepEqual(shortcuts, []);
336
- assert.ok(tools.some((tool) => tool.name === "freeflow_context"));
337
- assert.deepEqual(
338
- toolNames.filter((name) => name.startsWith("freeflow_")),
339
- ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project", "freeflow_context"],
340
- );
341
- assert.ok(!toolNames.includes("freeflow_switch_profile"));
342
- assert.ok(!toolNames.includes("freeflow_cognitive_routing_history"));
343
- assert.ok(freeflowCommand);
344
- assert.ok(!freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "mode"));
345
- assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("mode "), []);
346
- assert.ok(
347
- !toolNames.some((name) => ["freeflow_status", "freeflow_search", "freeflow_run", "freeflow_batch"].includes(name)),
348
- );
349
- });
350
-
351
- test("PiFlow keeps Cognitive Routing unavailable while exposing its configuration", async () => {
352
- const cwd = await configuredRepo({
353
- cognitiveRouting: {
354
- enabled: true,
355
- profiles: {
356
- coordinator: { provider: "test", model: "coordinator", thinking: "high" },
357
- executor: { provider: "test", model: "executor", thinking: "max" },
358
- },
359
- },
360
- });
361
- try {
362
- const { commands, handlers, shortcuts, tools, activeToolNames } = loadExtension(freeflowExtension, PIFLOW_HOST);
363
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
364
- assert.ok(freeflowCommand);
365
- assert.deepEqual(shortcuts, []);
366
- assert.deepEqual(
367
- tools.filter((tool) => tool.name.startsWith("freeflow_")).map((tool) => tool.name),
368
- ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project", "freeflow_context"],
369
- );
370
- assert.ok(!tools.some((tool) => tool.name === "freeflow_switch_profile"));
371
- assert.ok(!tools.some((tool) => tool.name === "freeflow_cognitive_routing_history"));
372
- assert.ok(!freeflowCommand.definition.getArgumentCompletions("").some((item) => item.value === "profile"));
373
- const capabilityState = await readCapabilityState(cwd, undefined, undefined);
374
- assert.equal(capabilityState.cognitiveRouting.enabled, true);
375
- assert.equal(capabilityState.cognitiveRouting.effective, false);
376
- assert.equal(capabilityState.cognitiveRouting.blockingReason.code, "host_unsupported");
377
-
378
- const ctx = context(cwd);
379
- await handlers.get("session_start")({ type: "session_start" }, ctx);
380
- assert.match(ctx.statuses.at(-1).value, /cognitive blocked ·/);
381
- assert.ok(activeToolNames().includes("freeflow_delegate"));
382
- const before = await handlers.get("before_agent_start")({ systemPrompt: "base" }, ctx);
383
- assert.match(before.systemPrompt, /guidance is dormant/);
384
- } finally {
385
- await rm(cwd, { recursive: true, force: true });
386
- }
387
- });
388
-
389
- test("normal Pi settings expose active Cognitive Routing configuration", async () => {
390
- const cwd = await configuredRepo({
391
- cognitiveRouting: {
392
- enabled: true,
393
- profiles: {
394
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
395
- executor: { provider: "test", model: "model-b", thinking: "high" },
396
- },
397
- },
398
- });
399
- try {
400
- const { commands } = loadExtension(freeflowExtension, null, {
401
- appendEntry() {},
402
- async setModel() {
403
- return true;
404
- },
405
- setThinkingLevel() {},
406
- });
407
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
408
- assert.ok(freeflowCommand);
409
- const settingsCtx = context(cwd);
410
- settingsCtx.mode = "tui";
411
- settingsCtx.hasUI = true;
412
- settingsCtx.isIdle = () => true;
413
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
414
- settingsCtx.ui.custom = async (factory) => {
415
- const component = factory({ requestRender() {} }, testTheme, {}, () => {});
416
- const rootText = renderText(component);
417
- assert.match(rootText, /Cognitive Routing\s+enabled \(6\) active/);
418
- assert.doesNotMatch(rootText, /PiFlow only/);
419
- return undefined;
420
- };
421
-
422
- await freeflowCommand.definition.handler("settings", settingsCtx);
423
- } finally {
424
- await rm(cwd, { recursive: true, force: true });
425
- }
426
- });
427
-
428
- test("Pi settings persist delegation mode without rewriting complete presets", async () => {
429
- const initial = {
430
- cognitiveRouting: {
431
- enabled: true,
432
- profiles: {
433
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
434
- helper: { provider: "test", model: "model-b", thinking: "high" },
435
- executor: { provider: "test", model: "model-b", thinking: "max" },
436
- },
437
- },
438
- };
439
- const cwd = await configuredRepo(initial);
440
- try {
441
- const { commands } = loadExtension(freeflowExtension, null, {
442
- appendEntry() {},
443
- async setModel() {
444
- return true;
445
- },
446
- setThinkingLevel() {},
447
- });
448
- const command = commands.find((candidate) => candidate.name === "freeflow");
449
- const settingsCtx = context(cwd);
450
- settingsCtx.isIdle = () => true;
451
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
452
- settingsCtx.ui.custom = async (factory) => {
453
- let result;
454
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
455
- result = value;
456
- });
457
- component.handleInput("\u001b[B");
458
- component.handleInput("\r");
459
- component.handleInput("\u001b[B");
460
- component.handleInput("\r");
461
- component.handleInput("\u001b[B");
462
- component.handleInput("\u001b[B");
463
- component.handleInput("\r");
464
- await component.waitForWrites();
465
- component.handleInput("\u001b");
466
- component.handleInput("\u001b");
467
- return result;
468
- };
469
- await command.definition.handler("settings repo", settingsCtx);
470
- const saved = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
471
- assert.equal(saved.cognitiveRouting.delegation, "both");
472
- assert.deepEqual(saved.cognitiveRouting.profiles, initial.cognitiveRouting.profiles);
473
- } finally {
474
- await rm(cwd, { recursive: true, force: true });
475
- }
476
- });
477
-
478
- test("personal delegation override and inherit preserve repository mode and other local presets", async () => {
479
- const repository = {
480
- cognitiveRouting: {
481
- enabled: true,
482
- delegation: "helper",
483
- profiles: {
484
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
485
- helper: { provider: "test", model: "model-b", thinking: "high" },
486
- executor: { provider: "test", model: "model-b", thinking: "max" },
487
- },
488
- },
489
- };
490
- const local = {
491
- contextVirtualization: true,
492
- cognitiveRouting: {
493
- profiles: {
494
- helper: { provider: "test", model: "model-b", thinking: "medium" },
495
- },
496
- },
497
- };
498
- const cwd = await configuredRepo(repository);
499
- const repositoryPath = join(cwd, ".freeflow/config.json");
500
- const localPath = join(cwd, ".freeflow/local.json");
501
- await writeFile(localPath, JSON.stringify(local, null, 2), "utf8");
502
- try {
503
- const { commands, pi } = loadExtension(freeflowExtension, null, {
504
- appendEntry() {},
505
- async setModel() {
506
- return true;
507
- },
508
- setThinkingLevel() {},
509
- });
510
- const command = commands.find((candidate) => candidate.name === "freeflow");
511
- const settingsCtx = context(cwd);
512
- settingsCtx.isIdle = () => true;
513
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
514
-
515
- const chooseDelegation = async (direction, commit = true) => {
516
- settingsCtx.ui.custom = async (factory) => {
517
- let result;
518
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
519
- result = value;
520
- });
521
- component.handleInput("\u001b[B");
522
- component.handleInput("\r");
523
- component.handleInput("\u001b[B");
524
- component.handleInput("\r");
525
- component.handleInput(direction);
526
- component.handleInput(commit ? "\r" : "\u001b");
527
- if (commit) await component.waitForWrites();
528
- component.handleInput("\u001b");
529
- component.handleInput("\u001b");
530
- return result;
531
- };
532
- await command.definition.handler("settings", settingsCtx);
533
- };
534
-
535
- await chooseDelegation("\u001b[B");
536
- let saved = JSON.parse(await readFile(localPath, "utf8"));
537
- assert.equal(saved.cognitiveRouting.delegation, "executor");
538
- assert.deepEqual(saved.cognitiveRouting.profiles, local.cognitiveRouting.profiles);
539
- assert.deepEqual(JSON.parse(await readFile(repositoryPath, "utf8")), repository);
540
- assert.equal((await readCapabilityState(cwd, settingsCtx, pi.host)).cognitiveRouting.delegation, "executor");
541
-
542
- await chooseDelegation("\u001b[A");
543
- saved = JSON.parse(await readFile(localPath, "utf8"));
544
- assert.equal(saved.cognitiveRouting.delegation, undefined);
545
- assert.deepEqual(saved.cognitiveRouting.profiles, local.cognitiveRouting.profiles);
546
- assert.equal((await readCapabilityState(cwd, settingsCtx, pi.host)).cognitiveRouting.delegation, "helper");
547
-
548
- const beforeCancel = await readFile(localPath, "utf8");
549
- await chooseDelegation("\u001b[B", false);
550
- assert.equal(await readFile(localPath, "utf8"), beforeCancel);
551
- assert.deepEqual(JSON.parse(await readFile(repositoryPath, "utf8")), repository);
552
- } finally {
553
- await rm(cwd, { recursive: true, force: true });
554
- }
555
- });
556
-
557
- test("both mode session settings expose Helper and both existing profile presets", async () => {
558
- const cwd = await configuredRepo({
559
- cognitiveRouting: {
560
- enabled: true,
561
- delegation: "both",
562
- profiles: {
563
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
564
- helper: { provider: "test", model: "model-b", thinking: "high" },
565
- executor: { provider: "test", model: "model-b", thinking: "max" },
566
- },
567
- },
568
- });
569
- try {
570
- const { commands } = loadExtension(freeflowExtension, null, {
571
- appendEntry() {},
572
- async setModel() {
573
- return true;
574
- },
575
- setThinkingLevel() {},
576
- });
577
- const command = commands.find((candidate) => candidate.name === "freeflow");
578
- const settingsCtx = context(cwd);
579
- settingsCtx.isIdle = () => true;
580
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
581
- settingsCtx.ui.custom = async (factory) => {
582
- let result;
583
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
584
- result = value;
585
- });
586
- component.handleInput("\u001b[B");
587
- component.handleInput("\u001b[B");
588
- component.handleInput("\r");
589
- const presets = component.render(180).join("\n");
590
- assert.match(presets, /Coordinator preset/);
591
- assert.match(presets, /Helper preset/);
592
- assert.match(presets, /Executor preset/);
593
- result = { changed: false };
594
- return result;
595
- };
596
- await command.definition.handler("settings session", settingsCtx);
597
- } finally {
598
- await rm(cwd, { recursive: true, force: true });
599
- }
600
- });
601
-
602
- test("Pi statusline reports only dynamic Cognitive Routing and context state", () => {
603
- const ctx = context();
604
- setFreeflowStatus(
605
- ctx,
606
- {
607
- configured: true,
608
- enabled: true,
609
- configSources: { enabled: "builtin" },
610
- contextVirtualization: { effective: true },
611
- conversationHistory: { effective: false },
612
- cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
613
- },
614
- { effective: true, activeProfile: "executor", controlMode: "automatic" },
615
- readyFreeflowContext,
616
- );
617
- assert.equal(ctx.statuses.at(-1).value, "freeflow: executor · automatic · context");
618
- assert.doesNotMatch(ctx.statuses.at(-1).value, /interaction|workflow|mode|skills/i);
619
- });
620
-
621
- test("Pi statusline defaults pending activation to the Coordinator profile", () => {
622
- const ctx = context();
623
- setFreeflowStatus(
624
- ctx,
625
- {
626
- configured: true,
627
- enabled: true,
628
- configSources: { enabled: "builtin" },
629
- contextVirtualization: { effective: false },
630
- conversationHistory: { effective: false },
631
- cognitiveRouting: {
632
- enabled: true,
633
- effective: true,
634
- blockingReason: null,
635
- },
636
- },
637
- undefined,
638
- readyFreeflowContext,
639
- { cognitiveRoutingStartupPending: true },
640
- );
641
- assert.equal(ctx.statuses.at(-1).value, "freeflow: coordinator · pending");
642
- });
643
-
644
- test("Pi statusline keeps an inactive Cognitive Routing runtime blocked", () => {
645
- const ctx = context();
646
- setFreeflowStatus(
647
- ctx,
648
- {
649
- configured: true,
650
- enabled: true,
651
- configSources: { enabled: "builtin" },
652
- contextVirtualization: { effective: false },
653
- conversationHistory: { effective: false },
654
- cognitiveRouting: { enabled: true, effective: true, blockingReason: null },
655
- },
656
- { effective: false, activeProfile: "coordinator", controlMode: "automatic" },
657
- readyFreeflowContext,
658
- { cognitiveRoutingStartupPending: true },
659
- );
660
- assert.equal(ctx.statuses.at(-1).value, "freeflow: cognitive blocked · runtime_inactive");
661
- });
662
-
663
- test("PiFlow keeps configured routing unavailable before the first prompt", async () => {
664
- const cwd = await configuredRepo({
665
- cognitiveRouting: {
666
- enabled: true,
667
- profiles: {
668
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
669
- executor: { provider: "test", model: "model-b", thinking: "high" },
670
- },
671
- },
672
- });
673
- try {
674
- const { handlers } = loadExtension();
675
- const ctx = context(cwd);
676
- ctx.modelRegistry = cognitiveRoutingModelRegistry();
677
- await handlers.get("session_start")({ type: "session_start", reason: "startup" }, ctx);
678
- assert.match(ctx.statuses.at(-1).value, /cognitive blocked ·/);
679
- } finally {
680
- await rm(cwd, { recursive: true, force: true });
681
- }
682
- });
683
-
684
- test("Pi exposes bypass scope argument completions", () => {
685
- const { commands } = loadExtension();
686
- const bypassCommand = commands.find((command) => command.name === "bypass");
687
- assert.ok(bypassCommand);
688
- assert.deepEqual(bypassCommand.definition.getArgumentCompletions(""), [
689
- { value: "next", label: "next", description: "Skip one optional step" },
690
- { value: "task", label: "task", description: "Reduce optional pressure for the current task" },
691
- ]);
692
- });
693
-
694
- test("Pi describes the mode-free Freeflow argument surface and manual profile controls", () => {
695
- const { commands } = loadExtension(
696
- freeflowExtension,
697
- {},
698
- {
699
- appendEntry() {},
700
- setModel() {},
701
- setThinkingLevel() {},
702
- },
703
- );
704
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
705
- assert.ok(freeflowCommand);
706
- assert.deepEqual(freeflowCommand.definition.getArgumentCompletions(""), [
707
- { value: "settings", label: "settings", description: "Open personal override settings" },
708
- { value: "status", label: "status", description: "Show effective Freeflow state" },
709
- { value: "context", label: "context", description: "Inspect Freeflow Context" },
710
- { value: "profile", label: "profile", description: "Hold or release Cognitive Routing profile control" },
711
- { value: "resume", label: "resume", description: "Resume the current saved routing responsibility" },
712
- { value: "enable", label: "enable", description: "Enable Freeflow for this repository" },
713
- { value: "disable", label: "disable", description: "Disable Freeflow for this repository" },
714
- ]);
715
- assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("profile "), [
716
- { value: "profile coordinator", label: "coordinator", description: "Hold Coordinator manually" },
717
- { value: "profile helper", label: "helper", description: "Hold Helper manually when enabled" },
718
- { value: "profile executor", label: "executor", description: "Hold Executor manually when enabled" },
719
- { value: "profile auto", label: "auto", description: "Return to automatic Coordinator reconciliation" },
720
- { value: "profile history", label: "history", description: "Read routing observations" },
721
- ]);
722
- assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("mode "), []);
723
- assert.deepEqual(freeflowCommand.definition.getArgumentCompletions("context "), [
724
- { value: "context status", label: "status", description: "Show Freeflow Context state" },
725
- { value: "context list", label: "list", description: "List archived context projections" },
726
- { value: "context restore", label: "restore", description: "Restore one or more context references" },
727
- { value: "context reset all", label: "reset all", description: "Reset projection decisions on the active branch" },
728
- ]);
729
- });
730
-
731
- test("Pi exposes 24 base skills without TDD, a mode skill, or compatibility aliases", async () => {
732
- const cwd = await configuredRepo();
733
- try {
734
- const { handlers, commands, sentMessages, sentMessageOptions } = loadExtension();
735
- const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
736
- const skillNames = resources.skillPaths
737
- .filter((path) => path.includes("/skills/"))
738
- .map((path) => {
739
- const match = path.match(/[\\/]skills[\\/]([^\\/]+)\/SKILL\.md$/);
740
- assert.ok(match, `unexpected skill path: ${path}`);
741
- return match[1];
742
- });
743
- assert.equal(skillNames.length, 24);
744
- assert.ok(skillNames.includes("workflow"));
745
- assert.ok(!skillNames.includes("tdd"));
746
- assert.ok(!skillNames.includes("mode-contract"));
747
- assert.ok(!skillNames.includes("discover"));
748
- assert.ok(!skillNames.includes("execute-plan"));
749
-
750
- for (const [commandName, expectedSkill] of [
751
- ["action-selection", "action-selection"],
752
- ["discuss", "discuss"],
753
- ["track-work", "track-work"],
754
- ["execute-work", "execute-work"],
755
- ]) {
756
- const command = commands.find((candidate) => candidate.name === commandName);
757
- assert.ok(command);
758
- await command.definition.handler(undefined, context(cwd));
759
- assert.equal(sentMessages.at(-1), `/skill:${expectedSkill}`);
760
- assert.deepEqual(sentMessageOptions.at(-1), { expandPromptTemplates: true });
761
- }
762
- } finally {
763
- await rm(cwd, { recursive: true, force: true });
764
- }
765
- });
766
-
767
- test("Pi expands setup-freeflow when dispatching its unconfigured skill command", async () => {
768
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-setup-dispatch-"));
769
- try {
770
- const { commands, sentMessages, sentMessageOptions } = loadExtension();
771
- const setup = commands.find((command) => command.name === "setup-freeflow");
772
- assert.ok(setup);
773
-
774
- await setup.definition.handler(undefined, context(cwd));
775
-
776
- assert.equal(sentMessages.at(-1), "/skill:setup-freeflow");
777
- assert.deepEqual(sentMessageOptions.at(-1), { expandPromptTemplates: true });
778
- } finally {
779
- await rm(cwd, { recursive: true, force: true });
780
- }
781
- });
782
-
783
- test("Pi keeps Freeflow inactive until repository activation exists", async () => {
784
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-missing-setup-"));
785
- try {
786
- const { handlers } = loadExtension();
787
- const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
788
- assert.equal(resources.skillPaths.length, 27);
789
- assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/setup-freeflow/SKILL.md")));
790
- const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, context(cwd));
791
- assert.ok(result.systemPrompt.startsWith("base prompt\n\n"));
792
- assert.match(result.systemPrompt, /guidance is dormant/);
793
- } finally {
794
- await rm(cwd, { recursive: true, force: true });
795
- }
796
- });
797
-
798
- test("Pi rejects obsolete modes and removed core toggles in configuration", async () => {
799
- for (const config of [{ defaultMode: "workflow" }, { interactionContract: false }, { skills: { enabled: false } }]) {
800
- const cwd = await configuredRepo(config);
801
- try {
802
- const layers = await readFreeflowConfigLayers(cwd);
803
- assert.equal(layers.configured, false);
804
- assert.equal(layers.repository.valid, false);
805
- assert.match(layers.parseError, /unsupported top-level config key/);
806
- } finally {
807
- await rm(cwd, { recursive: true, force: true });
808
- }
809
- }
810
- });
811
-
812
- test("Pi treats invalid configuration as inactive", async () => {
813
- const cwd = await configuredRepo({ enabled: "false" });
814
- try {
815
- const { handlers, commands } = loadExtension();
816
- const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
817
- assert.equal(resources.skillPaths.length, 27);
818
- assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/setup-freeflow/SKILL.md")));
819
- const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, context(cwd));
820
- assert.ok(result.systemPrompt.startsWith("base prompt\n\n"));
821
- assert.match(result.systemPrompt, /guidance is dormant/);
822
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
823
- const statusCtx = context(cwd);
824
- await freeflowCommand.definition.handler("status", statusCtx);
825
- assert.match(statusCtx.notifications.at(-1).message, /invalid config/);
826
- } finally {
827
- await rm(cwd, { recursive: true, force: true });
828
- }
829
- });
830
-
831
- test("Pi resolves only the remaining layered core values", async () => {
832
- const cwd = await configuredRepo();
833
- try {
834
- await writeFile(
835
- join(cwd, ".freeflow/local.json"),
836
- JSON.stringify({ enabled: false, contextVirtualization: true }, null, 2),
837
- "utf8",
838
- );
839
- const layers = await readFreeflowConfigLayers(cwd);
840
- assert.deepEqual(layers.coreConfig, {
841
- enabled: false,
842
- contextVirtualization: true,
843
- conversationHistory: false,
844
- });
845
- assert.deepEqual(layers.sources, {
846
- enabled: "local",
847
- contextVirtualization: "local",
848
- conversationHistory: "builtin",
849
- });
850
- const state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
851
- assert.equal(state.enabled, false);
852
- assert.equal("skills" in state, false);
853
- assert.equal("interactionContract" in state, false);
854
- } finally {
855
- await rm(cwd, { recursive: true, force: true });
856
- }
857
- });
858
-
859
- test("Pi session enablement cannot bypass repository activation", async () => {
860
- const cwd = await mkdtemp(join(tmpdir(), "freeflow-pi-session-activation-"));
861
- await mkdir(join(cwd, ".freeflow"));
862
- const { pi } = loadExtension();
863
- const ctx = context(cwd);
864
- try {
865
- await setSessionCoreOverride("enabled", true, ctx, pi);
866
- let state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
867
- assert.equal(state.configured, false);
868
- assert.equal(state.enabled, false);
869
-
870
- await writeFile(join(cwd, ".freeflow/config.json"), JSON.stringify({ enabled: false }), "utf8");
871
- state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
872
- assert.equal(state.configured, true);
873
- assert.equal(state.enabled, true);
874
- assert.equal(state.configSources.enabled, "session");
875
- } finally {
876
- await resetSessionOverrides(ctx, pi);
877
- await rm(cwd, { recursive: true, force: true });
878
- }
879
- });
880
-
881
- test("Pi restores remaining session overrides from the active branch and ignores old mode entries", async () => {
882
- const cwd = await configuredRepo();
883
- try {
884
- const activeBranchEntries = [
885
- { type: "custom", customType: "freeflow-mode", data: { currentMode: "conversation" } },
886
- {
887
- type: "custom",
888
- customType: "freeflow-session-overrides",
889
- data: { overrides: { enabled: false, contextVirtualization: true } },
890
- },
891
- ];
892
- restoreSessionOverrides(context(cwd, activeBranchEntries, activeBranchEntries));
893
- const state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
894
- assert.equal(state.enabled, false);
895
- assert.deepEqual(state.sessionOverrides, { enabled: false, contextVirtualization: true });
896
- assert.equal("currentMode" in state, false);
897
- } finally {
898
- await resetSessionOverrides(context(cwd), loadExtension().pi);
899
- await rm(cwd, { recursive: true, force: true });
900
- }
901
- });
902
-
903
- test("Pi fails closed when an existing local override is invalid", async () => {
904
- const cwd = await configuredRepo();
905
- try {
906
- await writeFile(join(cwd, ".freeflow/local.json"), "{ invalid\n", "utf8");
907
- const layers = await readFreeflowConfigLayers(cwd);
908
- assert.equal(layers.repositoryConfigured, true);
909
- assert.equal(layers.configured, false);
910
- assert.equal(layers.local.valid, false);
911
- const state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
912
- assert.equal(state.configured, false);
913
- assert.equal(state.enabled, false);
914
- const { handlers } = loadExtension();
915
- const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
916
- assert.equal(resources.skillPaths.length, 27);
917
- assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/setup-freeflow/SKILL.md")));
918
- } finally {
919
- await rm(cwd, { recursive: true, force: true });
920
- }
921
- });
922
-
923
- test("Pi local enablement overrides the repository master switch", async () => {
924
- const cwd = await configuredRepo({ enabled: true });
925
- try {
926
- await writeFile(join(cwd, ".freeflow/local.json"), JSON.stringify({ enabled: false }, null, 2), "utf8");
927
- const state = await readCapabilityState(cwd, undefined, PIFLOW_HOST);
928
- assert.equal(state.configured, true);
929
- assert.equal(state.enabled, false);
930
- assert.equal(state.configSources.enabled, "local");
931
- } finally {
932
- await rm(cwd, { recursive: true, force: true });
933
- }
934
- });
935
-
936
- test("Pi master Freeflow toggle makes features inactive while preserving their reference surface", async () => {
937
- const cwd = await configuredRepo({ enabled: false });
938
- try {
939
- const { handlers, activeToolNames } = loadExtension();
940
- const ctx = context(cwd);
941
- const resources = await handlers.get("resources_discover")({ cwd }, ctx);
942
- assert.equal(resources.skillPaths.length, 27);
943
- const result = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
944
- assert.ok(result.systemPrompt.startsWith("base prompt\n\n"));
945
- assert.match(result.systemPrompt, /guidance is dormant/);
946
- const providerContext = await handlers.get("context")({ messages: [] }, ctx);
947
- assert.match(lastRuntimeState(providerContext.messages).content, /Freeflow: inactive/);
948
- assert.doesNotMatch(
949
- lastRuntimeState(providerContext.messages).content,
950
- /Default mode|Active mode|Interaction Contract|Skills/,
951
- );
952
- assert.ok(activeToolNames().includes("freeflow_context"));
953
- } finally {
954
- await rm(cwd, { recursive: true, force: true });
955
- }
956
- });
957
-
958
- test("Pi settings expose no mode, Skills, or Interaction Contract controls", async () => {
959
- const cwd = await configuredRepo({ enabled: false });
960
- try {
961
- const { commands } = loadExtension();
962
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
963
- assert.ok(freeflowCommand);
964
- const ctx = context(cwd);
965
- ctx.ui.custom = async (factory) => {
966
- let result;
967
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
968
- result = value;
969
- });
970
- const text = renderText(component);
971
- assert.match(text, /Freeflow Settings/);
972
- assert.match(text, /Freeflow Context/);
973
- assert.doesNotMatch(text, /Interaction Contract|Skills|Session mode|Default mode|Mode/);
974
- component.handleInput("\u001b");
975
- return result;
976
- };
977
- await freeflowCommand.definition.handler("settings", ctx);
978
- assert.equal(ctx.reloads.length, 0);
979
- } finally {
980
- await rm(cwd, { recursive: true, force: true });
981
- }
982
- });
983
-
984
- test("Pi enable and disable commands mutate only the master switch", async () => {
985
- const cwd = await configuredRepo();
986
- try {
987
- const { commands } = loadExtension();
988
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
989
- assert.ok(freeflowCommand);
990
- const disableCtx = context(cwd);
991
- await freeflowCommand.definition.handler("disable", disableCtx);
992
- assert.deepEqual(JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8")), { enabled: false });
993
- assert.equal(disableCtx.statuses.at(-1).value, "freeflow: off");
994
- assert.equal(disableCtx.reloads.length, 1);
995
-
996
- const enableCtx = context(cwd);
997
- await freeflowCommand.definition.handler("enable", enableCtx);
998
- assert.deepEqual(JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8")), {});
999
- assert.equal(enableCtx.reloads.length, 1);
1000
- } finally {
1001
- await rm(cwd, { recursive: true, force: true });
1002
- }
1003
- });
1004
-
1005
- test("Pi statusline uses one umbrella context label for either enabled context feature", async () => {
1006
- const cwd = await configuredRepo({ contextVirtualization: true, conversationHistory: true });
1007
- try {
1008
- const { handlers } = loadExtension();
1009
- const ctx = context(cwd);
1010
- await handlers.get("session_start")({ reason: "startup" }, ctx);
1011
- assert.equal(ctx.statuses.at(-1).value, "freeflow: context");
1012
- } finally {
1013
- await rm(cwd, { recursive: true, force: true });
1014
- }
1015
- });
1016
-
1017
- test("Pi settings refuse to overwrite an invalid local override", async () => {
1018
- const cwd = await configuredRepo();
1019
- try {
1020
- const localPath = join(cwd, ".freeflow/local.json");
1021
- await writeFile(localPath, "{ invalid\n", "utf8");
1022
- const { commands } = loadExtension();
1023
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
1024
- assert.ok(freeflowCommand);
1025
- const ctx = context(cwd);
1026
- ctx.ui.custom = async () => assert.fail("settings UI must not open for invalid local config");
1027
- await freeflowCommand.definition.handler("settings", ctx);
1028
- assert.equal(await readFile(localPath, "utf8"), "{ invalid\n");
1029
- assert.match(ctx.notifications.at(-1).message, /local\.json is invalid; repair or remove it/);
1030
- } finally {
1031
- await rm(cwd, { recursive: true, force: true });
1032
- }
1033
- });
1034
-
1035
- test("Pi personal settings refuse to write a tracked local override", async () => {
1036
- const cwd = await configuredRepo();
1037
- try {
1038
- await writeFile(join(cwd, ".freeflow/local.json"), "{}\n", "utf8");
1039
- await execFileAsync("git", ["init", "-q", cwd]);
1040
- await execFileAsync("git", ["-C", cwd, "add", ".freeflow/local.json"]);
1041
- const { commands } = loadExtension();
1042
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
1043
- assert.ok(freeflowCommand);
1044
- const ctx = context(cwd);
1045
- ctx.ui.custom = async (factory) => {
1046
- let result;
1047
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1048
- result = value;
1049
- });
1050
- component.handleInput("\r");
1051
- component.handleInput("\u001b[B");
1052
- component.handleInput("\r");
1053
- component.handleInput("\u001b");
1054
- return result;
1055
- };
1056
- await freeflowCommand.definition.handler("settings", ctx);
1057
- assert.deepEqual(JSON.parse(await readFile(join(cwd, ".freeflow/local.json"), "utf8")), {});
1058
- assert.match(
1059
- ctx.notifications.map((notification) => notification.message).join("\n"),
1060
- /local\.json is tracked by git/,
1061
- );
1062
- assert.equal(ctx.reloads.length, 0);
1063
- assert.doesNotMatch(
1064
- ctx.notifications.map((notification) => notification.message).join("\n"),
1065
- /personal overrides saved/,
1066
- );
1067
- } finally {
1068
- await rm(cwd, { recursive: true, force: true });
1069
- }
1070
- });
1071
-
1072
- test("Pi Cognitive Routing settings preserve complete presets", async () => {
1073
- const cwd = await configuredRepo({
1074
- cognitiveRouting: {
1075
- enabled: true,
1076
- profiles: {
1077
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
1078
- executor: { provider: "test", model: "model-b", thinking: "high" },
1079
- },
1080
- },
1081
- });
1082
- try {
1083
- const { commands } = loadExtension(
1084
- freeflowExtension,
1085
- {},
1086
- {
1087
- appendEntry() {},
1088
- async setModel() {
1089
- return true;
1090
- },
1091
- setThinkingLevel() {},
1092
- },
1093
- );
1094
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
1095
- assert.ok(freeflowCommand);
1096
- const settingsCtx = context(cwd);
1097
- settingsCtx.isIdle = () => true;
1098
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
1099
- settingsCtx.ui.custom = async (factory) => {
1100
- let result;
1101
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1102
- result = value;
1103
- });
1104
- component.handleInput("\u001b[B");
1105
- component.handleInput("\r");
1106
- for (let index = 0; index < 4; index++) component.handleInput("\u001b[B");
1107
- component.handleInput("\r");
1108
- component.handleInput("\r");
1109
- for (let index = 0; index < 1; index++) component.handleInput("\u001b[B");
1110
- component.handleInput("\r");
1111
- component.handleInput("\r");
1112
- await component.waitForWrites();
1113
- return result;
1114
- };
1115
- await freeflowCommand.definition.handler("settings repo", settingsCtx);
1116
- const saved = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
1117
- assert.deepEqual(saved.cognitiveRouting.profiles.executor, {
1118
- provider: "test",
1119
- model: "model-b",
1120
- thinking: "max",
1121
- });
1122
- assert.equal(settingsCtx.reloads.length, 1);
1123
- } finally {
1124
- await rm(cwd, { recursive: true, force: true });
1125
- }
1126
- });
1127
-
1128
- test("Pi session settings expose both routing preset wizards without changing config files", async () => {
1129
- const cwd = await configuredRepo({
1130
- cognitiveRouting: {
1131
- enabled: true,
1132
- profiles: {
1133
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
1134
- executor: { provider: "test", model: "model-b", thinking: "high" },
1135
- },
1136
- },
1137
- });
1138
- try {
1139
- const configPath = join(cwd, ".freeflow/config.json");
1140
- const localPath = join(cwd, ".freeflow/local.json");
1141
- const originalConfig = await readFile(configPath, "utf8");
1142
- const originalLocal = await readFile(localPath, "utf8").catch(() => undefined);
1143
- const { commands } = loadExtension(
1144
- freeflowExtension,
1145
- {},
1146
- {
1147
- async setModel() {
1148
- return true;
1149
- },
1150
- setThinkingLevel() {},
1151
- },
1152
- );
1153
- const freeflowCommand = commands.find((command) => command.name === "freeflow");
1154
- assert.ok(freeflowCommand);
1155
- const settingsCtx = context(cwd);
1156
- settingsCtx.isIdle = () => true;
1157
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
1158
- settingsCtx.ui.custom = async (factory) => {
1159
- let result;
1160
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1161
- result = value;
1162
- });
1163
- const rendered = component.render(180).join("\\n");
1164
- assert.match(rendered, /Cognitive Routing presets/);
1165
- component.handleInput("\u001b[B");
1166
- component.handleInput("\u001b[B");
1167
- component.handleInput("\r");
1168
- const presetRendered = component.render(180).join("\\n");
1169
- assert.match(presetRendered, /Coordinator preset/);
1170
- assert.match(presetRendered, /Executor preset/);
1171
- result = { changed: false };
1172
- await component.waitForWrites();
1173
- return result;
1174
- };
1175
- await freeflowCommand.definition.handler("settings session", settingsCtx);
1176
- assert.equal(await readFile(configPath, "utf8"), originalConfig);
1177
- assert.equal(await readFile(localPath, "utf8").catch(() => undefined), originalLocal);
1178
- } finally {
1179
- await rm(cwd, { recursive: true, force: true });
1180
- }
1181
- });
1182
-
1183
- test("Pi session preset wizard applies a complete pair without writing config", async () => {
1184
- const cwd = await configuredRepo({
1185
- cognitiveRouting: {
1186
- enabled: true,
1187
- profiles: {
1188
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
1189
- executor: { provider: "test", model: "model-b", thinking: "high" },
1190
- },
1191
- },
1192
- });
1193
- try {
1194
- const configPath = join(cwd, ".freeflow/config.json");
1195
- const originalConfig = await readFile(configPath, "utf8");
1196
- const calls = [];
1197
- const controller = {
1198
- state: () => ({ effective: true, controlMode: "automatic", activeProfile: "coordinator" }),
1199
- sessionProfileOverrides: () => ({}),
1200
- setManualProfile: async () => ({ status: "active" }),
1201
- setAutomaticControl: async () => ({ status: "automatic" }),
1202
- setSessionProfileOverride: async (profile, override) => {
1203
- calls.push({ profile, override });
1204
- return { status: "active" };
1205
- },
1206
- resetSessionProfileOverrides: async () => ({ status: "unchanged" }),
1207
- };
1208
- const settingsCtx = context(cwd);
1209
- settingsCtx.isIdle = () => true;
1210
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
1211
- const pi = {
1212
- host: {},
1213
- appendEntry() {},
1214
- async setModel() {
1215
- return true;
1216
- },
1217
- setThinkingLevel() {},
1218
- };
1219
- settingsCtx.ui.custom = async (factory) => {
1220
- let finish;
1221
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1222
- finish = value;
1223
- });
1224
- component.handleInput("\u001b[B");
1225
- component.handleInput("\u001b[B");
1226
- component.handleInput("\r");
1227
- component.handleInput("\r");
1228
- component.handleInput("\u001b[B");
1229
- component.handleInput("\r");
1230
- component.handleInput("\u001b[B");
1231
- component.handleInput("\r");
1232
- component.handleInput("\r");
1233
- await component.waitForWrites();
1234
- finish?.({ changed: false });
1235
- return { changed: false };
1236
- };
1237
- await handleFreeflowCommand("settings session", settingsCtx, async () => {}, pi, controller);
1238
- assert.deepEqual(calls, [
1239
- {
1240
- profile: "coordinator",
1241
- override: { provider: "test", modelId: "model-a", thinking: "low" },
1242
- },
1243
- ]);
1244
- assert.equal(await readFile(configPath, "utf8"), originalConfig);
1245
- } finally {
1246
- await rm(cwd, { recursive: true, force: true });
1247
- }
1248
- });
1249
-
1250
- test("both mode session wizard applies a Helper pair without writing config", async () => {
1251
- const cwd = await configuredRepo({
1252
- cognitiveRouting: {
1253
- enabled: true,
1254
- delegation: "both",
1255
- profiles: {
1256
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
1257
- helper: { provider: "test", model: "model-b", thinking: "high" },
1258
- executor: { provider: "test", model: "model-b", thinking: "max" },
1259
- },
1260
- },
1261
- });
1262
- try {
1263
- const configPath = join(cwd, ".freeflow/config.json");
1264
- const originalConfig = await readFile(configPath, "utf8");
1265
- const calls = [];
1266
- const controller = {
1267
- state: () => ({ effective: true, controlMode: "automatic", activeProfile: "coordinator" }),
1268
- sessionProfileOverrides: () => ({}),
1269
- setManualProfile: async () => ({ status: "active" }),
1270
- setAutomaticControl: async () => ({ status: "automatic" }),
1271
- setSessionProfileOverride: async (profile, override) => {
1272
- calls.push({ profile, override });
1273
- return { status: "stored" };
1274
- },
1275
- resetSessionProfileOverrides: async () => ({ status: "unchanged" }),
1276
- };
1277
- const settingsCtx = context(cwd);
1278
- settingsCtx.isIdle = () => true;
1279
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
1280
- const pi = {
1281
- host: {},
1282
- appendEntry() {},
1283
- async setModel() {
1284
- return true;
1285
- },
1286
- setThinkingLevel() {},
1287
- };
1288
- settingsCtx.ui.custom = async (factory) => {
1289
- let finish;
1290
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1291
- finish = value;
1292
- });
1293
- component.handleInput("\u001b[B");
1294
- component.handleInput("\u001b[B");
1295
- component.handleInput("\r");
1296
- component.handleInput("\u001b[B");
1297
- component.handleInput("\r");
1298
- component.handleInput("\u001b[B");
1299
- component.handleInput("\r");
1300
- component.handleInput("\u001b[B");
1301
- component.handleInput("\r");
1302
- component.handleInput("\r");
1303
- await component.waitForWrites();
1304
- finish?.({ changed: false });
1305
- return { changed: false };
1306
- };
1307
- await handleFreeflowCommand("settings session", settingsCtx, async () => {}, pi, controller);
1308
- assert.deepEqual(calls, [
1309
- {
1310
- profile: "helper",
1311
- override: { provider: "test", modelId: "model-a", thinking: "low" },
1312
- },
1313
- ]);
1314
- assert.equal(await readFile(configPath, "utf8"), originalConfig);
1315
- } finally {
1316
- await rm(cwd, { recursive: true, force: true });
1317
- }
1318
- });
1319
-
1320
- test("session reset preserves core overrides when routing reset fails", async () => {
1321
- const cwd = await configuredRepo({
1322
- cognitiveRouting: {
1323
- enabled: true,
1324
- profiles: {
1325
- coordinator: { provider: "test", model: "model-a", thinking: "low" },
1326
- executor: { provider: "test", model: "model-b", thinking: "high" },
1327
- },
1328
- },
1329
- });
1330
- try {
1331
- const configPath = join(cwd, ".freeflow/config.json");
1332
- const originalConfig = await readFile(configPath, "utf8");
1333
- const settingsCtx = context(cwd);
1334
- settingsCtx.isIdle = () => true;
1335
- settingsCtx.modelRegistry = cognitiveRoutingModelRegistry();
1336
- const pi = {
1337
- host: {},
1338
- appendEntry() {},
1339
- async setModel() {
1340
- return true;
1341
- },
1342
- setThinkingLevel() {},
1343
- };
1344
- await setSessionCoreOverride("contextVirtualization", true, settingsCtx, pi);
1345
- const controller = {
1346
- state: () => ({ effective: true, controlMode: "automatic", activeProfile: "coordinator" }),
1347
- sessionProfileOverrides: () => ({}),
1348
- setManualProfile: async () => ({ status: "active" }),
1349
- setAutomaticControl: async () => ({ status: "automatic" }),
1350
- setSessionProfileOverride: async () => ({ status: "stored" }),
1351
- resetSessionProfileOverrides: async () => ({ status: "blocked", reason: "fixture failure" }),
1352
- };
1353
- settingsCtx.ui.custom = async (factory) => {
1354
- let finish;
1355
- const component = factory({ requestRender() {} }, testTheme, {}, (value) => {
1356
- finish = value;
1357
- });
1358
- component.handleInput("\u001b[B");
1359
- component.handleInput("\u001b[B");
1360
- component.handleInput("\u001b[B");
1361
- component.handleInput("\r");
1362
- component.handleInput("\r");
1363
- await component.waitForWrites();
1364
- finish?.({ changed: false });
1365
- return { changed: false };
1366
- };
1367
- await handleFreeflowCommand("settings session", settingsCtx, async () => {}, pi, controller);
1368
- const state = await readCapabilityState(cwd, settingsCtx, pi.host);
1369
- assert.equal(state.contextVirtualization.effective, true);
1370
- assert.equal(state.sessionOverrides.contextVirtualization, true);
1371
- assert.equal(await readFile(configPath, "utf8"), originalConfig);
1372
- assert.match(settingsCtx.notifications.at(-1)?.message ?? "", /fixture failure/);
1373
- } finally {
1374
- await rm(cwd, { recursive: true, force: true });
1375
- }
1376
- });
1377
-
1378
- test("Pi filters persisted Workflow and Cognitive Routing bootstrap entries without creating them", async () => {
1379
- const cwd = await configuredRepo();
1380
- try {
1381
- const { handlers } = loadExtension();
1382
- const userMessage = { role: "user", content: "hello", timestamp: Date.now() };
1383
- const messages = [
1384
- { role: "custom", customType: "freeflow-workflow-bootstrap", content: "old workflow", display: false },
1385
- { role: "custom", customType: "freeflow-cognitive-routing-bootstrap", content: "old routing", display: false },
1386
- userMessage,
1387
- ];
1388
- const result = await handlers.get("context")({ messages }, context(cwd));
1389
- assert.deepEqual(
1390
- result.messages.filter((message) => message.customType !== "freeflow-runtime-state"),
1391
- [userMessage],
1392
- );
1393
- const runtimeStateIndex = result.messages.findIndex((message) => message.customType === "freeflow-runtime-state");
1394
- assert.equal(runtimeStateIndex, result.messages.findIndex((message) => message.role === "user") - 1);
1395
- } finally {
1396
- await rm(cwd, { recursive: true, force: true });
1397
- }
1398
- });
1399
-
1400
- test("Pi preserves the host prompt prefix and dormant contract when a mandatory file is missing", async () => {
1401
- const root = await mkdtemp(join(tmpdir(), "freeflow-pi-missing-prompt-"));
1402
- const cwd = await configuredRepo();
1403
- try {
1404
- await mkdir(join(root, "pi-extension"), { recursive: true });
1405
- await execFileAsync("cp", ["-R", join(process.cwd(), "pi-extension", "dist"), join(root, "pi-extension")]);
1406
- await execFileAsync("cp", ["-R", join(process.cwd(), "runtime"), root]);
1407
- await execFileAsync("ln", ["-s", join(process.cwd(), "node_modules"), join(root, "node_modules")]);
1408
- await execFileAsync("rm", [join(root, "runtime", "prompts", "interaction-contract.md")]);
1409
- const extension = (
1410
- await import(`${new URL(`file://${join(root, "pi-extension", "dist", "index.js")}`).href}?missing=${Date.now()}`)
1411
- ).default;
1412
- const { handlers } = loadExtension(extension);
1413
- const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, context(cwd));
1414
- assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
1415
- assert.match(before.systemPrompt, /guidance is dormant/);
1416
- const resources = await handlers.get("resources_discover")({ cwd }, context(cwd));
1417
- assert.equal(resources.skillPaths.length, 27);
1418
- } finally {
1419
- await rm(cwd, { recursive: true, force: true });
1420
- await rm(root, { recursive: true, force: true });
1421
- }
1422
- });
1423
-
1424
- function cognitiveRoutingModelRegistry() {
1425
- const models = [
1426
- { provider: "test", id: "model-a", name: "Model A", reasoning: true, contextWindow: 128000 },
1427
- { provider: "test", id: "model-b", name: "Model B", reasoning: true, contextWindow: 128000 },
1428
- ];
1429
- const supported = new Map([
1430
- ["model-a", new Set(["off", "low", "medium", "high"])],
1431
- ["model-b", new Set(["off", "low", "medium", "high", "max"])],
1432
- ]);
1433
- return {
1434
- getAvailable() {
1435
- return models;
1436
- },
1437
- find(provider, id) {
1438
- return models.find((model) => model.provider === provider && model.id === id);
1439
- },
1440
- async getApiKeyAndHeaders() {
1441
- return { ok: true };
1442
- },
1443
- clampThinkingLevel(model, level) {
1444
- return supported.get(model.id)?.has(level) ? level : "off";
1445
- },
1446
- };
1447
- }