@hassangameryt/freeflow 0.3.0 → 0.5.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 (692) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +6 -2
  3. package/.codex-plugin/plugin.json +9 -9
  4. package/README.md +190 -127
  5. package/assets/feedback-loop.png +0 -0
  6. package/assets/freeflow_workflow_map.png +0 -0
  7. package/assets/workflow.png +0 -0
  8. package/{skills → capabilities}/output-router/SKILL.md +21 -17
  9. package/{skills → capabilities}/output-router/references/safety-policy.md +1 -1
  10. package/capabilities/output-router/references/setup.md +110 -0
  11. package/command-surface.json +62 -7
  12. package/hooks/freeflow-runtime-context.mjs +572 -154
  13. package/hooks/hooks.json +26 -1
  14. package/hooks/tests/check-runtime-context-hook.sh +375 -0
  15. package/package.json +24 -24
  16. package/pi-extension/dist/index.js +264 -80
  17. package/pi-extension/dist/integrations/mcp-config.js +29 -0
  18. package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
  19. package/pi-extension/dist/output-router/native-safety-net.js +216 -0
  20. package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
  21. package/pi-extension/dist/output-router/renderers.js +1023 -0
  22. package/pi-extension/dist/output-router/router-tools.js +554 -0
  23. package/pi-extension/dist/output-router/schemas.js +383 -0
  24. package/pi-extension/dist/output-router/status.js +744 -0
  25. package/pi-extension/dist/output-router/utils.js +502 -0
  26. package/pi-extension/dist/runtime/runtime-context.js +798 -0
  27. package/pi-extension/dist/settings/settings-tui.js +314 -0
  28. package/pi-extension/dist/settings/settings-ui.js +1487 -0
  29. package/pi-extension/freeflow/index.js +1 -0
  30. package/pi-extension/src/index.ts +230 -56
  31. package/pi-extension/src/{host-producer-identification.ts → output-router/host-producer-identification.ts} +37 -7
  32. package/pi-extension/src/{native-safety-net.ts → output-router/native-safety-net.ts} +8 -12
  33. package/pi-extension/src/{observed-tool-routing.ts → output-router/observed-tool-routing.ts} +19 -11
  34. package/pi-extension/src/{renderers.ts → output-router/renderers.ts} +246 -55
  35. package/pi-extension/src/{router-tools.ts → output-router/router-tools.ts} +130 -29
  36. package/pi-extension/src/{schemas.ts → output-router/schemas.ts} +171 -43
  37. package/pi-extension/src/output-router/status.ts +791 -0
  38. package/pi-extension/src/{utils.ts → output-router/utils.ts} +80 -18
  39. package/pi-extension/src/runtime/runtime-context.ts +895 -0
  40. package/pi-extension/src/settings/settings-tui.ts +408 -0
  41. package/pi-extension/src/settings/settings-ui.ts +1730 -0
  42. package/pi-extension/tests/integration/pi-extension.test.js +4020 -0
  43. package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
  44. package/pi-extension/tests/output-router/search.test.js +722 -0
  45. package/pi-extension/tests/settings/settings-tui.test.js +243 -0
  46. package/router/dist/benchmarks/benchmark-harness.d.ts +16 -13
  47. package/router/dist/benchmarks/benchmark-harness.js +62 -62
  48. package/router/dist/benchmarks/benchmarks.d.ts +80 -73
  49. package/router/dist/benchmarks/benchmarks.js +860 -761
  50. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +71 -67
  51. package/router/dist/benchmarks/codex-qa-benchmarks.js +429 -390
  52. package/router/dist/benchmarks/command-benchmarks.d.ts +107 -92
  53. package/router/dist/benchmarks/command-benchmarks.js +616 -571
  54. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +64 -56
  55. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +679 -613
  56. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +103 -95
  57. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1188 -1103
  58. package/router/dist/benchmarks/context-saving-baseline.d.ts +47 -41
  59. package/router/dist/benchmarks/context-saving-baseline.js +405 -374
  60. package/router/dist/benchmarks/index-benchmarks.d.ts +74 -69
  61. package/router/dist/benchmarks/index-benchmarks.js +761 -661
  62. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +80 -69
  63. package/router/dist/benchmarks/storage-policy-benchmarks.js +549 -469
  64. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +43 -34
  65. package/router/dist/benchmarks/vault-index-benchmarks.js +294 -229
  66. package/router/dist/config/config.d.ts +62 -55
  67. package/router/dist/config/config.js +552 -373
  68. package/router/dist/config/router-contract.d.ts +2 -2
  69. package/router/dist/config/router-contract.js +38 -38
  70. package/router/dist/config/schema.d.ts +19 -10
  71. package/router/dist/config/schema.js +586 -538
  72. package/router/dist/config/types.d.ts +413 -361
  73. package/router/dist/config/types.js +19 -8
  74. package/router/dist/evidence/bounded-evidence.d.ts +26 -26
  75. package/router/dist/evidence/bounded-evidence.js +265 -252
  76. package/router/dist/evidence/evidence-range-selector.d.ts +16 -14
  77. package/router/dist/evidence/evidence-range-selector.js +192 -191
  78. package/router/dist/evidence/evidence-search.d.ts +18 -16
  79. package/router/dist/evidence/evidence-search.js +401 -377
  80. package/router/dist/evidence/evidence.d.ts +17 -17
  81. package/router/dist/evidence/evidence.js +163 -159
  82. package/router/dist/evidence/failure-contracts.d.ts +25 -15
  83. package/router/dist/evidence/failure-contracts.js +132 -126
  84. package/router/dist/evidence/line-ranges.d.ts +17 -15
  85. package/router/dist/evidence/line-ranges.js +18 -18
  86. package/router/dist/experiments/local-index.d.ts +47 -41
  87. package/router/dist/experiments/local-index.js +477 -458
  88. package/router/dist/local/local-traversal.d.ts +18 -0
  89. package/router/dist/local/local-traversal.js +334 -0
  90. package/router/dist/processing/engine.d.ts +94 -70
  91. package/router/dist/processing/engine.js +462 -364
  92. package/router/dist/processing/reducers.d.ts +208 -194
  93. package/router/dist/processing/reducers.js +1434 -1256
  94. package/router/dist/processing/renderers.d.ts +24 -18
  95. package/router/dist/processing/renderers.js +161 -152
  96. package/router/dist/processing/scripts.d.ts +80 -67
  97. package/router/dist/processing/scripts.js +343 -290
  98. package/router/dist/repo/repo-traversal.d.ts +9 -9
  99. package/router/dist/repo/repo-traversal.js +205 -202
  100. package/router/dist/routing/observed-reducers.d.ts +25 -19
  101. package/router/dist/routing/observed-reducers.js +559 -545
  102. package/router/dist/routing/observed-routing.d.ts +23 -14
  103. package/router/dist/routing/observed-routing.js +319 -318
  104. package/router/dist/routing/parsers.d.ts +10 -10
  105. package/router/dist/routing/parsers.js +454 -406
  106. package/router/dist/routing/run-filters.d.ts +45 -41
  107. package/router/dist/routing/run-filters.js +215 -207
  108. package/router/dist/routing/run-reducers.d.ts +45 -26
  109. package/router/dist/routing/run-reducers.js +102 -90
  110. package/router/dist/sandbox/adapter-roots.d.ts +34 -26
  111. package/router/dist/sandbox/adapter-roots.js +43 -41
  112. package/router/dist/sandbox/eryx-python-adapter.d.ts +9 -5
  113. package/router/dist/sandbox/eryx-python-adapter.js +659 -588
  114. package/router/dist/sandbox/jq-wasm-adapter.d.ts +3 -3
  115. package/router/dist/sandbox/jq-wasm-adapter.js +319 -311
  116. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +9 -5
  117. package/router/dist/sandbox/quickjs-wasi-adapter.js +336 -333
  118. package/router/dist/sandbox/script-sandbox.d.ts +105 -84
  119. package/router/dist/sandbox/script-sandbox.js +294 -260
  120. package/router/dist/setup/script-transform-adapters.d.ts +23 -19
  121. package/router/dist/setup/script-transform-adapters.js +223 -204
  122. package/router/dist/tools/batch.d.ts +27 -16
  123. package/router/dist/tools/batch.js +493 -416
  124. package/router/dist/tools/run.d.ts +47 -35
  125. package/router/dist/tools/run.js +1172 -1087
  126. package/router/dist/tools/search.d.ts +53 -31
  127. package/router/dist/tools/search.js +1224 -1065
  128. package/router/dist/transform/engine.d.ts +121 -96
  129. package/router/dist/transform/engine.js +1853 -1736
  130. package/router/dist/vault/vault-index.d.ts +97 -78
  131. package/router/dist/vault/vault-index.js +347 -342
  132. package/router/dist/vault/vault.d.ts +109 -74
  133. package/router/dist/vault/vault.js +528 -533
  134. package/runtime/interaction-contract.md +16 -0
  135. package/skills/bypass/SKILL.md +52 -38
  136. package/skills/commit-work/SKILL.md +54 -46
  137. package/skills/commit-work/references/staging-decisions.md +46 -24
  138. package/skills/decision-gate/SKILL.md +48 -0
  139. package/skills/design-for-depth/SKILL.md +75 -59
  140. package/skills/design-for-depth/references/design-pressure-signals.md +56 -7
  141. package/skills/design-for-depth/references/interface-design-loop.md +156 -0
  142. package/skills/design-for-depth/references/software-design-philosophy.md +32 -3
  143. package/skills/diagnose-failure/SKILL.md +65 -60
  144. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
  145. package/skills/diagnose-failure/references/flaky-and-performance.md +30 -18
  146. package/skills/discuss/SKILL.md +121 -0
  147. package/skills/discuss/references/checkpoints.md +66 -0
  148. package/skills/evaluate-skill/SKILL.md +62 -53
  149. package/skills/evaluate-skill/references/definition-schema.md +345 -0
  150. package/skills/evaluate-skill/references/evaluation-design.md +86 -0
  151. package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
  152. package/skills/evaluate-skill/references/review-and-revision.md +51 -0
  153. package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
  154. package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
  155. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
  156. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
  157. package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
  158. package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
  159. package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
  160. package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
  161. package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
  162. package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
  163. package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
  164. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
  165. package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
  166. package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
  167. package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
  168. package/skills/execute-work/SKILL.md +99 -0
  169. package/skills/execute-work/references/code-practices.md +78 -0
  170. package/skills/execute-work/references/execution-loop.md +77 -0
  171. package/skills/finish-branch/SKILL.md +117 -0
  172. package/skills/finish-branch/references/integration-options.md +80 -0
  173. package/skills/handoff/SKILL.md +65 -39
  174. package/skills/handoff/references/templates.md +104 -27
  175. package/skills/launch-work/SKILL.md +116 -0
  176. package/skills/launch-work/references/launch-readiness.md +85 -0
  177. package/skills/migration-work/SKILL.md +115 -0
  178. package/skills/migration-work/references/migration-lifecycle.md +82 -0
  179. package/skills/mode-contract/SKILL.md +72 -52
  180. package/skills/release-work/SKILL.md +118 -0
  181. package/skills/release-work/references/release-evidence.md +78 -0
  182. package/skills/review-artifact/SKILL.md +77 -69
  183. package/skills/review-artifact/references/reviewer-prompt.md +133 -42
  184. package/skills/review-work/SKILL.md +82 -57
  185. package/skills/review-work/references/reviewer-prompt.md +97 -60
  186. package/skills/review-work/references/security-risk-lens.md +67 -0
  187. package/skills/setup-freeflow/SKILL.md +54 -68
  188. package/skills/setup-freeflow/references/activation-contract.md +84 -69
  189. package/skills/setup-freeflow/references/host-setup.md +50 -17
  190. package/skills/simplify-code/SKILL.md +80 -0
  191. package/skills/simplify-code/references/simplification-patterns.md +70 -0
  192. package/skills/tdd/SKILL.md +96 -0
  193. package/skills/tdd/references/test-design.md +96 -0
  194. package/skills/track-work/SKILL.md +84 -0
  195. package/skills/track-work/references/working-record-schema.md +378 -0
  196. package/skills/verify-work/SKILL.md +59 -38
  197. package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
  198. package/skills/verify-work/references/integration-evidence.md +51 -0
  199. package/skills/verify-work/references/performance-evidence.md +57 -0
  200. package/skills/workflow/SKILL.md +53 -59
  201. package/skills/workflow/references/domain-skill-composition.md +57 -0
  202. package/skills/workflow/references/workflow-loop.md +92 -0
  203. package/skills/write-plan/SKILL.md +85 -58
  204. package/skills/write-plan/references/plan-shapes.md +132 -46
  205. package/skills/write-skill/SKILL.md +82 -51
  206. package/skills/write-skill/references/activation-boundaries.md +62 -0
  207. package/skills/write-skill/references/agent-first-instructions.md +87 -0
  208. package/skills/write-skill/references/progressive-disclosure.md +63 -0
  209. package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
  210. package/skills/write-skill/scripts/skill-author.mjs +104 -0
  211. package/skills/write-spec/SKILL.md +65 -54
  212. package/skills/write-spec/references/artifact-standards.md +60 -41
  213. package/skills/write-spec/references/decision-records.md +77 -0
  214. package/skills/write-spec/references/spec-shapes.md +119 -60
  215. package/evals/README.md +0 -32
  216. package/evals/fixtures/copy-conventions.md +0 -31
  217. package/evals/fixtures/output-router/README.md +0 -12
  218. package/evals/fixtures/output-router/failed-command-output.txt +0 -9
  219. package/evals/fixtures/output-router/large-router-manual.md +0 -63
  220. package/evals/fixtures/output-router/native-large-output.txt +0 -40
  221. package/evals/fixtures/output-router/noisy-test-output.txt +0 -30
  222. package/evals/fixtures/output-router/verification-output.txt +0 -8
  223. package/evals/fixtures/tiny-artifact-review-app/README.md +0 -5
  224. package/evals/fixtures/tiny-artifact-review-app/docs/billing-policy.md +0 -10
  225. package/evals/fixtures/tiny-artifact-review-app/docs/specs/billing-webhook-api-draft.md +0 -21
  226. package/evals/fixtures/tiny-artifact-review-app/docs/specs/failed-payment-grace-period.md +0 -24
  227. package/evals/fixtures/tiny-artifact-review-app/docs/specs/immediate-downgrade.md +0 -17
  228. package/evals/fixtures/tiny-artifact-review-app/tests/billing.test.md +0 -8
  229. package/evals/fixtures/tiny-commit-ambiguous-app/.freeflow-eval-setup.sh +0 -15
  230. package/evals/fixtures/tiny-commit-ambiguous-app/README.md +0 -3
  231. package/evals/fixtures/tiny-commit-ambiguous-app/src/billing.ts +0 -5
  232. package/evals/fixtures/tiny-commit-ambiguous-app/src/settings-page.ts +0 -1
  233. package/evals/fixtures/tiny-commit-ambiguous-app/tests/settings.test.md +0 -5
  234. package/evals/fixtures/tiny-commit-app/.freeflow-eval-setup.sh +0 -14
  235. package/evals/fixtures/tiny-commit-app/README.md +0 -3
  236. package/evals/fixtures/tiny-commit-app/docs/specs/settings-title.md +0 -5
  237. package/evals/fixtures/tiny-commit-app/src/billing.ts +0 -5
  238. package/evals/fixtures/tiny-commit-app/src/settings-page.ts +0 -1
  239. package/evals/fixtures/tiny-commit-app/tests/settings.test.md +0 -5
  240. package/evals/fixtures/tiny-commit-everything-app/.freeflow-eval-setup.sh +0 -15
  241. package/evals/fixtures/tiny-commit-everything-app/README.md +0 -3
  242. package/evals/fixtures/tiny-commit-everything-app/docs/specs/settings-title.md +0 -5
  243. package/evals/fixtures/tiny-commit-everything-app/src/billing.ts +0 -5
  244. package/evals/fixtures/tiny-commit-everything-app/src/settings-page.ts +0 -1
  245. package/evals/fixtures/tiny-commit-everything-app/tests/settings.test.md +0 -5
  246. package/evals/fixtures/tiny-commit-staged-app/.freeflow-eval-setup.sh +0 -15
  247. package/evals/fixtures/tiny-commit-staged-app/README.md +0 -3
  248. package/evals/fixtures/tiny-commit-staged-app/docs/specs/settings-title.md +0 -5
  249. package/evals/fixtures/tiny-commit-staged-app/src/billing.ts +0 -5
  250. package/evals/fixtures/tiny-commit-staged-app/src/settings-page.ts +0 -1
  251. package/evals/fixtures/tiny-commit-staged-app/tests/settings.test.md +0 -5
  252. package/evals/fixtures/tiny-commit-staged-mixed-app/.freeflow-eval-setup.sh +0 -15
  253. package/evals/fixtures/tiny-commit-staged-mixed-app/README.md +0 -3
  254. package/evals/fixtures/tiny-commit-staged-mixed-app/docs/billing-policy.md +0 -3
  255. package/evals/fixtures/tiny-commit-staged-mixed-app/docs/release-notes.md +0 -3
  256. package/evals/fixtures/tiny-commit-staged-mixed-app/src/billing.ts +0 -5
  257. package/evals/fixtures/tiny-dashboard-stale-app/README.md +0 -11
  258. package/evals/fixtures/tiny-dashboard-stale-app/src/api-client.ts +0 -13
  259. package/evals/fixtures/tiny-dashboard-stale-app/src/dashboard-data.ts +0 -19
  260. package/evals/fixtures/tiny-dashboard-stale-app/tests/dashboard-data.test.md +0 -9
  261. package/evals/fixtures/tiny-decision-destination-app/README.md +0 -6
  262. package/evals/fixtures/tiny-decision-destination-app/src/risk.ts +0 -8
  263. package/evals/fixtures/tiny-decision-destination-app/tests/risk.test.md +0 -4
  264. package/evals/fixtures/tiny-design-depth-app/README.md +0 -15
  265. package/evals/fixtures/tiny-design-depth-app/src/routes.ts +0 -25
  266. package/evals/fixtures/tiny-design-depth-app/src/sendEmail.ts +0 -3
  267. package/evals/fixtures/tiny-design-depth-app/src/sendSms.ts +0 -3
  268. package/evals/fixtures/tiny-design-depth-app/src/telemetry.ts +0 -3
  269. package/evals/fixtures/tiny-design-depth-app/tests/routes.test.md +0 -12
  270. package/evals/fixtures/tiny-eval-method-app/README.md +0 -8
  271. package/evals/fixtures/tiny-eval-method-app/evals/reports/review-pr-eval-result.md +0 -5
  272. package/evals/fixtures/tiny-eval-method-app/evals/reports/triage-question-failure.md +0 -17
  273. package/evals/fixtures/tiny-eval-method-app/evals/runs/review-pr-with-skill-output.diff +0 -11
  274. package/evals/fixtures/tiny-eval-method-app/evals/runs/review-pr-with-skill-output.md +0 -3
  275. package/evals/fixtures/tiny-eval-method-app/skills/review-pr/SKILL.md +0 -10
  276. package/evals/fixtures/tiny-eval-method-app/skills/triage-question/SKILL.md +0 -10
  277. package/evals/fixtures/tiny-execute-plan-app/README.md +0 -5
  278. package/evals/fixtures/tiny-execute-plan-app/checks/settings-and-billing-check.sh +0 -5
  279. package/evals/fixtures/tiny-execute-plan-app/docs/billing-policy.md +0 -10
  280. package/evals/fixtures/tiny-execute-plan-app/docs/plans/immediate-downgrade-plan.md +0 -11
  281. package/evals/fixtures/tiny-execute-plan-app/docs/plans/settings-title-plan.md +0 -12
  282. package/evals/fixtures/tiny-execute-plan-app/docs/plans/settings-title-with-bad-check.md +0 -15
  283. package/evals/fixtures/tiny-execute-plan-app/docs/plans/unverified-billing-plan.md +0 -9
  284. package/evals/fixtures/tiny-execute-plan-app/docs/specs/failed-payment-grace-period.md +0 -9
  285. package/evals/fixtures/tiny-execute-plan-app/docs/specs/settings-title.md +0 -9
  286. package/evals/fixtures/tiny-execute-plan-app/src/billing.ts +0 -7
  287. package/evals/fixtures/tiny-execute-plan-app/src/settings-page.ts +0 -1
  288. package/evals/fixtures/tiny-execute-plan-app/tests/billing.test.md +0 -7
  289. package/evals/fixtures/tiny-execute-plan-app/tests/settings.test.md +0 -5
  290. package/evals/fixtures/tiny-execute-review-loop-app/README.md +0 -5
  291. package/evals/fixtures/tiny-execute-review-loop-app/docs/billing-policy.md +0 -9
  292. package/evals/fixtures/tiny-execute-review-loop-app/docs/plans/settings-title-plan.md +0 -12
  293. package/evals/fixtures/tiny-execute-review-loop-app/docs/review/previous-agent-summary.md +0 -15
  294. package/evals/fixtures/tiny-execute-review-loop-app/docs/review/review-pass-1-non-pass.md +0 -11
  295. package/evals/fixtures/tiny-execute-review-loop-app/docs/specs/settings-title.md +0 -9
  296. package/evals/fixtures/tiny-execute-review-loop-app/src/billing.ts +0 -7
  297. package/evals/fixtures/tiny-execute-review-loop-app/src/settings-page.ts +0 -1
  298. package/evals/fixtures/tiny-execute-review-loop-app/tests/billing.test.md +0 -7
  299. package/evals/fixtures/tiny-execute-review-loop-app/tests/settings.test.md +0 -5
  300. package/evals/fixtures/tiny-flaky-session-app/README.md +0 -10
  301. package/evals/fixtures/tiny-flaky-session-app/src/session-timeout.ts +0 -13
  302. package/evals/fixtures/tiny-flaky-session-app/tests/session-timeout.test.md +0 -8
  303. package/evals/fixtures/tiny-handoff-app/README.md +0 -5
  304. package/evals/fixtures/tiny-handoff-app/docs/billing-policy.md +0 -10
  305. package/evals/fixtures/tiny-handoff-app/docs/handoffs/old-agent-handoff.md +0 -8
  306. package/evals/fixtures/tiny-handoff-app/docs/plans/billing-retry-plan.md +0 -11
  307. package/evals/fixtures/tiny-handoff-app/src/billing.ts +0 -7
  308. package/evals/fixtures/tiny-handoff-report-app/README.md +0 -3
  309. package/evals/fixtures/tiny-handoff-report-app/evals/runs/handoff-3/hof-003/with-skill-output.diff +0 -1
  310. package/evals/fixtures/tiny-handoff-report-app/evals/runs/handoff-3/hof-003/with-skill-output.md +0 -1
  311. package/evals/fixtures/tiny-handoff-report-app/evals/scores/hof-003-score.txt +0 -4
  312. package/evals/fixtures/tiny-plan-app/README.md +0 -5
  313. package/evals/fixtures/tiny-plan-app/docs/billing-policy.md +0 -10
  314. package/evals/fixtures/tiny-plan-app/docs/specs/failed-payment-grace-period.md +0 -25
  315. package/evals/fixtures/tiny-plan-app/src/billing.ts +0 -17
  316. package/evals/fixtures/tiny-plan-app/src/settings-page.ts +0 -1
  317. package/evals/fixtures/tiny-plan-app/tests/billing.test.md +0 -8
  318. package/evals/fixtures/tiny-plan-app/tests/settings.test.md +0 -5
  319. package/evals/fixtures/tiny-plan-strict-api-app/README.md +0 -3
  320. package/evals/fixtures/tiny-plan-strict-api-app/docs/billing-policy.md +0 -5
  321. package/evals/fixtures/tiny-plan-strict-api-app/docs/specs/billing-webhook-api.md +0 -25
  322. package/evals/fixtures/tiny-plan-strict-api-app/src/billing.ts +0 -5
  323. package/evals/fixtures/tiny-plan-strict-api-app/src/webhooks.ts +0 -3
  324. package/evals/fixtures/tiny-plan-strict-api-app/tests/billing-webhook.test.md +0 -8
  325. package/evals/fixtures/tiny-post-setup-invalid-config-app/.freeflow/config.json +0 -3
  326. package/evals/fixtures/tiny-post-setup-invalid-config-app/AGENTS.md +0 -15
  327. package/evals/fixtures/tiny-post-setup-invalid-config-app/README.md +0 -16
  328. package/evals/fixtures/tiny-post-setup-invalid-config-app/docs/auth-review.md +0 -15
  329. package/evals/fixtures/tiny-post-setup-invalid-config-app/docs/billing-policy.md +0 -10
  330. package/evals/fixtures/tiny-post-setup-invalid-config-app/docs/copy-conventions.md +0 -15
  331. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/auth-middleware.ts +0 -12
  332. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/billing.ts +0 -20
  333. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/login-copy.ts +0 -1
  334. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/settings-page.ts +0 -1
  335. package/evals/fixtures/tiny-post-setup-invalid-config-app/tests/auth-middleware.test.md +0 -8
  336. package/evals/fixtures/tiny-post-setup-invalid-config-app/tests/billing.test.md +0 -8
  337. package/evals/fixtures/tiny-post-setup-invalid-config-app/tests/copy.test.md +0 -7
  338. package/evals/fixtures/tiny-post-setup-missing-config-app/AGENTS.md +0 -15
  339. package/evals/fixtures/tiny-post-setup-missing-config-app/README.md +0 -16
  340. package/evals/fixtures/tiny-post-setup-missing-config-app/docs/auth-review.md +0 -15
  341. package/evals/fixtures/tiny-post-setup-missing-config-app/docs/billing-policy.md +0 -10
  342. package/evals/fixtures/tiny-post-setup-missing-config-app/docs/copy-conventions.md +0 -15
  343. package/evals/fixtures/tiny-post-setup-missing-config-app/src/auth-middleware.ts +0 -12
  344. package/evals/fixtures/tiny-post-setup-missing-config-app/src/billing.ts +0 -20
  345. package/evals/fixtures/tiny-post-setup-missing-config-app/src/login-copy.ts +0 -1
  346. package/evals/fixtures/tiny-post-setup-missing-config-app/src/settings-page.ts +0 -1
  347. package/evals/fixtures/tiny-post-setup-missing-config-app/tests/auth-middleware.test.md +0 -8
  348. package/evals/fixtures/tiny-post-setup-missing-config-app/tests/billing.test.md +0 -8
  349. package/evals/fixtures/tiny-post-setup-missing-config-app/tests/copy.test.md +0 -7
  350. package/evals/fixtures/tiny-post-setup-source-conflict-app/.freeflow/config.json +0 -3
  351. package/evals/fixtures/tiny-post-setup-source-conflict-app/AGENTS.md +0 -15
  352. package/evals/fixtures/tiny-post-setup-source-conflict-app/README.md +0 -16
  353. package/evals/fixtures/tiny-post-setup-source-conflict-app/docs/auth-review.md +0 -15
  354. package/evals/fixtures/tiny-post-setup-source-conflict-app/docs/billing-policy.md +0 -10
  355. package/evals/fixtures/tiny-post-setup-source-conflict-app/docs/copy-conventions.md +0 -15
  356. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/auth-middleware.ts +0 -12
  357. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/billing.ts +0 -20
  358. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/login-copy.ts +0 -1
  359. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/settings-page.ts +0 -1
  360. package/evals/fixtures/tiny-post-setup-source-conflict-app/tests/auth-middleware.test.md +0 -8
  361. package/evals/fixtures/tiny-post-setup-source-conflict-app/tests/billing.test.md +0 -8
  362. package/evals/fixtures/tiny-post-setup-source-conflict-app/tests/copy.test.md +0 -7
  363. package/evals/fixtures/tiny-report-performance-app/README.md +0 -11
  364. package/evals/fixtures/tiny-report-performance-app/docs/performance.md +0 -10
  365. package/evals/fixtures/tiny-report-performance-app/src/report-summary.ts +0 -29
  366. package/evals/fixtures/tiny-report-performance-app/tests/report-performance.test.md +0 -8
  367. package/evals/fixtures/tiny-review-request-app/README.md +0 -3
  368. package/evals/fixtures/tiny-review-request-app/docs/billing-policy.md +0 -5
  369. package/evals/fixtures/tiny-review-request-app/docs/plans/billing-webhook-api.md +0 -19
  370. package/evals/fixtures/tiny-review-request-app/docs/review/previous-agent-summary.md +0 -20
  371. package/evals/fixtures/tiny-review-request-app/docs/specs/billing-webhook-api.md +0 -25
  372. package/evals/fixtures/tiny-review-request-app/src/billing.ts +0 -11
  373. package/evals/fixtures/tiny-review-request-app/src/webhooks.ts +0 -19
  374. package/evals/fixtures/tiny-review-request-app/tests/billing-webhook.test.md +0 -8
  375. package/evals/fixtures/tiny-review-work-app/README.md +0 -5
  376. package/evals/fixtures/tiny-review-work-app/docs/billing-policy.md +0 -9
  377. package/evals/fixtures/tiny-review-work-app/docs/plans/settings-title-plan.md +0 -12
  378. package/evals/fixtures/tiny-review-work-app/docs/review/previous-agent-summary.md +0 -15
  379. package/evals/fixtures/tiny-review-work-app/docs/specs/settings-title.md +0 -9
  380. package/evals/fixtures/tiny-review-work-app/src/billing.ts +0 -7
  381. package/evals/fixtures/tiny-review-work-app/src/settings-page.ts +0 -1
  382. package/evals/fixtures/tiny-review-work-app/tests/billing.test.md +0 -7
  383. package/evals/fixtures/tiny-review-work-app/tests/settings.test.md +0 -5
  384. package/evals/fixtures/tiny-saas-app/README.md +0 -16
  385. package/evals/fixtures/tiny-saas-app/docs/auth-review.md +0 -15
  386. package/evals/fixtures/tiny-saas-app/docs/billing-policy.md +0 -10
  387. package/evals/fixtures/tiny-saas-app/docs/copy-conventions.md +0 -15
  388. package/evals/fixtures/tiny-saas-app/docs/specs/settings-title.md +0 -13
  389. package/evals/fixtures/tiny-saas-app/src/auth-middleware.ts +0 -12
  390. package/evals/fixtures/tiny-saas-app/src/billing.ts +0 -20
  391. package/evals/fixtures/tiny-saas-app/src/login-copy.ts +0 -1
  392. package/evals/fixtures/tiny-saas-app/src/settings-page.ts +0 -1
  393. package/evals/fixtures/tiny-saas-app/tests/auth-middleware.test.md +0 -8
  394. package/evals/fixtures/tiny-saas-app/tests/billing.test.md +0 -8
  395. package/evals/fixtures/tiny-saas-app/tests/copy.test.md +0 -7
  396. package/evals/fixtures/tiny-setup-agents-app/AGENTS.md +0 -6
  397. package/evals/fixtures/tiny-setup-both-app/AGENTS.md +0 -5
  398. package/evals/fixtures/tiny-setup-both-app/CLAUDE.md +0 -5
  399. package/evals/fixtures/tiny-setup-claude-app/CLAUDE.md +0 -6
  400. package/evals/fixtures/tiny-setup-codex-rules-app/.codex/rules/shell.rules +0 -5
  401. package/evals/fixtures/tiny-setup-codex-rules-app/AGENTS.md +0 -6
  402. package/evals/fixtures/tiny-setup-conflict-app/AGENTS.md +0 -6
  403. package/evals/fixtures/tiny-setup-existing-block-app/AGENTS.md +0 -14
  404. package/evals/fixtures/tiny-setup-four-rules-app/CLAUDE.md +0 -6
  405. package/evals/fixtures/tiny-skill-dev-app/README.md +0 -10
  406. package/evals/fixtures/tiny-skill-dev-app/evals/prompts/review-pr-001.txt +0 -8
  407. package/evals/fixtures/tiny-skill-dev-app/evals/reports/review-pr-failure.md +0 -17
  408. package/evals/fixtures/tiny-skill-dev-app/skills/review-pr/SKILL.md +0 -13
  409. package/evals/fixtures/tiny-team-app/README.md +0 -5
  410. package/evals/fixtures/tiny-team-app/docs/product.md +0 -6
  411. package/evals/fixtures/tiny-team-app/src/teams.ts +0 -9
  412. package/evals/fixtures/tiny-team-app/src/users.ts +0 -5
  413. package/evals/fixtures/tiny-team-app/tests/teams.test.md +0 -7
  414. package/evals/fixtures/tiny-verify-app/README.md +0 -5
  415. package/evals/fixtures/tiny-verify-app/src/settings-nav.ts +0 -1
  416. package/evals/fixtures/tiny-verify-app/src/settings-page.ts +0 -1
  417. package/evals/fixtures/tiny-verify-app/tests/settings.test.md +0 -7
  418. package/evals/prompts/aon-001.txt +0 -2
  419. package/evals/prompts/byp-001.txt +0 -2
  420. package/evals/prompts/byp-002.txt +0 -2
  421. package/evals/prompts/cap-001.txt +0 -3
  422. package/evals/prompts/cap-002.txt +0 -3
  423. package/evals/prompts/cmd-001.txt +0 -3
  424. package/evals/prompts/cmd-002.txt +0 -3
  425. package/evals/prompts/cmd-003.txt +0 -6
  426. package/evals/prompts/cmd-004.txt +0 -5
  427. package/evals/prompts/cmd-005.txt +0 -5
  428. package/evals/prompts/cmd-006.txt +0 -5
  429. package/evals/prompts/cmd-007.txt +0 -5
  430. package/evals/prompts/cmd-008.txt +0 -5
  431. package/evals/prompts/cmd-009.txt +0 -8
  432. package/evals/prompts/cmd-010.txt +0 -5
  433. package/evals/prompts/cmd-011.txt +0 -5
  434. package/evals/prompts/cmd-012.txt +0 -5
  435. package/evals/prompts/cmd-013.txt +0 -5
  436. package/evals/prompts/cmd-014.txt +0 -7
  437. package/evals/prompts/cmd-015.txt +0 -7
  438. package/evals/prompts/cmd-016.txt +0 -5
  439. package/evals/prompts/cmt-001.txt +0 -1
  440. package/evals/prompts/cmt-002.txt +0 -1
  441. package/evals/prompts/cmt-003.txt +0 -1
  442. package/evals/prompts/cmt-004.txt +0 -1
  443. package/evals/prompts/cmt-005.txt +0 -1
  444. package/evals/prompts/dfd-001.txt +0 -3
  445. package/evals/prompts/dia-001.txt +0 -3
  446. package/evals/prompts/dia-002.txt +0 -3
  447. package/evals/prompts/dia-003.txt +0 -3
  448. package/evals/prompts/dis-001.txt +0 -5
  449. package/evals/prompts/dis-002.txt +0 -5
  450. package/evals/prompts/esk-001.txt +0 -5
  451. package/evals/prompts/esk-002.txt +0 -5
  452. package/evals/prompts/esk-003.txt +0 -5
  453. package/evals/prompts/grc-001.txt +0 -3
  454. package/evals/prompts/hof-001.txt +0 -3
  455. package/evals/prompts/hof-002.txt +0 -1
  456. package/evals/prompts/hof-003.txt +0 -1
  457. package/evals/prompts/hof-004.txt +0 -1
  458. package/evals/prompts/hof-005.txt +0 -1
  459. package/evals/prompts/ivg-001.txt +0 -1
  460. package/evals/prompts/ivg-002.txt +0 -11
  461. package/evals/prompts/ivg-003.txt +0 -1
  462. package/evals/prompts/ivg-004.txt +0 -1
  463. package/evals/prompts/mode-001.txt +0 -3
  464. package/evals/prompts/mode-002.txt +0 -1
  465. package/evals/prompts/mode-003.txt +0 -3
  466. package/evals/prompts/mode-004.txt +0 -3
  467. package/evals/prompts/mode-005.txt +0 -3
  468. package/evals/prompts/mode-006.txt +0 -3
  469. package/evals/prompts/mode-007.txt +0 -3
  470. package/evals/prompts/otr-001.txt +0 -3
  471. package/evals/prompts/otr-002.txt +0 -3
  472. package/evals/prompts/rar-001.txt +0 -5
  473. package/evals/prompts/rar-002.txt +0 -3
  474. package/evals/prompts/rar-003.txt +0 -6
  475. package/evals/prompts/rar-004.txt +0 -15
  476. package/evals/prompts/rar-005.txt +0 -10
  477. package/evals/prompts/rbr-001.txt +0 -3
  478. package/evals/prompts/rbr-002.txt +0 -3
  479. package/evals/prompts/rbr-003.txt +0 -7
  480. package/evals/prompts/rev-001.txt +0 -3
  481. package/evals/prompts/rev-002.txt +0 -5
  482. package/evals/prompts/rev-003.txt +0 -6
  483. package/evals/prompts/rev-004.txt +0 -1
  484. package/evals/prompts/rev-005.txt +0 -15
  485. package/evals/prompts/rev-006.txt +0 -5
  486. package/evals/prompts/stp-001.txt +0 -1
  487. package/evals/prompts/stp-002.txt +0 -1
  488. package/evals/prompts/stp-003.txt +0 -1
  489. package/evals/prompts/stp-004.txt +0 -1
  490. package/evals/prompts/stp-005.txt +0 -1
  491. package/evals/prompts/stp-006.txt +0 -1
  492. package/evals/prompts/stp-007.txt +0 -1
  493. package/evals/prompts/stp-008.txt +0 -1
  494. package/evals/prompts/stp-009.txt +0 -1
  495. package/evals/prompts/stp-010.txt +0 -1
  496. package/evals/prompts/stp-011.txt +0 -1
  497. package/evals/prompts/vfy-003.txt +0 -1
  498. package/evals/prompts/wpl-001.txt +0 -3
  499. package/evals/prompts/wpl-002.txt +0 -5
  500. package/evals/prompts/wpl-003.txt +0 -3
  501. package/evals/prompts/wpl-004.txt +0 -5
  502. package/evals/prompts/wpl-005.txt +0 -1
  503. package/evals/prompts/wrc-001.txt +0 -10
  504. package/evals/prompts/wrc-002.txt +0 -3
  505. package/evals/prompts/wsk-001.txt +0 -6
  506. package/evals/prompts/wsp-001.txt +0 -5
  507. package/evals/prompts/wsp-002.txt +0 -15
  508. package/evals/prompts/wsp-003.txt +0 -3
  509. package/evals/prompts/wsp-004.txt +0 -15
  510. package/evals/prompts/wsp-005.txt +0 -5
  511. package/evals/prompts/wsp-006.txt +0 -12
  512. package/evals/prompts/wsp-007.txt +0 -9
  513. package/evals/prompts/wsp-008.txt +0 -13
  514. package/evals/prompts/xpl-001.txt +0 -1
  515. package/evals/prompts/xpl-002.txt +0 -3
  516. package/evals/prompts/xpl-003.txt +0 -3
  517. package/evals/prompts/xpl-004.txt +0 -3
  518. package/evals/prompts/xpl-005.txt +0 -5
  519. package/evals/prompts/xpl-006.txt +0 -3
  520. package/evals/registries/adversarial-evals.json +0 -39
  521. package/evals/registries/fixture-evals.json +0 -2513
  522. package/evals/registries/handoff-evals.json +0 -74
  523. package/evals/registries/legacy-evals.json +0 -147
  524. package/evals/registries/skill-evidence.json +0 -193
  525. package/evals/registries/verify-work-evals.json +0 -46
  526. package/evals/reports/acceptance/v0.1-acceptance-report.md +0 -130
  527. package/evals/reports/by-command-surface/command-surface-1-report.md +0 -45
  528. package/evals/reports/by-command-surface/command-surface-10-report.md +0 -76
  529. package/evals/reports/by-command-surface/command-surface-11-report.md +0 -77
  530. package/evals/reports/by-command-surface/command-surface-12-report.md +0 -91
  531. package/evals/reports/by-command-surface/command-surface-13-report.md +0 -64
  532. package/evals/reports/by-command-surface/command-surface-14-report.md +0 -66
  533. package/evals/reports/by-command-surface/command-surface-15-report.md +0 -124
  534. package/evals/reports/by-command-surface/command-surface-16-report.md +0 -39
  535. package/evals/reports/by-command-surface/command-surface-2-report.md +0 -80
  536. package/evals/reports/by-command-surface/command-surface-3-report.md +0 -86
  537. package/evals/reports/by-command-surface/command-surface-4-report.md +0 -76
  538. package/evals/reports/by-command-surface/command-surface-5-report.md +0 -80
  539. package/evals/reports/by-command-surface/command-surface-6-report.md +0 -74
  540. package/evals/reports/by-command-surface/command-surface-7-report.md +0 -84
  541. package/evals/reports/by-command-surface/command-surface-8-report.md +0 -79
  542. package/evals/reports/by-command-surface/command-surface-9-report.md +0 -79
  543. package/evals/reports/by-command-surface/command-surface-matrix.md +0 -91
  544. package/evals/reports/by-skill/bypass-1-report.md +0 -124
  545. package/evals/reports/by-skill/capture-decisions-1-report.md +0 -99
  546. package/evals/reports/by-skill/capture-decisions-2-report.md +0 -102
  547. package/evals/reports/by-skill/commit-work-1-report.md +0 -165
  548. package/evals/reports/by-skill/commit-work-2-report.md +0 -109
  549. package/evals/reports/by-skill/design-for-depth-1-report.md +0 -95
  550. package/evals/reports/by-skill/diagnose-failure-1-report.md +0 -91
  551. package/evals/reports/by-skill/diagnose-failure-2-report.md +0 -94
  552. package/evals/reports/by-skill/discover-1-report.md +0 -118
  553. package/evals/reports/by-skill/evaluate-skill-2-report.md +0 -71
  554. package/evals/reports/by-skill/execute-plan-1-report.md +0 -103
  555. package/evals/reports/by-skill/execute-plan-2-report.md +0 -112
  556. package/evals/reports/by-skill/execute-plan-3-report.md +0 -87
  557. package/evals/reports/by-skill/grill-context-1-report.md +0 -103
  558. package/evals/reports/by-skill/handoff-1-report.md +0 -123
  559. package/evals/reports/by-skill/handoff-2-report.md +0 -105
  560. package/evals/reports/by-skill/handoff-3-report.md +0 -100
  561. package/evals/reports/by-skill/handoff-7-report.md +0 -77
  562. package/evals/reports/by-skill/interview-gate-1-report.md +0 -63
  563. package/evals/reports/by-skill/interview-gate-2-report.md +0 -105
  564. package/evals/reports/by-skill/mode-contract-1-report.md +0 -74
  565. package/evals/reports/by-skill/mode-contract-2-report.md +0 -87
  566. package/evals/reports/by-skill/mode-contract-3-report.md +0 -65
  567. package/evals/reports/by-skill/mode-contract-4-report.md +0 -71
  568. package/evals/reports/by-skill/mode-contract-5-report.md +0 -70
  569. package/evals/reports/by-skill/output-router-1-report.md +0 -76
  570. package/evals/reports/by-skill/output-router-2-report.md +0 -65
  571. package/evals/reports/by-skill/research-1-report.md +0 -144
  572. package/evals/reports/by-skill/research-brief-1-report.md +0 -94
  573. package/evals/reports/by-skill/research-brief-2-report.md +0 -75
  574. package/evals/reports/by-skill/review-artifact-1-report.md +0 -91
  575. package/evals/reports/by-skill/review-artifact-2-report.md +0 -79
  576. package/evals/reports/by-skill/review-artifact-3-report.md +0 -120
  577. package/evals/reports/by-skill/review-artifact-4-report.md +0 -89
  578. package/evals/reports/by-skill/review-artifact-5-report.md +0 -66
  579. package/evals/reports/by-skill/review-work-1-report.md +0 -85
  580. package/evals/reports/by-skill/review-work-2-report.md +0 -87
  581. package/evals/reports/by-skill/review-work-3-report.md +0 -104
  582. package/evals/reports/by-skill/review-work-4-report.md +0 -108
  583. package/evals/reports/by-skill/review-work-5-report.md +0 -73
  584. package/evals/reports/by-skill/review-work-6-report.md +0 -66
  585. package/evals/reports/by-skill/setup-freeflow-1-report.md +0 -199
  586. package/evals/reports/by-skill/setup-freeflow-2-report.md +0 -105
  587. package/evals/reports/by-skill/setup-freeflow-3-report.md +0 -105
  588. package/evals/reports/by-skill/setup-freeflow-4-report.md +0 -88
  589. package/evals/reports/by-skill/setup-freeflow-5-report.md +0 -115
  590. package/evals/reports/by-skill/skill-authoring-1-report.md +0 -81
  591. package/evals/reports/by-skill/verify-work-1-report.md +0 -111
  592. package/evals/reports/by-skill/verify-work-2-report.md +0 -120
  593. package/evals/reports/by-skill/workflow-1-report.md +0 -65
  594. package/evals/reports/by-skill/write-plan-1-report.md +0 -123
  595. package/evals/reports/by-skill/write-plan-2-report.md +0 -98
  596. package/evals/reports/by-skill/write-plan-3-report.md +0 -110
  597. package/evals/reports/by-skill/write-spec-1-report.md +0 -88
  598. package/evals/reports/by-skill/write-spec-2-report.md +0 -89
  599. package/evals/reports/by-skill/write-spec-3-report.md +0 -90
  600. package/evals/reports/by-skill/write-spec-4-report.md +0 -167
  601. package/evals/reports/harness/claude-harness-1-report.md +0 -103
  602. package/evals/reports/harness/eval-harness-1-report.md +0 -91
  603. package/evals/reports/iterations/iteration-1-report.md +0 -71
  604. package/evals/reports/iterations/iteration-2-report.md +0 -78
  605. package/evals/reports/iterations/iteration-3-report.md +0 -118
  606. package/evals/reports/iterations/iteration-4-report.md +0 -131
  607. package/evals/reports/iterations/iteration-5-report.md +0 -149
  608. package/evals/reports/iterations/iteration-6-report.md +0 -136
  609. package/evals/reports/runtime/always-on-runtime-1-report.md +0 -107
  610. package/evals/reports/runtime/context-mode-normalized-benchmark-1-report.md +0 -52
  611. package/evals/reports/runtime/context-mode-real-deep-baseline-1-report.md +0 -410
  612. package/evals/reports/runtime/context-mode-real-deep-final-slice-11-report.md +0 -203
  613. package/evals/reports/runtime/context-mode-real-deep-final-slice-11-review.md +0 -51
  614. package/evals/reports/runtime/context-saving-native-tools-baseline-1-report.md +0 -99
  615. package/evals/reports/runtime/eryx-python-compatibility-reprobe-1-report.md +0 -166
  616. package/evals/reports/runtime/eryx-python-proof-spike-1-report.md +0 -96
  617. package/evals/reports/runtime/eryx-python-proof-spike-2-report.md +0 -151
  618. package/evals/reports/runtime/eryx-python-script-derive-execution-1-report.md +0 -233
  619. package/evals/reports/runtime/jq-script-derive-execution-1-report.md +0 -215
  620. package/evals/reports/runtime/jq-wasm-proof-spike-1-report.md +0 -67
  621. package/evals/reports/runtime/output-router-benchmark-1-report.md +0 -81
  622. package/evals/reports/runtime/output-router-capture-derive-provider-eval-1-report.md +0 -35
  623. package/evals/reports/runtime/output-router-codex-large-index-sqlite-adhoc-report.md +0 -150
  624. package/evals/reports/runtime/output-router-codex-qa-benchmark-1-report.md +0 -46
  625. package/evals/reports/runtime/output-router-command-benchmark-1-report.md +0 -62
  626. package/evals/reports/runtime/output-router-index-benchmark-1-report.md +0 -64
  627. package/evals/reports/runtime/output-router-regression-1-report.md +0 -81
  628. package/evals/reports/runtime/output-router-transform-eval-1-report.md +0 -28
  629. package/evals/reports/runtime/pi-observed-routing-eval-1-report.md +0 -44
  630. package/evals/reports/runtime/quickjs-script-derive-execution-1-report.md +0 -159
  631. package/evals/reports/runtime/quickjs-wasi-proof-spike-1-report.md +0 -55
  632. package/evals/reports/runtime/render-format-row-protocol-1-report.md +0 -58
  633. package/evals/reports/runtime/script-sandbox-adapter-selection-review-1-report.md +0 -84
  634. package/evals/reports/runtime/script-sandbox-probe-resource-hardening-1-report.md +0 -51
  635. package/evals/reports/runtime/storage-policy-benchmark-1-report.md +0 -176
  636. package/evals/reports/runtime/vault-index-storage-spike-1-report.md +0 -39
  637. package/evals/reports/runtime/workflow-context-hook-1-report.md +0 -82
  638. package/evals/runbooks/adversarial-eval-runbook.md +0 -28
  639. package/evals/runbooks/fixture-eval-runbook.md +0 -137
  640. package/evals/runbooks/handoff-eval-runbook.md +0 -22
  641. package/evals/runbooks/verify-work-eval-runbook.md +0 -27
  642. package/evals/scripts/audit-command-surface.sh +0 -160
  643. package/evals/scripts/check-activation-contract.sh +0 -121
  644. package/evals/scripts/check-runtime-context-hook.sh +0 -183
  645. package/evals/scripts/grade-fixture-eval.sh +0 -446
  646. package/evals/scripts/run-claude-fixture-eval.sh +0 -26
  647. package/evals/scripts/run-codex-fixture-eval.sh +0 -26
  648. package/evals/scripts/run-eryx-python-proof-spike.js +0 -559
  649. package/evals/scripts/run-fixture-eval-by-id.sh +0 -23
  650. package/evals/scripts/run-fixture-eval.sh +0 -464
  651. package/evals/scripts/run-jq-wasm-proof-spike.js +0 -299
  652. package/evals/scripts/run-output-router-transform-eval.js +0 -115
  653. package/evals/scripts/run-pi-observed-routing-eval.js +0 -478
  654. package/evals/scripts/run-quickjs-wasi-proof-spike.js +0 -339
  655. package/evals/scripts/skill-evidence.sh +0 -316
  656. package/evals/scripts/validate-release-metadata.sh +0 -486
  657. package/evals/suites/v0.1-acceptance-suite.md +0 -131
  658. package/pi-extension/dist/host-producer-identification.js +0 -186
  659. package/pi-extension/dist/mcp-config.js +0 -30
  660. package/pi-extension/dist/native-safety-net.js +0 -209
  661. package/pi-extension/dist/observed-tool-routing.js +0 -114
  662. package/pi-extension/dist/renderers.js +0 -835
  663. package/pi-extension/dist/router-tools.js +0 -438
  664. package/pi-extension/dist/runtime-context.js +0 -288
  665. package/pi-extension/dist/schemas.js +0 -254
  666. package/pi-extension/dist/status.js +0 -449
  667. package/pi-extension/dist/utils.js +0 -467
  668. package/pi-extension/src/runtime-context.ts +0 -347
  669. package/pi-extension/src/status.ts +0 -505
  670. package/plugin-docs/README.md +0 -40
  671. package/plugin-docs/adr/0001-three-modes.md +0 -27
  672. package/plugin-docs/adr/0002-evals-before-hooks.md +0 -17
  673. package/plugin-docs/adr/0003-release-boundary.md +0 -21
  674. package/plugin-docs/adr/0004-discover-replaces-shallow-discovery-skills.md +0 -26
  675. package/plugin-docs/adr/README.md +0 -10
  676. package/plugin-docs/architecture.md +0 -89
  677. package/plugin-docs/output-router.md +0 -396
  678. package/plugin-docs/release-evidence.md +0 -77
  679. package/plugin-docs/skills.md +0 -44
  680. package/plugin-docs/workflow.md +0 -57
  681. package/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
  682. package/skills/discover/ARTIFACT-DESTINATIONS.md +0 -58
  683. package/skills/discover/CHECKPOINTS.md +0 -74
  684. package/skills/discover/SKILL.md +0 -173
  685. package/skills/evaluate-skill/references/eval-patterns.md +0 -41
  686. package/skills/evaluate-skill/references/grading-priority.md +0 -33
  687. package/skills/execute-plan/SKILL.md +0 -133
  688. package/skills/execute-plan/references/execution-map.md +0 -122
  689. package/skills/interview-gate/SKILL.md +0 -90
  690. package/skills/setup-freeflow/references/output-router-setup.md +0 -102
  691. package/skills/workflow/references/workflow-map.md +0 -104
  692. /package/pi-extension/src/{mcp-config.ts → integrations/mcp-config.ts} +0 -0
@@ -0,0 +1,1730 @@
1
+ import { execFile } from "node:child_process";
2
+ import { appendFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ import {
7
+ DEFAULT_OBSERVED_ROUTING_CONFIG,
8
+ DEFAULT_OUTPUT_ROUTER_ENABLED,
9
+ DEFAULT_POST_TOOL_ROUTING,
10
+ DEFAULT_ROUTER_THRESHOLDS,
11
+ DEFAULT_SCRIPT_TRANSFORM_CONFIG,
12
+ DEFAULT_STORAGE_POLICY,
13
+ DEFAULT_VAULT_RETENTION,
14
+ DEFAULT_VAULT_ROOT,
15
+ normalizeFreeflowConfig,
16
+ } from "../../../router/dist/index.js";
17
+ import {
18
+ handleModeCommand,
19
+ readCapabilityState,
20
+ readFreeflowConfig,
21
+ readFreeflowConfigLayers,
22
+ readFreeflowLocalConfig,
23
+ readModeState,
24
+ resetSessionOverrides,
25
+ setSessionCoreOverride,
26
+ VALID_MODES,
27
+ } from "../runtime/runtime-context.js";
28
+ import {
29
+ PiSettingsComponent,
30
+ type SettingsCommitResult,
31
+ type SettingsEntry,
32
+ type SettingsSessionResult,
33
+ } from "./settings-tui.js";
34
+
35
+ const POST_TOOL_ROUTING_VALUES = ["off", "safety-net", "strict"] as const;
36
+ const STORAGE_POLICY_VALUES = ["hybrid-dedupe", "store-everything"] as const;
37
+ const OBSERVED_PERSISTENCE_VALUES = ["none", "metadata-only", "exact"] as const;
38
+ const SCRIPT_LANGUAGES = ["javascript", "python", "jq"] as const;
39
+ const DEFAULT_FREEFLOW_ENABLED = true;
40
+ const DEFAULT_INTERACTION_CONTRACT_ENABLED = true;
41
+ const DEFAULT_SKILLS_ENABLED = true;
42
+ const MODE_VALUES = ["conversation", "workflow", "strict-workflow"];
43
+ const MODE_LABELS = {
44
+ default: "Use configured default",
45
+ conversation: "conversation",
46
+ workflow: "workflow",
47
+ "strict-workflow": "strict-workflow",
48
+ };
49
+ const MODE_DESCRIPTIONS = {
50
+ conversation: "Discussion and read-only inspection",
51
+ workflow: "Adaptive workflow for consequential work",
52
+ "strict-workflow": "Stronger decision and evidence pressure for high-risk work",
53
+ };
54
+ const LOCAL_INHERIT = "inherit";
55
+ const execFileAsync = promisify(execFile);
56
+
57
+ type ConfigScope = "session" | "local" | "repository";
58
+ type ConfigSource = "session" | "local" | "repository" | "builtin";
59
+
60
+ type SettingKind = "boolean" | "enum" | "integer" | "string" | "list" | "json" | "group";
61
+
62
+ type SettingsItem = {
63
+ id: string;
64
+ label: string;
65
+ description: string;
66
+ path?: string[];
67
+ kind: SettingKind;
68
+ value: unknown;
69
+ defaultValue?: unknown;
70
+ values?: string[];
71
+ valueLabels?: Record<string, string>;
72
+ valueDescriptions?: Record<string, string>;
73
+ inactive?: boolean;
74
+ displaySuffix?: string;
75
+ children?: SettingsItem[];
76
+ parse?: (text: string) => unknown;
77
+ format?: (value: unknown) => string;
78
+ configScope?: ConfigScope;
79
+ configValues?: Record<string, unknown>;
80
+ effectiveValue?: unknown;
81
+ effectiveSource?: ConfigSource;
82
+ inheritedValue?: unknown;
83
+ inheritedSource?: ConfigSource;
84
+ localOverrideValue?: unknown;
85
+ transient?: boolean;
86
+ };
87
+
88
+ type OpenSettingsOptions = {
89
+ title: string;
90
+ items: SettingsItem[];
91
+ ctx: any;
92
+ onChange: (item: SettingsItem, value: unknown) => Promise<SettingsCommitResult>;
93
+ initialChoice?: SettingsItem;
94
+ };
95
+
96
+ function isRecord(value: unknown): value is Record<string, unknown> {
97
+ return typeof value === "object" && value !== null && !Array.isArray(value);
98
+ }
99
+
100
+ function cloneJson<T>(value: T): T {
101
+ if (value === undefined) return value;
102
+ try {
103
+ return JSON.parse(JSON.stringify(value));
104
+ } catch (error) {
105
+ throw new Error(`Could not clone settings value: ${error instanceof Error ? error.message : String(error)}`);
106
+ }
107
+ }
108
+
109
+ function getPath(source: unknown, path: string[]): unknown {
110
+ let current = source;
111
+ for (const key of path) {
112
+ if (!isRecord(current)) return undefined;
113
+ current = current[key];
114
+ }
115
+ return current;
116
+ }
117
+
118
+ function setPath(target: Record<string, unknown>, path: string[], value: unknown) {
119
+ let current: Record<string, unknown> = target;
120
+ for (const key of path.slice(0, -1)) {
121
+ const next = current[key];
122
+ if (!isRecord(next)) {
123
+ current[key] = {};
124
+ }
125
+ current = current[key] as Record<string, unknown>;
126
+ }
127
+ current[path[path.length - 1]!] = value;
128
+ }
129
+
130
+ function deletePath(target: Record<string, unknown>, path: string[]) {
131
+ let current: unknown = target;
132
+ const parents: Array<{ object: Record<string, unknown>; key: string }> = [];
133
+ for (const key of path.slice(0, -1)) {
134
+ if (!isRecord(current)) return;
135
+ parents.push({ object: current, key });
136
+ current = current[key];
137
+ }
138
+ if (!isRecord(current)) return;
139
+ delete current[path[path.length - 1]!];
140
+
141
+ for (let index = parents.length - 1; index >= 0; index--) {
142
+ const { object, key } = parents[index]!;
143
+ const child = object[key];
144
+ if (isRecord(child) && Object.keys(child).length === 0) {
145
+ delete object[key];
146
+ }
147
+ }
148
+ }
149
+
150
+ function valuesEqual(a: unknown, b: unknown): boolean {
151
+ return JSON.stringify(a) === JSON.stringify(b);
152
+ }
153
+
154
+ function isEmptyValue(value: unknown): boolean {
155
+ return Array.isArray(value) && value.length === 0;
156
+ }
157
+
158
+ function setConfigValue(config: Record<string, unknown>, item: SettingsItem, value: unknown) {
159
+ if (!item.path?.length) {
160
+ throw new Error(`${item.label} is a settings group, not a writable setting.`);
161
+ }
162
+ if (item.defaultValue !== undefined && valuesEqual(value, item.defaultValue)) {
163
+ deletePath(config, item.path);
164
+ return;
165
+ }
166
+ if (isEmptyValue(value)) {
167
+ deletePath(config, item.path);
168
+ return;
169
+ }
170
+ setPath(config, item.path, value);
171
+ }
172
+
173
+ function parseStringList(text: string): string[] {
174
+ return text
175
+ .split(",")
176
+ .map((item) => item.trim())
177
+ .filter(Boolean);
178
+ }
179
+
180
+ function parsePositiveInteger(text: string): number {
181
+ const value = Number(text.trim());
182
+ if (!Number.isInteger(value) || value <= 0) {
183
+ throw new Error("Expected a positive integer.");
184
+ }
185
+ return value;
186
+ }
187
+
188
+ function parseJsonObject(text: string): unknown {
189
+ const trimmed = text.trim();
190
+ if (!trimmed) return {};
191
+ let parsed: unknown;
192
+ try {
193
+ parsed = JSON.parse(trimmed);
194
+ } catch (error) {
195
+ throw new Error(`Expected valid JSON: ${error instanceof Error ? error.message : String(error)}`);
196
+ }
197
+ if (!isRecord(parsed)) {
198
+ throw new Error("Expected a JSON object.");
199
+ }
200
+ return parsed;
201
+ }
202
+
203
+ function formatList(value: unknown): string {
204
+ return Array.isArray(value) ? value.join(", ") : "";
205
+ }
206
+
207
+ function formatJson(value: unknown): string {
208
+ return JSON.stringify(isRecord(value) ? value : {});
209
+ }
210
+
211
+ function booleanValue(value: unknown): string {
212
+ return value === true ? "enabled" : "disabled";
213
+ }
214
+
215
+ function normalizedSettingsConfig(rawConfig: Record<string, unknown>) {
216
+ const rawRouter = isRecord(rawConfig.outputRouter) ? rawConfig.outputRouter : {};
217
+ const normalized = normalizeFreeflowConfig({
218
+ ...rawConfig,
219
+ outputRouter: { ...rawRouter, enabled: true },
220
+ });
221
+ return normalized.config;
222
+ }
223
+
224
+ function rawValue(rawConfig: Record<string, unknown>, path: string[], fallback: unknown): unknown {
225
+ const value = getPath(rawConfig, path);
226
+ return value === undefined ? fallback : value;
227
+ }
228
+
229
+ function outputRouterItems(rawConfig: Record<string, unknown>, freeflowInactive = false): SettingsItem[] {
230
+ const effective = normalizedSettingsConfig(rawConfig);
231
+ const routerEnabled = normalizeFreeflowConfig(rawConfig).config.outputRouter.enabled;
232
+ const router = effective.outputRouter;
233
+ const scriptTransform = effective.scriptTransform;
234
+ const observedRouting = effective.observedRouting;
235
+ const inactive = freeflowInactive || !routerEnabled;
236
+
237
+ return [
238
+ {
239
+ id: "outputRouter.enabled",
240
+ label: "Output Router",
241
+ description: "Master switch for Freeflow routed evidence tools and router runtime guidance.",
242
+ path: ["outputRouter", "enabled"],
243
+ kind: "boolean",
244
+ value: routerEnabled,
245
+ defaultValue: DEFAULT_OUTPUT_ROUTER_ENABLED,
246
+ inactive: freeflowInactive,
247
+ },
248
+ {
249
+ id: "outputRouter.postToolRouting",
250
+ label: "Native safety net",
251
+ description:
252
+ "Post-process large/noisy native read/bash output. safety-net can replace oversized native output with vaulted routed evidence; strict is stronger.",
253
+ path: ["outputRouter", "postToolRouting"],
254
+ kind: "enum",
255
+ value: router.postToolRouting,
256
+ values: [...POST_TOOL_ROUTING_VALUES],
257
+ valueDescriptions: {
258
+ off: "Leave native tool output unchanged",
259
+ "safety-net": "Route oversized or noisy native output",
260
+ strict: "Apply stronger native output routing",
261
+ },
262
+ defaultValue: DEFAULT_POST_TOOL_ROUTING,
263
+ inactive,
264
+ },
265
+ {
266
+ id: "outputRouter.storagePolicy",
267
+ label: "Storage policy",
268
+ description: "Vault storage behavior for exactness-sensitive and noisy output.",
269
+ path: ["outputRouter", "storagePolicy"],
270
+ kind: "enum",
271
+ value: router.storagePolicy,
272
+ values: [...STORAGE_POLICY_VALUES],
273
+ defaultValue: DEFAULT_STORAGE_POLICY,
274
+ inactive,
275
+ },
276
+ {
277
+ id: "outputRouter.thresholds.largeOutputBytes",
278
+ label: "Large output bytes",
279
+ description: "Byte threshold for treating command/tool output as large.",
280
+ path: ["outputRouter", "thresholds", "largeOutputBytes"],
281
+ kind: "integer",
282
+ value: router.thresholds.largeOutputBytes,
283
+ defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
284
+ parse: parsePositiveInteger,
285
+ inactive,
286
+ },
287
+ {
288
+ id: "outputRouter.thresholds.largeOutputLines",
289
+ label: "Large output lines",
290
+ description: "Line threshold for treating command/tool output as large.",
291
+ path: ["outputRouter", "thresholds", "largeOutputLines"],
292
+ kind: "integer",
293
+ value: router.thresholds.largeOutputLines,
294
+ defaultValue: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
295
+ parse: parsePositiveInteger,
296
+ inactive,
297
+ },
298
+ {
299
+ id: "outputRouter.vault.root",
300
+ label: "Vault root",
301
+ description: "Root directory for recoverable routed output.",
302
+ path: ["outputRouter", "vault", "root"],
303
+ kind: "string",
304
+ value: router.vault.root,
305
+ defaultValue: DEFAULT_VAULT_ROOT,
306
+ inactive,
307
+ },
308
+ {
309
+ id: "outputRouter.vault.retention.ttlDays",
310
+ label: "Vault retention days",
311
+ description: "TTL retention for vaulted output. Set a positive integer; default is 7 days.",
312
+ path: ["outputRouter", "vault", "retention", "ttlDays"],
313
+ kind: "integer",
314
+ value:
315
+ router.vault.retention?.strategy === "ttl" ? router.vault.retention.ttlDays : DEFAULT_VAULT_RETENTION.ttlDays,
316
+ defaultValue: DEFAULT_VAULT_RETENTION.ttlDays,
317
+ parse: parsePositiveInteger,
318
+ inactive,
319
+ },
320
+ {
321
+ id: "outputRouter.generatedPaths",
322
+ label: "Generated paths",
323
+ description:
324
+ "Comma-separated globs for generated repo paths that should be treated as noisy or lower-priority evidence.",
325
+ path: ["outputRouter", "hints", "generatedPathGlobs"],
326
+ kind: "list",
327
+ value: rawValue(
328
+ rawConfig,
329
+ ["outputRouter", "hints", "generatedPathGlobs"],
330
+ rawValue(rawConfig, ["outputRouter", "generatedPaths"], router.hints?.generatedPathGlobs ?? []),
331
+ ),
332
+ format: formatList,
333
+ parse: parseStringList,
334
+ inactive,
335
+ },
336
+ {
337
+ id: "outputRouter.noisyCommandHints",
338
+ label: "Noisy command hints",
339
+ description: "Comma-separated command patterns that are likely to produce noisy output.",
340
+ path: ["outputRouter", "hints", "noisyCommandPatterns"],
341
+ kind: "list",
342
+ value: rawValue(
343
+ rawConfig,
344
+ ["outputRouter", "hints", "noisyCommandPatterns"],
345
+ rawValue(rawConfig, ["outputRouter", "noisyCommandHints"], router.hints?.noisyCommandPatterns ?? []),
346
+ ),
347
+ format: formatList,
348
+ parse: parseStringList,
349
+ inactive,
350
+ },
351
+ {
352
+ id: "outputRouter.scriptTransform.enabled",
353
+ label: "Script transform",
354
+ description:
355
+ "Enable sandboxed script transforms/producers. Requires proof-backed adapters; no unsandboxed fallback.",
356
+ path: ["outputRouter", "scriptTransform", "enabled"],
357
+ kind: "boolean",
358
+ value: scriptTransform.enabled,
359
+ defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
360
+ inactive,
361
+ },
362
+ {
363
+ id: "outputRouter.scriptTransform.languages",
364
+ label: "Script languages",
365
+ description: "Comma-separated sandbox languages to allow: javascript, python, jq.",
366
+ path: ["outputRouter", "scriptTransform", "languages"],
367
+ kind: "list",
368
+ value: scriptTransform.languages,
369
+ defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
370
+ format: formatList,
371
+ parse: (text) => parseStringList(text).filter((value) => (SCRIPT_LANGUAGES as readonly string[]).includes(value)),
372
+ inactive,
373
+ },
374
+ {
375
+ id: "outputRouter.scriptTransform.limits.timeoutMs",
376
+ label: "Script timeout ms",
377
+ description: "Maximum sandboxed script execution time.",
378
+ path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
379
+ kind: "integer",
380
+ value: scriptTransform.limits.timeoutMs,
381
+ defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
382
+ parse: parsePositiveInteger,
383
+ inactive,
384
+ },
385
+ {
386
+ id: "outputRouter.scriptTransform.limits.maxInputBytes",
387
+ label: "Script max input bytes",
388
+ description: "Maximum input size for sandboxed scripts.",
389
+ path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
390
+ kind: "integer",
391
+ value: scriptTransform.limits.maxInputBytes,
392
+ defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
393
+ parse: parsePositiveInteger,
394
+ inactive,
395
+ },
396
+ {
397
+ id: "outputRouter.scriptTransform.limits.maxOutputBytes",
398
+ label: "Script max output bytes",
399
+ description: "Maximum output size for sandboxed scripts.",
400
+ path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
401
+ kind: "integer",
402
+ value: scriptTransform.limits.maxOutputBytes,
403
+ defaultValue: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
404
+ parse: parsePositiveInteger,
405
+ inactive,
406
+ },
407
+ {
408
+ id: "outputRouter.observedRouting.enabled",
409
+ label: "Observed routing",
410
+ description: "Route configured host tool results after they run, e.g. MCP/web/fetch/code-search output.",
411
+ path: ["outputRouter", "observedRouting", "enabled"],
412
+ kind: "boolean",
413
+ value: observedRouting.enabled,
414
+ defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
415
+ inactive,
416
+ },
417
+ ...observedProducerItems("web", "Web search", observedRouting.web, inactive),
418
+ ...observedProducerItems("fetch", "Fetch content", observedRouting.fetch, inactive),
419
+ ...observedProducerItems("codeSearch", "Code search", observedRouting.codeSearch, inactive),
420
+ {
421
+ id: "outputRouter.observedRouting.mcp.servers",
422
+ label: "MCP servers JSON",
423
+ description: 'JSON object keyed by MCP server id. Example: {"github":{"enabled":true,"persistence":"exact"}}',
424
+ path: ["outputRouter", "observedRouting", "mcp", "servers"],
425
+ kind: "json",
426
+ value: observedRouting.mcp?.servers ?? {},
427
+ defaultValue: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
428
+ format: formatJson,
429
+ parse: parseJsonObject,
430
+ inactive,
431
+ },
432
+ ];
433
+ }
434
+
435
+ function observedProducerItems(
436
+ id: "web" | "fetch" | "codeSearch",
437
+ label: string,
438
+ value: any,
439
+ inactive: boolean,
440
+ ): SettingsItem[] {
441
+ return [
442
+ {
443
+ id: `outputRouter.observedRouting.${id}.enabled`,
444
+ label: `${label} observed`,
445
+ description: `Enable observed routing for ${label.toLowerCase()} output.`,
446
+ path: ["outputRouter", "observedRouting", id, "enabled"],
447
+ kind: "boolean",
448
+ value: value?.enabled === true,
449
+ defaultValue: false,
450
+ inactive,
451
+ },
452
+ {
453
+ id: `outputRouter.observedRouting.${id}.persistence`,
454
+ label: `${label} persistence`,
455
+ description: "Persistence mode for this observed producer: none, metadata-only, or exact.",
456
+ path: ["outputRouter", "observedRouting", id, "persistence"],
457
+ kind: "enum",
458
+ value: value?.persistence ?? "none",
459
+ values: [...OBSERVED_PERSISTENCE_VALUES],
460
+ valueDescriptions: {
461
+ none: "Do not persist observed output",
462
+ "metadata-only": "Persist metadata without raw output",
463
+ exact: "Persist exact recoverable output",
464
+ },
465
+ defaultValue: value?.enabled === true ? undefined : "none",
466
+ inactive,
467
+ },
468
+ ];
469
+ }
470
+
471
+ function configValueForChoice(item: SettingsItem, value: unknown): unknown {
472
+ const key = String(value);
473
+ if (item.configValues && Object.hasOwn(item.configValues, key)) {
474
+ return item.configValues[key];
475
+ }
476
+ return value;
477
+ }
478
+
479
+ function effectiveItemValue(item: SettingsItem): unknown {
480
+ return item.effectiveValue !== undefined ? item.effectiveValue : item.value;
481
+ }
482
+
483
+ function formatCoreValue(value: unknown): string {
484
+ return typeof value === "boolean" ? booleanValue(value) : String(value ?? "");
485
+ }
486
+
487
+ function coreDisplaySuffix(item: SettingsItem, inactive = false): string {
488
+ const source = item.effectiveSource ?? "builtin";
489
+ const parts =
490
+ item.configScope === "repository" && source === "local"
491
+ ? [`effective ${formatCoreValue(effectiveItemValue(item))}`, source]
492
+ : [source];
493
+ if (inactive) parts.push("inactive");
494
+ return `(${parts.join(" · ")})`;
495
+ }
496
+
497
+ function updateScopedItemState(item: SettingsItem, value: unknown) {
498
+ if (!item.configScope) return;
499
+ const configValue = configValueForChoice(item, value);
500
+ if (item.configScope === "session" || item.configScope === "local") {
501
+ item.effectiveValue = configValue === undefined ? item.inheritedValue : configValue;
502
+ item.effectiveSource = configValue === undefined ? (item.inheritedSource ?? "builtin") : item.configScope;
503
+ return;
504
+ }
505
+ if (item.localOverrideValue !== undefined) {
506
+ item.effectiveValue = item.localOverrideValue;
507
+ item.effectiveSource = "local";
508
+ return;
509
+ }
510
+ item.effectiveValue = configValue;
511
+ item.effectiveSource =
512
+ item.defaultValue !== undefined && valuesEqual(configValue, item.defaultValue) ? "builtin" : "repository";
513
+ }
514
+
515
+ type ScopedBooleanItemOptions = {
516
+ scope: ConfigScope;
517
+ rawConfig: Record<string, unknown>;
518
+ localConfig: Record<string, unknown>;
519
+ id: string;
520
+ label: string;
521
+ description: string;
522
+ path: string[];
523
+ effectiveValue: boolean;
524
+ effectiveSource: ConfigSource;
525
+ defaultValue: boolean;
526
+ };
527
+
528
+ function createScopedBooleanItem(options: ScopedBooleanItemOptions): SettingsItem {
529
+ const repositoryValue = getPath(options.rawConfig, options.path);
530
+ const inheritedValue = typeof repositoryValue === "boolean" ? repositoryValue : options.defaultValue;
531
+ const inheritedSource: ConfigSource = typeof repositoryValue === "boolean" ? "repository" : "builtin";
532
+ const localValue = getPath(options.localConfig, options.path);
533
+ let item: SettingsItem;
534
+ if (options.scope === "local") {
535
+ const selectedValue = typeof localValue === "boolean" ? String(localValue) : LOCAL_INHERIT;
536
+ item = {
537
+ id: options.id,
538
+ label: options.label,
539
+ description: `${options.description} Choose inherit to use the repository value; use /freeflow settings repo to edit shared defaults.`,
540
+ path: options.path,
541
+ kind: "enum",
542
+ value: selectedValue,
543
+ values: [LOCAL_INHERIT, "true", "false"],
544
+ valueLabels: {
545
+ inherit: "Inherit repository",
546
+ true: "enabled",
547
+ false: "disabled",
548
+ },
549
+ valueDescriptions: {
550
+ inherit: `Use ${formatCoreValue(inheritedValue)} from ${inheritedSource}.`,
551
+ true: "Set a personal enabled override.",
552
+ false: "Set a personal disabled override.",
553
+ },
554
+ format: (value) => {
555
+ if (value === LOCAL_INHERIT) return LOCAL_INHERIT;
556
+ return booleanValue(value === "true");
557
+ },
558
+ configScope: "local",
559
+ configValues: {
560
+ inherit: undefined,
561
+ true: true,
562
+ false: false,
563
+ },
564
+ effectiveValue: options.effectiveValue,
565
+ effectiveSource: options.effectiveSource,
566
+ inheritedValue,
567
+ inheritedSource,
568
+ };
569
+ } else {
570
+ item = {
571
+ id: options.id,
572
+ label: options.label,
573
+ description: `${options.description} This edits shared .freeflow/config.json.`,
574
+ path: options.path,
575
+ kind: "boolean",
576
+ value: inheritedValue,
577
+ defaultValue: options.defaultValue,
578
+ configScope: "repository",
579
+ effectiveValue: options.effectiveValue,
580
+ effectiveSource: options.effectiveSource,
581
+ localOverrideValue: typeof localValue === "boolean" ? localValue : undefined,
582
+ };
583
+ }
584
+ item.displaySuffix = coreDisplaySuffix(item);
585
+ return item;
586
+ }
587
+
588
+ type ScopedDefaultModeItemOptions = {
589
+ scope: ConfigScope;
590
+ repositoryDefaultMode: string;
591
+ repositoryDefaultSource: ConfigSource;
592
+ localDefaultMode?: string;
593
+ effectiveValue: string;
594
+ effectiveSource: ConfigSource;
595
+ };
596
+
597
+ function createScopedDefaultModeItem(options: ScopedDefaultModeItemOptions): SettingsItem {
598
+ let item: SettingsItem;
599
+ if (options.scope === "local") {
600
+ item = {
601
+ id: "freeflow.defaultMode",
602
+ label: "Default mode",
603
+ description:
604
+ "Personal default mode for this checkout. Choose inherit to use the repository default; use /freeflow settings repo to edit shared defaults.",
605
+ path: ["defaultMode"],
606
+ kind: "enum",
607
+ value: options.localDefaultMode ?? LOCAL_INHERIT,
608
+ values: [LOCAL_INHERIT, ...MODE_VALUES],
609
+ valueLabels: {
610
+ inherit: "Inherit repository",
611
+ ...MODE_LABELS,
612
+ },
613
+ valueDescriptions: {
614
+ inherit: `Use ${options.repositoryDefaultMode} from ${options.repositoryDefaultSource}.`,
615
+ ...MODE_DESCRIPTIONS,
616
+ },
617
+ format: (value) => String(value),
618
+ configScope: "local",
619
+ configValues: { inherit: undefined },
620
+ effectiveValue: options.effectiveValue,
621
+ effectiveSource: options.effectiveSource,
622
+ inheritedValue: options.repositoryDefaultMode,
623
+ inheritedSource: options.repositoryDefaultSource,
624
+ };
625
+ } else {
626
+ item = {
627
+ id: "freeflow.defaultMode",
628
+ label: "Default mode",
629
+ description:
630
+ "Shared repository default Freeflow mode used when Skills are enabled. This edits .freeflow/config.json.",
631
+ path: ["defaultMode"],
632
+ kind: "enum",
633
+ value: options.repositoryDefaultMode,
634
+ values: MODE_VALUES,
635
+ valueLabels: MODE_LABELS,
636
+ valueDescriptions: MODE_DESCRIPTIONS,
637
+ configScope: "repository",
638
+ effectiveValue: options.effectiveValue,
639
+ effectiveSource: options.effectiveSource,
640
+ localOverrideValue: options.localDefaultMode,
641
+ };
642
+ }
643
+ item.displaySuffix = coreDisplaySuffix(item);
644
+ return item;
645
+ }
646
+
647
+ function sessionModeDisplaySuffix(
648
+ sessionMode: string,
649
+ defaultMode: string,
650
+ defaultSource: ConfigSource,
651
+ inactive: boolean,
652
+ ): string | undefined {
653
+ if (sessionMode !== "default") return undefined;
654
+ const inactiveSuffix = inactive ? " · inactive" : "";
655
+ return `(${defaultMode} · ${defaultSource}${inactiveSuffix})`;
656
+ }
657
+
658
+ function validModeOrUndefined(value: unknown): string | undefined {
659
+ return typeof value === "string" && VALID_MODES.has(value) ? value : undefined;
660
+ }
661
+
662
+ function resolveSettingsCoreView(
663
+ rawConfig: Record<string, unknown>,
664
+ layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>,
665
+ ) {
666
+ const localConfig = layers?.local.valid && isRecord(layers.local.parsed) ? layers.local.parsed : {};
667
+ const repositorySkillsValue = getPath(rawConfig, ["skills"]);
668
+ const repositorySkills = isRecord(repositorySkillsValue) ? repositorySkillsValue : {};
669
+ const fallbackCore = {
670
+ enabled: getPath(rawConfig, ["enabled"]) !== false,
671
+ interactionContract: getPath(rawConfig, ["interactionContract"]) !== false,
672
+ skills: { enabled: getPath(repositorySkills, ["enabled"]) !== false },
673
+ defaultMode: validModeOrUndefined(rawConfig.defaultMode) ?? "workflow",
674
+ };
675
+ const fallbackSources = {
676
+ enabled: typeof getPath(rawConfig, ["enabled"]) === "boolean" ? "repository" : "builtin",
677
+ interactionContract: typeof getPath(rawConfig, ["interactionContract"]) === "boolean" ? "repository" : "builtin",
678
+ skillsEnabled: typeof getPath(repositorySkills, ["enabled"]) === "boolean" ? "repository" : "builtin",
679
+ defaultMode: validModeOrUndefined(rawConfig.defaultMode) !== undefined ? "repository" : "builtin",
680
+ } as const;
681
+ return {
682
+ localConfig,
683
+ core: layers?.coreConfig ?? fallbackCore,
684
+ sources: (layers?.sources ?? fallbackSources) as {
685
+ enabled: ConfigSource;
686
+ interactionContract: ConfigSource;
687
+ skillsEnabled: ConfigSource;
688
+ defaultMode: ConfigSource;
689
+ },
690
+ };
691
+ }
692
+
693
+ function groupEnabled(items: SettingsItem[], id: string): boolean {
694
+ return items.find((item) => item.id === id)?.value === true;
695
+ }
696
+
697
+ function createSessionBooleanItem(options: {
698
+ id: string;
699
+ label: string;
700
+ description: string;
701
+ key: "enabled" | "interactionContract" | "skillsEnabled";
702
+ inheritedValue: boolean;
703
+ inheritedSource: ConfigSource;
704
+ effectiveValue: boolean;
705
+ effectiveSource: ConfigSource;
706
+ sessionOverrides: Record<string, boolean>;
707
+ }): SettingsItem {
708
+ const override = options.sessionOverrides[options.key];
709
+ const item: SettingsItem = {
710
+ id: options.id,
711
+ label: options.label,
712
+ description: options.description,
713
+ kind: "enum",
714
+ value: typeof override === "boolean" ? String(override) : LOCAL_INHERIT,
715
+ values: [LOCAL_INHERIT, "true", "false"],
716
+ valueLabels: {
717
+ inherit: "Inherit configured value",
718
+ true: "Enabled for this session",
719
+ false: "Disabled for this session",
720
+ },
721
+ valueDescriptions: {
722
+ inherit: `Use ${formatCoreValue(options.inheritedValue)} from ${options.inheritedSource}.`,
723
+ true: "Temporarily enable this setting for the current Pi session.",
724
+ false: "Temporarily disable this setting for the current Pi session.",
725
+ },
726
+ format: (value) => {
727
+ if (value === LOCAL_INHERIT) return LOCAL_INHERIT;
728
+ return booleanValue(value === "true");
729
+ },
730
+ configScope: "session",
731
+ configValues: {
732
+ inherit: undefined,
733
+ true: true,
734
+ false: false,
735
+ },
736
+ effectiveValue: options.effectiveValue,
737
+ effectiveSource: options.effectiveSource,
738
+ inheritedValue: options.inheritedValue,
739
+ inheritedSource: options.inheritedSource,
740
+ };
741
+ item.displaySuffix = coreDisplaySuffix(item);
742
+ return item;
743
+ }
744
+
745
+ function sessionFreeflowItems(
746
+ state: Awaited<ReturnType<typeof readCapabilityState>>,
747
+ modeState: Awaited<ReturnType<typeof readModeState>>,
748
+ ): SettingsItem[] {
749
+ const sessionOverrides = state.sessionOverrides as Record<string, boolean>;
750
+ const configured = state.configuredCoreConfig;
751
+ const configuredSources = state.configuredSources as {
752
+ enabled: ConfigSource;
753
+ interactionContract: ConfigSource;
754
+ skillsEnabled: ConfigSource;
755
+ };
756
+ const effectiveSources = state.configSources as {
757
+ enabled: ConfigSource;
758
+ interactionContract: ConfigSource;
759
+ skillsEnabled: ConfigSource;
760
+ };
761
+
762
+ const freeflowItem = createSessionBooleanItem({
763
+ id: "freeflow.enabled",
764
+ label: "Freeflow",
765
+ description: "Temporary master override for this Pi session.",
766
+ key: "enabled",
767
+ inheritedValue: configured.enabled,
768
+ inheritedSource: configuredSources.enabled,
769
+ effectiveValue: state.enabled,
770
+ effectiveSource: effectiveSources.enabled,
771
+ sessionOverrides,
772
+ });
773
+ const interactionItem = createSessionBooleanItem({
774
+ id: "freeflow.interactionContract",
775
+ label: "Interaction Contract",
776
+ description: "Temporary Interaction Contract override for this Pi session.",
777
+ key: "interactionContract",
778
+ inheritedValue: configured.interactionContract,
779
+ inheritedSource: configuredSources.interactionContract,
780
+ effectiveValue: state.interactionContract.enabled,
781
+ effectiveSource: effectiveSources.interactionContract,
782
+ sessionOverrides,
783
+ });
784
+ const skillsItem = createSessionBooleanItem({
785
+ id: "freeflow.skills.enabled",
786
+ label: "Skills",
787
+ description: "Temporary Freeflow Skills override for this Pi session.",
788
+ key: "skillsEnabled",
789
+ inheritedValue: configured.skills.enabled,
790
+ inheritedSource: configuredSources.skillsEnabled,
791
+ effectiveValue: state.skills.enabled,
792
+ effectiveSource: effectiveSources.skillsEnabled,
793
+ sessionOverrides,
794
+ });
795
+
796
+ const freeflowInactive = !state.enabled;
797
+ const skillsEnabled = state.skills.enabled;
798
+ interactionItem.inactive = freeflowInactive;
799
+ skillsItem.inactive = freeflowInactive;
800
+
801
+ const sessionMode = modeState.currentMode ?? "default";
802
+ const sessionModeItem: SettingsItem = {
803
+ id: "freeflow.sessionMode",
804
+ label: "Mode",
805
+ description: "Temporary mode override for this Pi session.",
806
+ kind: "enum",
807
+ value: sessionMode,
808
+ values: ["default", ...MODE_VALUES],
809
+ valueLabels: {
810
+ ...MODE_LABELS,
811
+ default: "Use configured default",
812
+ },
813
+ valueDescriptions: {
814
+ default: `${modeState.defaultMode} from ${modeState.defaultModeSource}`,
815
+ ...MODE_DESCRIPTIONS,
816
+ },
817
+ inactive: freeflowInactive || !skillsEnabled,
818
+ displaySuffix: sessionModeDisplaySuffix(
819
+ sessionMode,
820
+ modeState.defaultMode,
821
+ modeState.defaultModeSource,
822
+ freeflowInactive || !skillsEnabled,
823
+ ),
824
+ inheritedValue: modeState.defaultMode,
825
+ inheritedSource: modeState.defaultModeSource,
826
+ };
827
+
828
+ return [
829
+ freeflowItem,
830
+ interactionItem,
831
+ skillsItem,
832
+ sessionModeItem,
833
+ {
834
+ id: "freeflow.session.reset",
835
+ label: "Reset session overrides",
836
+ description: "Clear Freeflow, Interaction Contract, Skills, and mode overrides for this Pi session.",
837
+ kind: "enum",
838
+ value: "available",
839
+ values: ["reset"],
840
+ valueLabels: { reset: "Reset all session overrides" },
841
+ valueDescriptions: { reset: "Return every session setting to its configured value." },
842
+ format: () => "available",
843
+ transient: true,
844
+ },
845
+ ];
846
+ }
847
+
848
+ function freeflowItems(
849
+ rawConfig: Record<string, unknown>,
850
+ modeState?: Awaited<ReturnType<typeof readModeState>>,
851
+ options: {
852
+ scope?: ConfigScope;
853
+ layers?: Awaited<ReturnType<typeof readFreeflowConfigLayers>>;
854
+ } = {},
855
+ ): SettingsItem[] {
856
+ const scope = options.scope ?? "repository";
857
+ const layers = options.layers;
858
+ const { localConfig, core, sources } = resolveSettingsCoreView(rawConfig, layers);
859
+
860
+ const freeflowItem = createScopedBooleanItem({
861
+ scope,
862
+ rawConfig,
863
+ localConfig,
864
+ id: "freeflow.enabled",
865
+ label: "Freeflow",
866
+ description: "Master switch for the Interaction Contract and Freeflow skills in this checkout.",
867
+ path: ["enabled"],
868
+ effectiveValue: core.enabled,
869
+ effectiveSource: sources.enabled,
870
+ defaultValue: DEFAULT_FREEFLOW_ENABLED,
871
+ });
872
+ const interactionItem = createScopedBooleanItem({
873
+ scope,
874
+ rawConfig,
875
+ localConfig,
876
+ id: "freeflow.interactionContract",
877
+ label: "Interaction Contract",
878
+ description: "Apply Freeflow's compact turn-interpretation and collaboration guidance.",
879
+ path: ["interactionContract"],
880
+ effectiveValue: core.interactionContract,
881
+ effectiveSource: sources.interactionContract,
882
+ defaultValue: DEFAULT_INTERACTION_CONTRACT_ENABLED,
883
+ });
884
+ const skillsItem = createScopedBooleanItem({
885
+ scope,
886
+ rawConfig,
887
+ localConfig,
888
+ id: "freeflow.skills.enabled",
889
+ label: "Skills",
890
+ description: "Expose Freeflow skills and load Workflow once on the first turn.",
891
+ path: ["skills", "enabled"],
892
+ effectiveValue: core.skills.enabled,
893
+ effectiveSource: sources.skillsEnabled,
894
+ defaultValue: DEFAULT_SKILLS_ENABLED,
895
+ });
896
+
897
+ const repositoryModeValue = validModeOrUndefined(rawConfig.defaultMode);
898
+ const repositoryDefaultMode = repositoryModeValue ?? "workflow";
899
+ const repositoryDefaultSource: ConfigSource = repositoryModeValue ? "repository" : "builtin";
900
+ const localDefaultMode = validModeOrUndefined(localConfig.defaultMode);
901
+ const defaultModeItem = createScopedDefaultModeItem({
902
+ scope,
903
+ repositoryDefaultMode,
904
+ repositoryDefaultSource,
905
+ localDefaultMode,
906
+ effectiveValue: core.defaultMode,
907
+ effectiveSource: sources.defaultMode,
908
+ });
909
+
910
+ const freeflowInactive = !core.enabled;
911
+ const skillsEnabled = core.skills.enabled;
912
+ interactionItem.inactive = freeflowInactive;
913
+ skillsItem.inactive = freeflowInactive;
914
+ defaultModeItem.inactive = freeflowInactive;
915
+ defaultModeItem.displaySuffix = coreDisplaySuffix(defaultModeItem, freeflowInactive || !skillsEnabled);
916
+ const sessionMode = modeState?.currentMode ?? "default";
917
+ const sessionModeItem: SettingsItem = {
918
+ id: "freeflow.sessionMode",
919
+ label: "Session mode",
920
+ description:
921
+ "Temporary mode override for this Pi session. Use configured default clears the override without changing either config file.",
922
+ kind: "enum",
923
+ value: sessionMode,
924
+ values: ["default", ...MODE_VALUES],
925
+ valueLabels: {
926
+ ...MODE_LABELS,
927
+ default: "Use configured default",
928
+ },
929
+ valueDescriptions: {
930
+ default: `${core.defaultMode} from ${sources.defaultMode}`,
931
+ ...MODE_DESCRIPTIONS,
932
+ },
933
+ inactive: freeflowInactive || !skillsEnabled,
934
+ displaySuffix: sessionModeDisplaySuffix(
935
+ sessionMode,
936
+ core.defaultMode,
937
+ sources.defaultMode,
938
+ freeflowInactive || !skillsEnabled,
939
+ ),
940
+ };
941
+
942
+ const routerItems = outputRouterItems(rawConfig, freeflowInactive);
943
+ const routerEnabled = groupEnabled(routerItems, "outputRouter.enabled");
944
+
945
+ return [
946
+ freeflowItem,
947
+ interactionItem,
948
+ skillsItem,
949
+ sessionModeItem,
950
+ defaultModeItem,
951
+ {
952
+ id: "outputRouter.group",
953
+ label: "Output Router",
954
+ description:
955
+ "Shared repository settings for routed evidence tools, native output safety net, vault storage, script transforms, and observed tool routing.",
956
+ kind: "group",
957
+ value: routerEnabled,
958
+ inactive: freeflowInactive,
959
+ displaySuffix: "(repository)",
960
+ children: routerItems,
961
+ },
962
+ ];
963
+ }
964
+
965
+ function migrateLegacyRouterConfig(config: Record<string, unknown>) {
966
+ const outputRouter = isRecord(config.outputRouter) ? config.outputRouter : undefined;
967
+ if (!outputRouter) return;
968
+ if (config.scriptTransform !== undefined && outputRouter.scriptTransform === undefined) {
969
+ outputRouter.scriptTransform = config.scriptTransform;
970
+ delete config.scriptTransform;
971
+ }
972
+ if (config.observedRouting !== undefined && outputRouter.observedRouting === undefined) {
973
+ outputRouter.observedRouting = config.observedRouting;
974
+ delete config.observedRouting;
975
+ }
976
+
977
+ const thresholds = isRecord(outputRouter.thresholds) ? outputRouter.thresholds : {};
978
+ if (outputRouter.largeOutputBytes !== undefined && thresholds.largeOutputBytes === undefined) {
979
+ thresholds.largeOutputBytes = outputRouter.largeOutputBytes;
980
+ outputRouter.thresholds = thresholds;
981
+ delete outputRouter.largeOutputBytes;
982
+ }
983
+ if (outputRouter.largeOutputLines !== undefined && thresholds.largeOutputLines === undefined) {
984
+ thresholds.largeOutputLines = outputRouter.largeOutputLines;
985
+ outputRouter.thresholds = thresholds;
986
+ delete outputRouter.largeOutputLines;
987
+ }
988
+
989
+ const vault = isRecord(outputRouter.vault) ? outputRouter.vault : {};
990
+ if (outputRouter.vaultRoot !== undefined && vault.root === undefined) {
991
+ vault.root = outputRouter.vaultRoot;
992
+ outputRouter.vault = vault;
993
+ delete outputRouter.vaultRoot;
994
+ }
995
+ if (outputRouter.vaultRetentionDays !== undefined && !isRecord(vault.retention)) {
996
+ vault.retention = {
997
+ strategy: "ttl",
998
+ ttlDays: outputRouter.vaultRetentionDays,
999
+ };
1000
+ outputRouter.vault = vault;
1001
+ delete outputRouter.vaultRetentionDays;
1002
+ }
1003
+
1004
+ const hints = isRecord(outputRouter.hints) ? outputRouter.hints : {};
1005
+ if (outputRouter.generatedPaths !== undefined && hints.generatedPathGlobs === undefined) {
1006
+ hints.generatedPathGlobs = outputRouter.generatedPaths;
1007
+ outputRouter.hints = hints;
1008
+ delete outputRouter.generatedPaths;
1009
+ }
1010
+ if (outputRouter.noisyCommandHints !== undefined && hints.noisyCommandPatterns === undefined) {
1011
+ hints.noisyCommandPatterns = outputRouter.noisyCommandHints;
1012
+ outputRouter.hints = hints;
1013
+ delete outputRouter.noisyCommandHints;
1014
+ }
1015
+ }
1016
+
1017
+ function pruneKnownDefaults(config: Record<string, unknown>) {
1018
+ const defaultPaths: Array<{ path: string[]; value: unknown }> = [
1019
+ { path: ["enabled"], value: DEFAULT_FREEFLOW_ENABLED },
1020
+ {
1021
+ path: ["interactionContract"],
1022
+ value: DEFAULT_INTERACTION_CONTRACT_ENABLED,
1023
+ },
1024
+ { path: ["skills", "enabled"], value: DEFAULT_SKILLS_ENABLED },
1025
+ { path: ["outputRouter", "enabled"], value: DEFAULT_OUTPUT_ROUTER_ENABLED },
1026
+ {
1027
+ path: ["outputRouter", "postToolRouting"],
1028
+ value: DEFAULT_POST_TOOL_ROUTING,
1029
+ },
1030
+ { path: ["outputRouter", "storagePolicy"], value: DEFAULT_STORAGE_POLICY },
1031
+ {
1032
+ path: ["outputRouter", "thresholds", "largeOutputBytes"],
1033
+ value: DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
1034
+ },
1035
+ {
1036
+ path: ["outputRouter", "thresholds", "largeOutputLines"],
1037
+ value: DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
1038
+ },
1039
+ { path: ["outputRouter", "vault", "root"], value: DEFAULT_VAULT_ROOT },
1040
+ {
1041
+ path: ["outputRouter", "vault", "retention", "ttlDays"],
1042
+ value: DEFAULT_VAULT_RETENTION.ttlDays,
1043
+ },
1044
+ {
1045
+ path: ["outputRouter", "scriptTransform", "enabled"],
1046
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled,
1047
+ },
1048
+ {
1049
+ path: ["outputRouter", "scriptTransform", "sandbox"],
1050
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.sandbox,
1051
+ },
1052
+ {
1053
+ path: ["outputRouter", "scriptTransform", "languages"],
1054
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.languages,
1055
+ },
1056
+ {
1057
+ path: ["outputRouter", "scriptTransform", "network"],
1058
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.network,
1059
+ },
1060
+ {
1061
+ path: ["outputRouter", "scriptTransform", "rawScriptPersistence"],
1062
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.rawScriptPersistence,
1063
+ },
1064
+ {
1065
+ path: ["outputRouter", "scriptTransform", "limits", "timeoutMs"],
1066
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.timeoutMs,
1067
+ },
1068
+ {
1069
+ path: ["outputRouter", "scriptTransform", "limits", "maxInputBytes"],
1070
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxInputBytes,
1071
+ },
1072
+ {
1073
+ path: ["outputRouter", "scriptTransform", "limits", "maxOutputBytes"],
1074
+ value: DEFAULT_SCRIPT_TRANSFORM_CONFIG.limits.maxOutputBytes,
1075
+ },
1076
+ {
1077
+ path: ["outputRouter", "observedRouting", "enabled"],
1078
+ value: DEFAULT_OBSERVED_ROUTING_CONFIG.enabled,
1079
+ },
1080
+ {
1081
+ path: ["outputRouter", "observedRouting", "onRoutingFailure"],
1082
+ value: DEFAULT_OBSERVED_ROUTING_CONFIG.onRoutingFailure,
1083
+ },
1084
+ {
1085
+ path: ["outputRouter", "observedRouting", "mcp", "servers"],
1086
+ value: DEFAULT_OBSERVED_ROUTING_CONFIG.mcp.servers,
1087
+ },
1088
+ {
1089
+ path: ["outputRouter", "observedRouting", "web", "enabled"],
1090
+ value: DEFAULT_OBSERVED_ROUTING_CONFIG.web.enabled,
1091
+ },
1092
+ {
1093
+ path: ["outputRouter", "observedRouting", "fetch", "enabled"],
1094
+ value: DEFAULT_OBSERVED_ROUTING_CONFIG.fetch.enabled,
1095
+ },
1096
+ {
1097
+ path: ["outputRouter", "observedRouting", "codeSearch", "enabled"],
1098
+ value: DEFAULT_OBSERVED_ROUTING_CONFIG.codeSearch.enabled,
1099
+ },
1100
+ ];
1101
+
1102
+ for (const item of defaultPaths) {
1103
+ if (valuesEqual(getPath(config, item.path), item.value)) {
1104
+ deletePath(config, item.path);
1105
+ }
1106
+ }
1107
+
1108
+ for (const producer of ["web", "fetch", "codeSearch"]) {
1109
+ const enabledPath = ["outputRouter", "observedRouting", producer, "enabled"];
1110
+ const persistencePath = ["outputRouter", "observedRouting", producer, "persistence"];
1111
+ if (getPath(config, enabledPath) !== true && getPath(config, persistencePath) === "none") {
1112
+ deletePath(config, persistencePath);
1113
+ }
1114
+ }
1115
+
1116
+ const servers = getPath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
1117
+ if (isRecord(servers) && Object.keys(servers).length === 0) {
1118
+ deletePath(config, ["outputRouter", "observedRouting", "mcp", "servers"]);
1119
+ }
1120
+ }
1121
+
1122
+ async function ensureLocalConfigIgnored(cwd: string) {
1123
+ let gitPath: string;
1124
+ try {
1125
+ const result = await execFileAsync("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"]);
1126
+ gitPath = result.stdout.trim();
1127
+ } catch {
1128
+ return;
1129
+ }
1130
+
1131
+ const tracked = await execFileAsync("git", ["-C", cwd, "ls-files", "--", ".freeflow/local.json"]);
1132
+ if (tracked.stdout.trim()) {
1133
+ throw new Error(
1134
+ ".freeflow/local.json is tracked by git; remove it from the index before writing personal overrides.",
1135
+ );
1136
+ }
1137
+
1138
+ try {
1139
+ await execFileAsync("git", ["-C", cwd, "check-ignore", "-q", "--", ".freeflow/local.json"]);
1140
+ return;
1141
+ } catch {
1142
+ // Add a local exclude when the repository does not already ignore the file.
1143
+ }
1144
+
1145
+ const excludePath = isAbsolute(gitPath) ? gitPath : resolve(cwd, gitPath);
1146
+ let existing = "";
1147
+ try {
1148
+ existing = await readFile(excludePath, "utf8");
1149
+ } catch {
1150
+ // The git metadata path may not exist yet in a minimal repository.
1151
+ }
1152
+ const rule = ".freeflow/local.json";
1153
+ if (existing.split(/\r?\n/).includes(rule)) return;
1154
+ await mkdir(dirname(excludePath), { recursive: true });
1155
+ const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
1156
+ await appendFile(excludePath, `${prefix}${rule}\n`, "utf8");
1157
+ }
1158
+
1159
+ async function updateConfig(
1160
+ cwd: string,
1161
+ item: SettingsItem,
1162
+ value: unknown,
1163
+ scope: ConfigScope = item.configScope ?? "repository",
1164
+ ) {
1165
+ if (scope === "local") {
1166
+ if (!item.path?.length) {
1167
+ throw new Error(`${item.label} is a settings group, not a writable setting.`);
1168
+ }
1169
+ const current = await readFreeflowLocalConfig(cwd);
1170
+ const next = cloneJson(current) as Record<string, unknown>;
1171
+ const configValue = configValueForChoice(item, value);
1172
+ const previousValue = getPath(current, item.path);
1173
+ if (configValue === undefined || isEmptyValue(configValue)) {
1174
+ if (previousValue === undefined) return;
1175
+ deletePath(next, item.path);
1176
+ } else {
1177
+ setPath(next, item.path, configValue);
1178
+ }
1179
+ const path = join(cwd, ".freeflow/local.json");
1180
+ await ensureLocalConfigIgnored(cwd);
1181
+ if (Object.keys(next).length === 0) {
1182
+ await rm(path, { force: true });
1183
+ return;
1184
+ }
1185
+ await mkdir(join(cwd, ".freeflow"), { recursive: true });
1186
+ await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
1187
+ return;
1188
+ }
1189
+
1190
+ const current = await readFreeflowConfig(cwd);
1191
+ const next = cloneJson(current) as Record<string, unknown>;
1192
+ setConfigValue(next, item, value);
1193
+ migrateLegacyRouterConfig(next);
1194
+ pruneKnownDefaults(next);
1195
+ await mkdir(join(cwd, ".freeflow"), { recursive: true });
1196
+ await writeFile(join(cwd, ".freeflow/config.json"), `${JSON.stringify(next, null, 2)}\n`, "utf8");
1197
+ }
1198
+
1199
+ function valueForDisplay(item: SettingsItem): string {
1200
+ let value: string;
1201
+ if (item.configScope === "local" || item.configScope === "session") {
1202
+ value = formatCoreValue(effectiveItemValue(item));
1203
+ } else if (item.kind === "boolean") {
1204
+ value = booleanValue(item.value);
1205
+ } else if (item.kind === "group") {
1206
+ const status = typeof item.value === "boolean" ? booleanValue(item.value) : String(item.value ?? "");
1207
+ const count = item.children?.length ?? 0;
1208
+ value = count > 0 ? `${status} (${count})` : status;
1209
+ } else if (item.format) {
1210
+ value = item.format(item.value);
1211
+ } else {
1212
+ value = String(item.value ?? "");
1213
+ }
1214
+ return item.displaySuffix ? `${value} ${item.displaySuffix}` : value;
1215
+ }
1216
+
1217
+ function walkSettingsItems(items: SettingsItem[], visitor: (item: SettingsItem) => void) {
1218
+ for (const item of items) {
1219
+ visitor(item);
1220
+ if (item.children) walkSettingsItems(item.children, visitor);
1221
+ }
1222
+ }
1223
+
1224
+ function findSettingsItem(items: SettingsItem[], id: string): SettingsItem | undefined {
1225
+ for (const item of items) {
1226
+ if (item.id === id) return item;
1227
+ const child = item.children ? findSettingsItem(item.children, id) : undefined;
1228
+ if (child) return child;
1229
+ }
1230
+ return undefined;
1231
+ }
1232
+
1233
+ function refreshSettingsDerivedState(items: SettingsItem[]) {
1234
+ const freeflowItem = findSettingsItem(items, "freeflow.enabled");
1235
+ const freeflowInactive = freeflowItem ? effectiveItemValue(freeflowItem) !== true : false;
1236
+ const skillsItem = findSettingsItem(items, "freeflow.skills.enabled");
1237
+ const skillsEnabled = skillsItem ? effectiveItemValue(skillsItem) === true : true;
1238
+ const routerEnabled = findSettingsItem(items, "outputRouter.enabled")?.value === true;
1239
+ const sessionModeItem = findSettingsItem(items, "freeflow.sessionMode");
1240
+ const defaultModeItem = findSettingsItem(items, "freeflow.defaultMode");
1241
+ const routerGroup = findSettingsItem(items, "outputRouter.group");
1242
+
1243
+ if (sessionModeItem) {
1244
+ const defaultMode = String(
1245
+ defaultModeItem ? effectiveItemValue(defaultModeItem) : (sessionModeItem.inheritedValue ?? "workflow"),
1246
+ );
1247
+ const defaultSource = defaultModeItem?.effectiveSource ?? sessionModeItem.inheritedSource ?? "builtin";
1248
+ sessionModeItem.inactive = freeflowInactive || !skillsEnabled;
1249
+ sessionModeItem.displaySuffix = sessionModeDisplaySuffix(
1250
+ String(sessionModeItem.value),
1251
+ defaultMode,
1252
+ defaultSource,
1253
+ freeflowInactive || !skillsEnabled,
1254
+ );
1255
+ sessionModeItem.valueDescriptions = {
1256
+ default: `${defaultMode} from ${defaultSource}`,
1257
+ ...MODE_DESCRIPTIONS,
1258
+ };
1259
+ }
1260
+
1261
+ if (routerGroup) {
1262
+ routerGroup.value = routerEnabled;
1263
+ routerGroup.inactive = freeflowInactive;
1264
+ }
1265
+
1266
+ walkSettingsItems(items, (candidate) => {
1267
+ if (candidate.id === "freeflow.session.reset") {
1268
+ candidate.inactive = false;
1269
+ } else if (candidate.configScope) {
1270
+ const inactive = candidate.id === "freeflow.enabled" ? false : freeflowInactive;
1271
+ const displayInactive = candidate.id === "freeflow.defaultMode" ? freeflowInactive || !skillsEnabled : inactive;
1272
+ candidate.inactive = inactive;
1273
+ candidate.displaySuffix = coreDisplaySuffix(candidate, displayInactive);
1274
+ } else if (candidate.id === "freeflow.sessionMode") {
1275
+ candidate.inactive = freeflowInactive || !skillsEnabled;
1276
+ } else if (candidate.id === "outputRouter.group") {
1277
+ candidate.inactive = freeflowInactive;
1278
+ } else if (candidate.id === "outputRouter.enabled") {
1279
+ candidate.inactive = freeflowInactive;
1280
+ } else if (candidate.id.startsWith("outputRouter.")) {
1281
+ candidate.inactive = freeflowInactive || !routerEnabled;
1282
+ } else {
1283
+ candidate.inactive = freeflowInactive;
1284
+ }
1285
+ });
1286
+ }
1287
+
1288
+ function settingsChoices(item: SettingsItem) {
1289
+ if (item.kind === "boolean") {
1290
+ return [
1291
+ { key: "true", value: true, label: "enabled", description: "Enable this setting." },
1292
+ { key: "false", value: false, label: "disabled", description: "Disable this setting." },
1293
+ ];
1294
+ }
1295
+ if (item.kind !== "enum") return undefined;
1296
+ return (item.values ?? []).map((value) => ({
1297
+ key: value,
1298
+ value,
1299
+ label: item.valueLabels?.[value] ?? value,
1300
+ description: item.valueDescriptions?.[value],
1301
+ }));
1302
+ }
1303
+
1304
+ function resetSessionItemState(items: SettingsItem[]) {
1305
+ walkSettingsItems(items, (item) => {
1306
+ if (item.configScope === "session") {
1307
+ item.value = LOCAL_INHERIT;
1308
+ item.effectiveValue = item.inheritedValue;
1309
+ item.effectiveSource = item.inheritedSource ?? "builtin";
1310
+ } else if (item.id === "freeflow.sessionMode") {
1311
+ item.value = "default";
1312
+ }
1313
+ });
1314
+ }
1315
+
1316
+ function settingsEntries(
1317
+ items: SettingsItem[],
1318
+ rootItems: SettingsItem[],
1319
+ onChange: OpenSettingsOptions["onChange"],
1320
+ ): SettingsEntry[] {
1321
+ return items.map((item) => ({
1322
+ id: item.id,
1323
+ label: item.label,
1324
+ description: item.description,
1325
+ currentValue: () => valueForDisplay(item),
1326
+ inactive: () => item.inactive === true,
1327
+ currentChoiceKey: () => String(item.value),
1328
+ choices: settingsChoices(item),
1329
+ children: item.children ? () => settingsEntries(item.children!, rootItems, onChange) : undefined,
1330
+ edit:
1331
+ !item.children && !["boolean", "enum", "group"].includes(item.kind)
1332
+ ? {
1333
+ initialValue: () => valueForDisplay(item),
1334
+ parse: (text: string) => (item.parse ? item.parse(text) : text),
1335
+ }
1336
+ : undefined,
1337
+ commit:
1338
+ item.kind === "group"
1339
+ ? undefined
1340
+ : async (value: unknown) => {
1341
+ if (valuesEqual(item.value, value)) return { changed: false, reloadRequired: false };
1342
+ const outcome = await onChange(item, value);
1343
+ if (outcome.changed) {
1344
+ updateScopedItemState(item, value);
1345
+ if (item.id === "freeflow.session.reset") {
1346
+ resetSessionItemState(rootItems);
1347
+ } else if (!item.transient) {
1348
+ item.value = value;
1349
+ }
1350
+ refreshSettingsDerivedState(rootItems);
1351
+ }
1352
+ return outcome;
1353
+ },
1354
+ }));
1355
+ }
1356
+
1357
+ async function openSettings(options: OpenSettingsOptions): Promise<SettingsSessionResult> {
1358
+ if (typeof options.ctx?.ui?.custom !== "function") {
1359
+ options.ctx?.ui?.notify?.(
1360
+ `${options.title} requires Pi TUI mode. Use the status command for a compact summary.`,
1361
+ "warning",
1362
+ );
1363
+ return { changed: false, configChanged: false, failed: false };
1364
+ }
1365
+
1366
+ const entries = settingsEntries(options.items, options.items, options.onChange);
1367
+ const initialChoice = options.initialChoice
1368
+ ? entries.find((entry) => entry.id === options.initialChoice?.id)
1369
+ : undefined;
1370
+ let component: PiSettingsComponent | undefined;
1371
+ await options.ctx.ui.custom((tui: any, theme: any, _keybindings: unknown, done: (value?: undefined) => void) => {
1372
+ component = new PiSettingsComponent({
1373
+ title: options.title,
1374
+ entries,
1375
+ initialChoice,
1376
+ theme: theme ?? {},
1377
+ requestRender: () => tui.requestRender(),
1378
+ notify: (message, level) => options.ctx?.ui?.notify?.(message, level),
1379
+ done,
1380
+ });
1381
+ return component;
1382
+ });
1383
+
1384
+ await component?.waitForWrites();
1385
+ return component?.sessionResult() ?? { changed: false, configChanged: false, failed: false };
1386
+ }
1387
+
1388
+ function outputRouterStatusText(rawConfig: Record<string, unknown>): string {
1389
+ const settingsConfig = normalizedSettingsConfig(rawConfig);
1390
+ const actual = normalizeFreeflowConfig(rawConfig).config;
1391
+ const freeflowEnabled = rawConfig.enabled !== false;
1392
+ const router = actual.outputRouter;
1393
+ const configured = settingsConfig;
1394
+ const generatedPaths = router.hints?.generatedPathGlobs?.length ?? 0;
1395
+ const noisyHints = router.hints?.noisyCommandPatterns?.length ?? 0;
1396
+ return [
1397
+ `Output Router: ${freeflowEnabled && router.enabled ? "enabled" : "disabled"}${freeflowEnabled ? "" : " (inactive: Freeflow off)"}`,
1398
+ `script transform: ${freeflowEnabled && configured.scriptTransform.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
1399
+ `observed routing: ${freeflowEnabled && configured.observedRouting.enabled ? "enabled" : "disabled"}${freeflowEnabled && router.enabled ? "" : " (inactive)"}`,
1400
+ `native safety net: ${freeflowEnabled ? router.postToolRouting : "off"}`,
1401
+ `storage: ${router.storagePolicy}`,
1402
+ `generated paths: ${generatedPaths}`,
1403
+ `noisy command hints: ${noisyHints}`,
1404
+ ].join("; ");
1405
+ }
1406
+
1407
+ function freeflowStatusText(state: Awaited<ReturnType<typeof readCapabilityState>>): string {
1408
+ if (!state.configured) {
1409
+ return state.configExists
1410
+ ? `Freeflow: inactive (invalid config: ${state.parseError ?? "unknown parse error"}); run /setup-freeflow or fix .freeflow/config.json`
1411
+ : "Freeflow: inactive (repo not set up); run /setup-freeflow";
1412
+ }
1413
+ const sessionSuffix = (source: ConfigSource) => (source === "session" ? " (session override)" : "");
1414
+ return [
1415
+ `Freeflow: ${state.enabled ? "enabled" : "disabled"}${sessionSuffix(state.configSources.enabled as ConfigSource)}`,
1416
+ `interaction contract: ${state.interactionContract.effective ? "enabled" : "disabled"}${sessionSuffix(
1417
+ state.configSources.interactionContract as ConfigSource,
1418
+ )}`,
1419
+ `skills: ${state.skills.effective ? "enabled" : "disabled (workflow modes inactive)"}${sessionSuffix(
1420
+ state.configSources.skillsEnabled as ConfigSource,
1421
+ )}`,
1422
+ `output router: ${state.outputRouter.enabled ? "enabled" : "disabled"}`,
1423
+ ].join("; ");
1424
+ }
1425
+
1426
+ const NON_TUI_SETTINGS_GUIDANCE =
1427
+ "Freeflow settings require Pi TUI mode. Use /freeflow status to inspect current state; supported non-TUI changes are /freeflow enable, /freeflow disable, and /freeflow mode <conversation|workflow|strict-workflow|reset>.";
1428
+ const NON_TUI_MODE_GUIDANCE =
1429
+ "Freeflow mode selector requires Pi TUI mode. Use /freeflow mode conversation, /freeflow mode workflow, /freeflow mode strict-workflow, or /freeflow mode reset.";
1430
+
1431
+ function nonTuiGuidance(ctx: any, message: string) {
1432
+ if (ctx?.hasUI === false) throw new Error(message);
1433
+ ctx?.ui?.notify?.(message, "warning");
1434
+ return { changed: false, reloaded: false, error: "tui_required" };
1435
+ }
1436
+
1437
+ async function finalizeSettingsSession(
1438
+ session: SettingsSessionResult,
1439
+ ctx: any,
1440
+ afterChange: (changed: boolean) => Promise<void> | void,
1441
+ savedMessage: string,
1442
+ reloadWarning = "Run /reload for Freeflow changes to fully apply.",
1443
+ ) {
1444
+ if (!session.configChanged) return false;
1445
+
1446
+ await afterChange(true);
1447
+ if (session.failed) return false;
1448
+
1449
+ ctx.ui.notify(savedMessage, "info");
1450
+ if (typeof ctx.reload !== "function") {
1451
+ ctx.ui.notify(reloadWarning, "warning");
1452
+ return false;
1453
+ }
1454
+
1455
+ await ctx.reload();
1456
+ return true;
1457
+ }
1458
+
1459
+ async function finalizeSessionSettings(
1460
+ session: SettingsSessionResult,
1461
+ ctx: any,
1462
+ afterChange: (changed: boolean) => Promise<void> | void,
1463
+ ) {
1464
+ if (!session.changed) return false;
1465
+
1466
+ await afterChange(true);
1467
+ if (session.failed) return false;
1468
+ if (!session.configChanged) {
1469
+ ctx.ui.notify("Freeflow session overrides updated.", "info");
1470
+ return false;
1471
+ }
1472
+
1473
+ ctx.ui.notify("Freeflow session overrides updated. Reloading skills and resources...", "info");
1474
+ if (typeof ctx.reload !== "function") {
1475
+ ctx.ui.notify("Run /reload for Freeflow session overrides to fully apply.", "warning");
1476
+ return false;
1477
+ }
1478
+
1479
+ await ctx.reload();
1480
+ return true;
1481
+ }
1482
+
1483
+ function actionIsMutation(action: string): boolean {
1484
+ return action === "" || action === "settings" || ["enable", "on", "true", "disable", "off", "false"].includes(action);
1485
+ }
1486
+
1487
+ async function maybeBlockLayerMutation(action: string, ctx: any, layerName: string) {
1488
+ if (!actionIsMutation(action)) return false;
1489
+ const state = await readCapabilityState(ctx.cwd);
1490
+ if (!state.configured) {
1491
+ ctx.ui.notify(
1492
+ `Freeflow is installed but this repo is not set up. Run /setup-freeflow before configuring ${layerName}.`,
1493
+ "warning",
1494
+ );
1495
+ return true;
1496
+ }
1497
+ if (!state.enabled) {
1498
+ ctx.ui.notify(
1499
+ `Freeflow is disabled for this repo. Use /freeflow enable before configuring ${layerName}.`,
1500
+ "warning",
1501
+ );
1502
+ return true;
1503
+ }
1504
+ return false;
1505
+ }
1506
+
1507
+ export async function handleFreeflowCommand(
1508
+ args: string | undefined,
1509
+ ctx: any,
1510
+ afterChange: (changed: boolean) => Promise<void> | void,
1511
+ pi: any,
1512
+ ) {
1513
+ const input = (args ?? "settings").trim().toLowerCase() || "settings";
1514
+ const [action, ...rest] = input.split(/\s+/);
1515
+ const actionValue = rest.join(" ");
1516
+ const nonTui = ctx?.mode && ctx.mode !== "tui";
1517
+ if (nonTui && action === "mode" && !actionValue) {
1518
+ return nonTuiGuidance(ctx, NON_TUI_MODE_GUIDANCE);
1519
+ }
1520
+ const settingsSelector =
1521
+ action === "settings" &&
1522
+ (!actionValue || ["session", "local", "personal", "repo", "repository", "shared"].includes(actionValue));
1523
+ if (nonTui && settingsSelector) {
1524
+ return nonTuiGuidance(ctx, NON_TUI_SETTINGS_GUIDANCE);
1525
+ }
1526
+
1527
+ const [layers, state, modeState] = await Promise.all([
1528
+ readFreeflowConfigLayers(ctx.cwd),
1529
+ readCapabilityState(ctx.cwd),
1530
+ readModeState(ctx.cwd),
1531
+ ]);
1532
+ const configState = layers.repository;
1533
+ const raw = configState.valid ? configState.parsed : {};
1534
+
1535
+ if (action === "status") {
1536
+ ctx.ui.notify(freeflowStatusText(state), "info");
1537
+ return { changed: false, reloaded: false };
1538
+ }
1539
+
1540
+ if (!configState.valid) {
1541
+ ctx.ui.notify(freeflowStatusText(state), "warning");
1542
+ return { changed: false, reloaded: false, error: "not_configured" };
1543
+ }
1544
+ if (layers.local.exists && !layers.local.valid) {
1545
+ ctx.ui.notify(
1546
+ `.freeflow/local.json is invalid; repair or remove it before changing Freeflow settings. ${layers.local.parseError ?? ""}`.trim(),
1547
+ "warning",
1548
+ );
1549
+ return { changed: false, reloaded: false, error: "invalid_local_config" };
1550
+ }
1551
+
1552
+ if (action === "mode") {
1553
+ if (actionValue) {
1554
+ const result = await handleModeCommand(actionValue, ctx, pi);
1555
+ return { changed: result.changed, reloaded: false, error: result.error };
1556
+ }
1557
+ if (!state.enabled || !state.skills.effective) {
1558
+ const result = await handleModeCommand(undefined, ctx, pi);
1559
+ return { changed: false, reloaded: false, error: result.error };
1560
+ }
1561
+ if (typeof ctx?.ui?.custom !== "function") {
1562
+ const result = await handleModeCommand(undefined, ctx, pi);
1563
+ return { changed: false, reloaded: false, error: result.error };
1564
+ }
1565
+
1566
+ const item = freeflowItems(raw, modeState, {
1567
+ scope: "local",
1568
+ layers,
1569
+ }).find((candidate) => candidate.id === "freeflow.sessionMode")!;
1570
+ const session = await openSettings({
1571
+ title: "Freeflow Mode",
1572
+ items: [item],
1573
+ initialChoice: item,
1574
+ ctx,
1575
+ onChange: async (_item, value) => {
1576
+ const result = await handleModeCommand(String(value), ctx, pi);
1577
+ return { changed: result.changed, reloadRequired: false };
1578
+ },
1579
+ });
1580
+ return { changed: session.changed, reloaded: false, error: session.failed ? "write_failed" : undefined };
1581
+ }
1582
+
1583
+ if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
1584
+ const enabled = ["enable", "on", "true"].includes(action);
1585
+ const item = freeflowItems(raw, modeState, {
1586
+ scope: "repository",
1587
+ layers,
1588
+ }).find((candidate) => candidate.id === "freeflow.enabled")!;
1589
+ await updateConfig(ctx.cwd, item, enabled, "repository");
1590
+ await afterChange(true);
1591
+ ctx.ui.notify(`Freeflow ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
1592
+ if (typeof ctx.reload === "function") {
1593
+ await ctx.reload();
1594
+ return { changed: true, reloaded: true };
1595
+ }
1596
+ ctx.ui.notify("Run /reload for Freeflow changes to fully apply.", "warning");
1597
+ return { changed: true, reloaded: false };
1598
+ }
1599
+
1600
+ if (action && action !== "settings") {
1601
+ ctx.ui.notify(
1602
+ "Usage: /freeflow, /freeflow settings [local|repo], /freeflow status, /freeflow mode [conversation|workflow|strict-workflow|reset], /freeflow enable, or /freeflow disable",
1603
+ "warning",
1604
+ );
1605
+ return { changed: false, reloaded: false, error: "invalid_action" };
1606
+ }
1607
+
1608
+ let settingsScope: ConfigScope = "local";
1609
+ if (actionValue === "session") {
1610
+ settingsScope = "session";
1611
+ } else if (["repo", "repository", "shared"].includes(actionValue)) {
1612
+ settingsScope = "repository";
1613
+ } else if (actionValue && !["local", "personal"].includes(actionValue)) {
1614
+ ctx.ui.notify(
1615
+ "Usage: /freeflow settings, /freeflow settings session, /freeflow settings local, or /freeflow settings repo",
1616
+ "warning",
1617
+ );
1618
+ return { changed: false, reloaded: false, error: "invalid_scope" };
1619
+ }
1620
+
1621
+ const items =
1622
+ settingsScope === "session"
1623
+ ? sessionFreeflowItems(state, modeState)
1624
+ : freeflowItems(raw, modeState, { scope: settingsScope, layers });
1625
+ const session = await openSettings({
1626
+ title:
1627
+ settingsScope === "session"
1628
+ ? "Freeflow Settings · Session overrides"
1629
+ : settingsScope === "local"
1630
+ ? "Freeflow Settings · Personal overrides"
1631
+ : "Freeflow Repository Settings · modifies .freeflow/config.json",
1632
+ items,
1633
+ ctx,
1634
+ onChange: async (item, value) => {
1635
+ if (item.id === "freeflow.sessionMode") {
1636
+ const result = await handleModeCommand(String(value), ctx, pi);
1637
+ return { changed: result.changed, reloadRequired: false };
1638
+ }
1639
+ if (item.id === "freeflow.session.reset") {
1640
+ const result = await resetSessionOverrides(ctx, pi);
1641
+ return { changed: result.changed, reloadRequired: result.reloadRequired };
1642
+ }
1643
+ if (item.configScope === "session") {
1644
+ const keyById = {
1645
+ "freeflow.enabled": "enabled",
1646
+ "freeflow.interactionContract": "interactionContract",
1647
+ "freeflow.skills.enabled": "skillsEnabled",
1648
+ } as const;
1649
+ const key = keyById[item.id as keyof typeof keyById];
1650
+ const override = value === LOCAL_INHERIT ? null : value === "true";
1651
+ const result = await setSessionCoreOverride(key, override, ctx, pi);
1652
+ return { changed: result.changed, reloadRequired: result.reloadRequired === true };
1653
+ }
1654
+ await updateConfig(ctx.cwd, item, value, item.configScope ?? "repository");
1655
+ return { changed: true, reloadRequired: true };
1656
+ },
1657
+ });
1658
+
1659
+ if (settingsScope === "session") {
1660
+ const reloaded = await finalizeSessionSettings(session, ctx, afterChange);
1661
+ return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1662
+ }
1663
+
1664
+ const savedTarget = settingsScope === "local" ? "personal overrides" : "repository settings";
1665
+ const reloaded = await finalizeSettingsSession(
1666
+ session,
1667
+ ctx,
1668
+ afterChange,
1669
+ `Freeflow ${savedTarget} saved. Reloading Freeflow runtime...`,
1670
+ );
1671
+ return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1672
+ }
1673
+
1674
+ export async function handleOutputRouterCommand(
1675
+ args: string | undefined,
1676
+ ctx: any,
1677
+ afterChange: (changed: boolean) => Promise<void> | void,
1678
+ ) {
1679
+ const action = (args ?? "").trim().toLowerCase();
1680
+ const raw = await readFreeflowConfig(ctx.cwd);
1681
+
1682
+ if (await maybeBlockLayerMutation(action, ctx, "Output Router")) {
1683
+ return { changed: false, reloaded: false, error: "freeflow_inactive" };
1684
+ }
1685
+
1686
+ if (action === "status") {
1687
+ const state = await readCapabilityState(ctx.cwd);
1688
+ const prefix = !state.configured || !state.enabled ? `${freeflowStatusText(state)}; ` : "";
1689
+ ctx.ui.notify(`${prefix}${outputRouterStatusText(raw)}`, !state.configured || !state.enabled ? "warning" : "info");
1690
+ return { changed: false, reloaded: false };
1691
+ }
1692
+
1693
+ if (["enable", "on", "true", "disable", "off", "false"].includes(action)) {
1694
+ const enabled = ["enable", "on", "true"].includes(action);
1695
+ const item = outputRouterItems(raw).find((candidate) => candidate.id === "outputRouter.enabled")!;
1696
+ await updateConfig(ctx.cwd, item, enabled);
1697
+ await afterChange(true);
1698
+ ctx.ui.notify(`Output Router ${enabled ? "enabled" : "disabled"}. Reloading Freeflow runtime...`, "info");
1699
+ if (typeof ctx.reload === "function") {
1700
+ await ctx.reload();
1701
+ return { changed: true, reloaded: true };
1702
+ }
1703
+ ctx.ui.notify("Run /reload for Output Router changes to fully apply.", "warning");
1704
+ return { changed: true, reloaded: false };
1705
+ }
1706
+
1707
+ if (action && action !== "settings") {
1708
+ ctx.ui.notify("Usage: /output-router, /output-router settings, or /output-router status", "warning");
1709
+ return { changed: false, reloaded: false, error: "invalid_action" };
1710
+ }
1711
+
1712
+ const session = await openSettings({
1713
+ title: "Output Router Settings",
1714
+ items: outputRouterItems(raw),
1715
+ ctx,
1716
+ onChange: async (item, value) => {
1717
+ await updateConfig(ctx.cwd, item, value);
1718
+ return { changed: true, reloadRequired: true };
1719
+ },
1720
+ });
1721
+
1722
+ const reloaded = await finalizeSettingsSession(
1723
+ session,
1724
+ ctx,
1725
+ afterChange,
1726
+ "Output Router settings saved. Reloading Freeflow runtime...",
1727
+ "Run /reload for Output Router changes to fully apply.",
1728
+ );
1729
+ return { changed: session.changed, reloaded, error: session.failed ? "write_failed" : undefined };
1730
+ }