@gtrabanco/pi-agentic-workflow 0.1.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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.es.md +154 -0
  3. package/README.md +148 -0
  4. package/dist/config/defaults.d.ts +8 -0
  5. package/dist/config/defaults.js +11 -0
  6. package/dist/config/load.d.ts +19 -0
  7. package/dist/config/load.js +76 -0
  8. package/dist/config/merge.d.ts +4 -0
  9. package/dist/config/merge.js +43 -0
  10. package/dist/config/schema.d.ts +21 -0
  11. package/dist/config/schema.js +144 -0
  12. package/dist/config/types.d.ts +53 -0
  13. package/dist/config/types.js +12 -0
  14. package/dist/extension/factory.d.ts +47 -0
  15. package/dist/extension/factory.js +49 -0
  16. package/dist/extension/index.d.ts +4 -0
  17. package/dist/extension/index.js +84 -0
  18. package/dist/routing/catalogue.d.ts +35 -0
  19. package/dist/routing/catalogue.js +79 -0
  20. package/dist/routing/dispatch.d.ts +34 -0
  21. package/dist/routing/dispatch.js +191 -0
  22. package/dist/routing/state.d.ts +13 -0
  23. package/dist/routing/state.js +65 -0
  24. package/dist/routing/types.d.ts +90 -0
  25. package/dist/routing/types.js +3 -0
  26. package/dist/settings/console.d.ts +55 -0
  27. package/dist/settings/console.js +266 -0
  28. package/dist/settings/store.d.ts +3 -0
  29. package/dist/settings/store.js +24 -0
  30. package/dist/settings/view.d.ts +11 -0
  31. package/dist/settings/view.js +46 -0
  32. package/package.json +59 -0
  33. package/skills/audit-docs/SKILL.md +171 -0
  34. package/skills/audit-pr/SKILL.md +201 -0
  35. package/skills/audit-pr/references/01_MERGE_GATES.md +24 -0
  36. package/skills/audit-pr/references/02_CLOSURE_AND_SCOPE_GATES.md +83 -0
  37. package/skills/audit-pr/references/03_AUDIT_PROCESS.md +69 -0
  38. package/skills/audit-pr/references/04_VERDICT.md +69 -0
  39. package/skills/audit-pr/references/05_ROUTING_AND_GUARDRAILS.md +58 -0
  40. package/skills/audit-pr/references/PORTABILITY.md +17 -0
  41. package/skills/design-feature/SKILL.md +162 -0
  42. package/skills/design-feature/references/INTERVIEW.md +52 -0
  43. package/skills/design-feature/references/PORTABILITY.md +15 -0
  44. package/skills/design-feature/references/UPSERT_EXAMPLE.md +8 -0
  45. package/skills/design-feature/references/WRITE_AND_UPSERT.md +92 -0
  46. package/skills/discover-repository-state/SKILL.md +83 -0
  47. package/skills/execute-phase/SKILL.md +115 -0
  48. package/skills/execute-phase/references/BATCH_AND_PORTABILITY.md +54 -0
  49. package/skills/execute-phase/references/CLOSEOUT.md +74 -0
  50. package/skills/execute-phase/references/DESCOPE.md +35 -0
  51. package/skills/execute-phase/references/EXECUTION_CONTRACT.md +147 -0
  52. package/skills/execute-phase/references/FOLDING.md +73 -0
  53. package/skills/execute-phase/references/FORGE_BODY.md +24 -0
  54. package/skills/execute-phase/references/HANDOFF.md +21 -0
  55. package/skills/execute-phase/references/OPPORTUNISTIC_FINDING.md +45 -0
  56. package/skills/execute-phase/references/PREFLIGHT.md +152 -0
  57. package/skills/execute-phase/references/UNIT_LOOP.md +88 -0
  58. package/skills/execute-phase/references/WORKFLOWS_FEATURE.md +37 -0
  59. package/skills/execute-phase/references/WORKFLOWS_FIX.md +43 -0
  60. package/skills/execute-phase/references/WORKFLOWS_LEGACY.md +26 -0
  61. package/skills/execute-phase/references/WORKFLOWS_SMALL_PHASED.md +38 -0
  62. package/skills/fold-findings/SKILL.md +141 -0
  63. package/skills/fold-findings/references/FOLD_POLICY.md +43 -0
  64. package/skills/fold-findings/references/FOLD_PROCESS.md +41 -0
  65. package/skills/generate-docs/SKILL.md +145 -0
  66. package/skills/generate-docs/references/ADAPTERS.md +15 -0
  67. package/skills/generate-docs/references/ADAPTER_DISCOVERY.md +33 -0
  68. package/skills/generate-docs/references/GENERATION_PROCESS.md +90 -0
  69. package/skills/init-workspace/SKILL.md +152 -0
  70. package/skills/init-workspace/references/BOOTSTRAP_DISCOVERY.md +70 -0
  71. package/skills/init-workspace/references/BOOTSTRAP_WRITE.md +58 -0
  72. package/skills/init-workspace/references/PORTABILITY.md +16 -0
  73. package/skills/init-workspace/references/UPGRADE.md +83 -0
  74. package/skills/log-session/SKILL.md +159 -0
  75. package/skills/loop-review-fold/SKILL.md +168 -0
  76. package/skills/orchestration-envelope/SKILL.md +81 -0
  77. package/skills/orchestration-envelope/references/TURN_CONTRACT.md +21 -0
  78. package/skills/phase-contract/SKILL.md +82 -0
  79. package/skills/plan-feature/SKILL.md +179 -0
  80. package/skills/plan-feature/references/ROUTING.md +83 -0
  81. package/skills/plan-feature-from-issue/SKILL.md +140 -0
  82. package/skills/plan-feature-scaffold/SKILL.md +93 -0
  83. package/skills/plan-feature-scaffold/references/SCAFFOLD_PROCESS.md +76 -0
  84. package/skills/plan-fix/SKILL.md +115 -0
  85. package/skills/plan-fix/references/PLANNING_PROCESS.md +118 -0
  86. package/skills/plan-fix/references/SPEC_CONTRACT.md +22 -0
  87. package/skills/planning-preflight/SKILL.md +130 -0
  88. package/skills/product-audit/SKILL.md +225 -0
  89. package/skills/product-audit/references/AUDIT_DIMENSIONS.md +24 -0
  90. package/skills/product-audit/references/AUDIT_PROCESS.md +85 -0
  91. package/skills/resolve-repository-state/SKILL.md +79 -0
  92. package/skills/review-a11y/SKILL.md +60 -0
  93. package/skills/review-brand/SKILL.md +65 -0
  94. package/skills/review-change/SKILL.md +156 -0
  95. package/skills/review-change/references/ADVERSARIAL_RECOMMENDATION.md +31 -0
  96. package/skills/review-change/references/ADVERSARIAL_SETUP.md +86 -0
  97. package/skills/review-change/references/ADVERSARIAL_SYNTHESIS.md +53 -0
  98. package/skills/review-change/references/OUTPUT_AND_GUARDRAILS.md +85 -0
  99. package/skills/review-change/references/PERSIST_AND_DECIDE.md +151 -0
  100. package/skills/review-change/references/PORTABILITY.md +74 -0
  101. package/skills/review-change/references/REVIEW_PROCESS.md +86 -0
  102. package/skills/review-code/SKILL.md +69 -0
  103. package/skills/review-debt/SKILL.md +72 -0
  104. package/skills/review-design/SKILL.md +60 -0
  105. package/skills/review-implementation/SKILL.md +108 -0
  106. package/skills/review-implementation/references/CLASSIFY.md +96 -0
  107. package/skills/review-implementation/references/FIND.md +35 -0
  108. package/skills/review-perf/SKILL.md +79 -0
  109. package/skills/review-security/SKILL.md +65 -0
  110. package/skills/review-seo/SKILL.md +65 -0
  111. package/skills/review-verify/SKILL.md +66 -0
  112. package/skills/ship-roadmap/SKILL.md +120 -0
  113. package/skills/ship-roadmap/references/ADVANCE.md +90 -0
  114. package/skills/ship-roadmap/references/AUDIT_AND_MERGE.md +64 -0
  115. package/skills/ship-roadmap/references/CLOSEOUT_AND_LOG.md +26 -0
  116. package/skills/ship-roadmap/references/FOUNDING.md +101 -0
  117. package/skills/ship-roadmap/references/GUARDRAILS.md +56 -0
  118. package/skills/ship-roadmap/references/MODEL_ROUTING.md +14 -0
  119. package/skills/ship-roadmap/references/PORTABILITY.md +34 -0
  120. package/skills/ship-roadmap/references/RECOVERY_AND_SELECTION.md +93 -0
  121. package/skills/ship-roadmap/references/STOP_CONDITIONS.md +9 -0
  122. package/skills/ship-roadmap/references/TERMINAL_REPORT.md +34 -0
  123. package/skills/triage-issue/SKILL.md +148 -0
  124. package/skills/triage-issue/references/AUDIT_FINDINGS.md +35 -0
  125. package/skills/triage-issue/references/FOLD_LEDGER.md +32 -0
  126. package/skills/triage-issue/references/ISSUE_PROCESS.md +101 -0
  127. package/skills/triage-issue/references/LABELS.md +83 -0
  128. package/skills/triage-issue/references/REVIEW_FINDING_PROCESS.md +41 -0
  129. package/skills/verification-contract/SKILL.md +77 -0
  130. package/skills/workflow-status/SKILL.md +143 -0
  131. package/skills/workflow-status/references/CRASH_RECOVERY.md +66 -0
  132. package/skills/workflow-status/references/ENVELOPE_CORE.md +107 -0
  133. package/skills/workflow-status/references/ENVELOPE_FIELDS.md +77 -0
  134. package/skills/workflow-status/references/GUARDRAILS.md +17 -0
  135. package/skills/workflow-status/references/PORTABILITY.md +14 -0
  136. package/skills/workflow-status/references/SENSOR_CORE.md +95 -0
  137. package/skills/workflow-status/references/SENSOR_SIGNALS.md +128 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gabriel Trabanco <gtrabanco@users.noreply.github.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.es.md ADDED
@@ -0,0 +1,154 @@
1
+ # @gtrabanco/pi-agentic-workflow
2
+
3
+ > 🇬🇧 [English version](README.md)
4
+
5
+ Una sola instalación del método
6
+ [agentic-workflow](https://github.com/gtrabanco/agentic-workflow) en
7
+ [Pi](https://github.com/badlogic/pi-mono): las skills canónicas, un comando por
8
+ cada una y, si quieres, modelo distinto por comando que devuelve tu sesión
9
+ después.
10
+
11
+ - **Skills canónicas, sin cambios.** El paquete trae los mismos archivos
12
+ `SKILL.md` de este repositorio, byte a byte — sin bifurcación para Pi que se
13
+ desincronice.
14
+ - **Comandos claros.** Escribes `/plan-feature --next`, no
15
+ `/skill:plan-feature --next`.
16
+ - **Enrutamiento que puedes olvidar.** Por defecto no se configura nada: cada
17
+ comando corre en el modelo que ya tenías.
18
+
19
+ ## Instalación
20
+
21
+ ```sh
22
+ pi install npm:@gtrabanco/pi-agentic-workflow
23
+ ```
24
+
25
+ Reinicia Pi. `/agentic-workflow-settings` y los comandos de workflow quedan
26
+ disponibles en cualquier proyecto. Si antes copiaste las skills a mano a
27
+ `~/.pi/agent/skills`, borra esa copia: el paquete ya las aporta, y dos copias
28
+ significan dos versiones del mismo método.
29
+
30
+ ## Comandos
31
+
32
+ Cada skill incluida cuyo frontmatter dice `user-invocable: true` obtiene un
33
+ comando con el mismo nombre. La lista se lee de las skills al arrancar, así que
34
+ añadir una skill añade su comando — no existe una tabla de alias que mantener.
35
+ Las skills internas que componen una skill pública (los pasos de revisión, el
36
+ preflight de planificación, el contrato del envelope) viajan en el paquete pero
37
+ no reciben comando propio — los componen los de arriba:
38
+
39
+ | Comando | Para qué lo usas |
40
+ | --- | --- |
41
+ | `/audit-docs` | Comprobar que docs, roadmap, código y el índice de fixes coinciden. |
42
+ | `/audit-pr` | La puerta de merge: ¿está este PR listo? |
43
+ | `/design-feature` | Convertir una idea suelta en un SPEC diseñado. |
44
+ | `/discover-repository-state` | Congelar hechos verificados del repositorio. |
45
+ | `/execute-phase` | Implementar las fases restantes de una unidad planificada. |
46
+ | `/fold-findings` | Reparar los hallazgos «fix-now» persistidos. |
47
+ | `/generate-docs` | Generar guías incrementales basadas en el diff. |
48
+ | `/init-workspace` | Adaptar el andamiaje del workflow a un repositorio. |
49
+ | `/log-session` | Añadir una entrada de sesión estructurada a `docs/LOGS.md`. |
50
+ | `/loop-review-fold` | Revisar una unidad y plegar lo que encontró. |
51
+ | `/plan-feature` | Dirigir el trabajo diseñado a planificación y roadmap. |
52
+ | `/plan-fix` | Redactar un SPEC de fix por fases desde uno o varios issues. |
53
+ | `/product-audit` | Auditar la superficie del producto, no solo el diff. |
54
+ | `/resolve-repository-state` | Resolver una contradicción en hechos congelados. |
55
+ | `/review-change` | Revisar un cambio con los ejes que apliquen. |
56
+ | `/ship-roadmap` | Encontrar o continuar un roadmap, una etapa por ejecución. |
57
+ | `/triage-issue` | Verificar un issue o hallazgo contra el código actual. |
58
+ | `/workflow-status` | Estado de solo lectura del repositorio y el roadmap. |
59
+
60
+ Los argumentos se reenvían tal cual: `/execute-phase P3 --fix` llega a la skill
61
+ como `P3 --fix`.
62
+
63
+ ## Enrutamiento por modelo
64
+
65
+ Dos archivos JSON, ambos opcionales:
66
+
67
+ | Alcance | Ruta | Se lee cuando |
68
+ | --- | --- | --- |
69
+ | Global | `~/.pi/agent/pi-agentic-workflow.json` | siempre |
70
+ | Proyecto | `<repo>/.pi/pi-agentic-workflow.json` | el proyecto es de confianza |
71
+
72
+ ```json
73
+ {
74
+ "default": { "model": "anthropic/claude-opus-4-5", "thinking": "high" },
75
+ "commands": {
76
+ "plan-feature": { "model": "anthropic/claude-sonnet-4-5", "thinking": "medium" },
77
+ "review-change": { "thinking": "max" }
78
+ },
79
+ "onUnavailableRoute": "stop"
80
+ }
81
+ ```
82
+
83
+ Cada valor se toma del primer sitio que lo declara: **comando en proyecto →
84
+ comando global → ruta por defecto resuelta → defecto del paquete**. `review-change`
85
+ de arriba usa el modelo del default con thinking `max`; todo lo demás corre con lo
86
+ que ya tuviera la sesión, porque la ruta por defecto del paquete es
87
+ `{"model": "inherit", "thinking": "inherit"}`.
88
+
89
+ - `model` debe ser `provider/modelId` — la referencia exacta que muestra
90
+ `/model` — o `"inherit"`.
91
+ - `thinking` es uno de `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`,
92
+ o `"inherit"`.
93
+ - Claves desconocidas, `null` y referencias mal formadas se **rechazan**, no se
94
+ ignoran: un error tipográfico que no hace nada en silencio es el bug que nunca
95
+ encuentras.
96
+
97
+ El primer comando de workflow tras la instalación avisa una sola vez que el
98
+ enrutamiento es configurable, y no vuelve a insistir. Ese aviso se guarda en
99
+ `~/.pi/agent/pi-agentic-workflow-state.json`, no en tu configuración.
100
+
101
+ ## Cuando el modelo configurado no está disponible
102
+
103
+ Por defecto el comando **se niega a arrancar** y te dice por qué: el modelo no
104
+ está en el registro, no tiene credenciales, o no se pudo seleccionar. No se envía
105
+ nada, así que nada corre en un modelo que no elegiste. Para usar el modelo actual
106
+ aun así:
107
+
108
+ ```json
109
+ { "onUnavailableRoute": "inherit" }
110
+ ```
111
+
112
+ ## Tu sesión vuelve
113
+
114
+ El enrutamiento dura un comando. Cuando el turno termina, la sesión vuelve a
115
+ como estaba — el modelo *y* el nivel de thinking, porque seleccionar un modelo
116
+ puede mover el nivel. Si cambias el modelo tú mismo a mitad de turno, con
117
+ `/model` por ejemplo, no se restaura nada: tu elección gana, y el comando lo
118
+ dice. Si mueves solo el nivel de thinking, lo conservas mientras el modelo
119
+ vuelve.
120
+
121
+ ## Consola de configuración
122
+
123
+ ```
124
+ /agentic-workflow-settings
125
+ ```
126
+
127
+ Muestra en qué corre cada comando ahora mismo, y qué archivo se niega a parsearse,
128
+ y luego deja editar **un archivo a la vez** y guardar en alcance global o de
129
+ proyecto. No guarda encima de un archivo que no sabe leer, y no toca el
130
+ archivo de proyecto mientras el proyecto no sea de confianza.
131
+
132
+ ## Diagnóstico
133
+
134
+ | Ves | Significa |
135
+ | --- | --- |
136
+ | `refused: invalid configuration` | Un archivo de configuración fue rechazado. El mismo mensaje nombra el campo, p. ej. `$.commands.plan-feature.model`. Ejecuta `/agentic-workflow-settings` para ver el archivo o arregla el JSON. |
137
+ | `stopped: the configured model` … `is not in the model registry` | La referencia es incorrecta o el proveedor no está configurado. Usa `/model` para ver el `provider/modelId` exacto. |
138
+ | `has no configured credentials` | El modelo existe pero aún no puedes usarlo. Autentica, o pon `onUnavailableRoute` en `inherit`. |
139
+ | `could not be selected` | Pi rechazó el cambio. El comando se detiene con el motivo — salvo que `onUnavailableRoute` sea `inherit`, en cuyo caso avisa y se ejecuta con tu modelo actual. |
140
+ | `refused: the agent is busy` | Hay un turno en ejecución. Espera a que termine. |
141
+ | `is still routed` | El comando enrutado anterior no ha terminado. |
142
+ | `leaving the model you chose in place` | Cambiaste el modelo durante el turno enrutado, así que no se restauró nada: tu elección ganó. |
143
+ | `these configured routes match no command` | Una clave de `commands` no nombra nada. Corrige la escritura o elimina la entrada. |
144
+
145
+ ## Notas
146
+
147
+ - Probado con Pi 0.84.3 (`pi install`, skills de paquete, `sendUserMessage` con
148
+ expansión de plantillas).
149
+ - El paquete declara Pi como peer dependency; no incluye ninguna copia de Pi.
150
+ - Las skills pueden indicar al modelo que ejecute comandos. Revísalas como con
151
+ cualquier paquete de terceros.
152
+
153
+ MIT · [Repositorio](https://github.com/gtrabanco/agentic-workflow) ·
154
+ [`docs/features/27-pi-agentic-workflow/`](../../docs/features/27-pi-agentic-workflow/SPEC.md)
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @gtrabanco/pi-agentic-workflow
2
+
3
+ > 🇪🇸 [Versión en español](README.es.md)
4
+
5
+ One install of the [agentic-workflow](https://github.com/gtrabanco/agentic-workflow)
6
+ method into [Pi](https://github.com/badlogic/pi-mono): the canonical skills, a
7
+ friendly slash command for each of them, and optional per-command model routing
8
+ that gives your session back afterwards.
9
+
10
+ - **Canonical skills, unchanged.** The package ships the same `SKILL.md` files as
11
+ this repository, byte for byte — no Pi-specific fork to drift.
12
+ - **Friendly commands.** Type `/plan-feature --next`, not
13
+ `/skill:plan-feature --next`.
14
+ - **Routing you can forget about.** Nothing is configured by default: every
15
+ command runs on the model you already have.
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ pi install npm:@gtrabanco/pi-agentic-workflow
21
+ ```
22
+
23
+ Restart Pi. `/agentic-workflow-settings` and the workflow commands are then
24
+ available in any project. If you previously copied the skills into
25
+ `~/.pi/agent/skills` by hand, delete that copy — the package provides them, and
26
+ two copies means two versions of the same method.
27
+
28
+ ## Commands
29
+
30
+ Every bundled skill whose frontmatter says `user-invocable: true` gets a command
31
+ with the same name. The list is read from the skills at startup, so adding a
32
+ skill adds its command — there is no alias table to keep in sync. Internal
33
+ skills that a user-facing skill composes (the review passes, the planning
34
+ preflight, the envelope contract) ship inside the package but get no command of
35
+ their own — they are composed by the ones above:
36
+
37
+ | Command | Use it for |
38
+ | --- | --- |
39
+ | `/audit-docs` | Check that docs, roadmap, code and the fix index agree. |
40
+ | `/audit-pr` | The merge gate: is this PR ready? |
41
+ | `/design-feature` | Turn a raw idea into a designed SPEC. |
42
+ | `/discover-repository-state` | Freeze verified repository facts. |
43
+ | `/execute-phase` | Implement the remaining phases of a planned unit. |
44
+ | `/fold-findings` | Repair persisted fix-now findings. |
45
+ | `/generate-docs` | Generate incremental, diff-driven developer guides. |
46
+ | `/init-workspace` | Adapt the workflow scaffold to a repository. |
47
+ | `/log-session` | Append a structured session entry to `docs/LOGS.md`. |
48
+ | `/loop-review-fold` | Review a unit, then fold what it found. |
49
+ | `/plan-feature` | Route designed work into planning and the roadmap. |
50
+ | `/plan-fix` | Draft a phased fix SPEC from one or more issues. |
51
+ | `/product-audit` | Audit the product surface, not just the diff. |
52
+ | `/resolve-repository-state` | Resolve a contradiction in frozen facts. |
53
+ | `/review-change` | Review a change with the applicable axes. |
54
+ | `/ship-roadmap` | Find or continue a roadmap, one stage per run. |
55
+ | `/triage-issue` | Verify an issue or finding against current code. |
56
+ | `/workflow-status` | Read-only state of the repository and roadmap. |
57
+
58
+ Arguments are forwarded verbatim: `/execute-phase P3 --fix` reaches the skill as
59
+ `P3 --fix`.
60
+
61
+ ## Model routing
62
+
63
+ Two JSON files, both optional:
64
+
65
+ | Scope | Path | Read when |
66
+ | --- | --- | --- |
67
+ | Global | `~/.pi/agent/pi-agentic-workflow.json` | always |
68
+ | Project | `<repo>/.pi/pi-agentic-workflow.json` | the project is trusted |
69
+
70
+ ```json
71
+ {
72
+ "default": { "model": "anthropic/claude-opus-4-5", "thinking": "high" },
73
+ "commands": {
74
+ "plan-feature": { "model": "anthropic/claude-sonnet-4-5", "thinking": "medium" },
75
+ "review-change": { "thinking": "max" }
76
+ },
77
+ "onUnavailableRoute": "stop"
78
+ }
79
+ ```
80
+
81
+ A value is taken from the first place that declares it: **project command →
82
+ global command → resolved default route → shipped default**. `review-change`
83
+ above runs on the default model with `max` thinking; anything else runs on
84
+ whatever the session already had, because the shipped default route is
85
+ `{"model": "inherit", "thinking": "inherit"}`.
86
+
87
+ - `model` must be `provider/modelId` — the exact reference `/model` shows — or
88
+ `"inherit"`.
89
+ - `thinking` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`,
90
+ or `"inherit"`.
91
+ - Unknown keys, `null`s and malformed references are **rejected**, not ignored: a
92
+ typo that silently did nothing is the bug you would never find.
93
+
94
+ The first workflow command you run after install says once that routing is
95
+ configurable, then never again. That acknowledgement is stored in
96
+ `~/.pi/agent/pi-agentic-workflow-state.json`, not in your config.
97
+
98
+ ## When a configured model is unavailable
99
+
100
+ Default: the command **refuses to start** and tells you why — the model is not in
101
+ the registry, has no credentials, or could not be selected. Nothing is sent, so
102
+ nothing runs on a model you did not pick. To run anyway on the current model, set:
103
+
104
+ ```json
105
+ { "onUnavailableRoute": "inherit" }
106
+ ```
107
+
108
+ ## Your session comes back
109
+
110
+ Routing lasts one command. When the turn settles, the session is put back the way
111
+ you had it — the model *and* the thinking level, because selecting a model can
112
+ move the level. If you change the model yourself mid-turn, with `/model`, say,
113
+ nothing is restored: your choice wins, and the command says so. Change only the
114
+ thinking level and you keep it while the model still comes back.
115
+
116
+ ## Settings console
117
+
118
+ ```
119
+ /agentic-workflow-settings
120
+ ```
121
+
122
+ Shows what each command runs on right now, and which file is refusing to parse,
123
+ then lets you edit **one file at a time** and save to global or project scope. It will not save over a file it cannot parse, and it will not
124
+ touch the project file while the project is untrusted.
125
+
126
+ ## Troubleshooting
127
+
128
+ | You see | Means |
129
+ | --- | --- |
130
+ | `refused: invalid configuration` | A config file was rejected. The same message names the field, e.g. `$.commands.plan-feature.model`. Run `/agentic-workflow-settings` to see the file, or fix the JSON. |
131
+ | `stopped: the configured model` … `is not in the model registry` | The reference is wrong or the provider is not configured. Use `/model` to see the exact `provider/modelId`. |
132
+ | `has no configured credentials` | The model exists but you cannot use it yet. Authenticate, or set `onUnavailableRoute` to `inherit`. |
133
+ | `could not be selected` | Pi refused the switch. The command stops with the reason — unless `onUnavailableRoute` is `inherit`, in which case it warns and runs on your current model. |
134
+ | `refused: the agent is busy` | A turn is running. Wait for it to settle. |
135
+ | `is still routed` | The previous routed command has not settled yet. |
136
+ | `leaving the model you chose in place` | You changed the model during a routed turn, so nothing was restored — your choice won. |
137
+ | `these configured routes match no command` | A `commands` key names nothing. Fix the spelling or delete the entry. |
138
+
139
+ ## Notes
140
+
141
+ - Verified against Pi 0.84.3 (`pi install`, package skills, `sendUserMessage` with
142
+ prompt template expansion).
143
+ - The package declares Pi as a peer dependency; it bundles no copy of Pi.
144
+ - Skills can instruct the model to run commands. Review them as you would any
145
+ third-party package.
146
+
147
+ MIT · [Repository](https://github.com/gtrabanco/agentic-workflow) ·
148
+ [`docs/features/27-pi-agentic-workflow/`](../../docs/features/27-pi-agentic-workflow/SPEC.md)
@@ -0,0 +1,8 @@
1
+ import type { EffectiveConfig, Route } from "./types.js";
2
+ /**
3
+ * The in-package default (SPEC S6, D-P6): a fresh install with no config file
4
+ * anywhere routes every command on the session model, and the fail-closed
5
+ * fallback policy starts at `stop`.
6
+ */
7
+ export declare const DEFAULT_ROUTE: Route;
8
+ export declare const DEFAULT_CONFIG: EffectiveConfig;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The in-package default (SPEC S6, D-P6): a fresh install with no config file
3
+ * anywhere routes every command on the session model, and the fail-closed
4
+ * fallback policy starts at `stop`.
5
+ */
6
+ export const DEFAULT_ROUTE = { model: "inherit", thinking: "inherit" };
7
+ export const DEFAULT_CONFIG = {
8
+ default: { ...DEFAULT_ROUTE },
9
+ commands: {},
10
+ onUnavailableRoute: "stop",
11
+ };
@@ -0,0 +1,19 @@
1
+ import type { ConfigPaths, ConfigProblem, EffectiveConfig } from "./types.js";
2
+ export declare function configFilePaths(agentDir: string, cwd: string): ConfigPaths;
3
+ export interface LoadConfigInput {
4
+ agentDir: string;
5
+ cwd: string;
6
+ projectTrusted: boolean;
7
+ /** Returns the file text, or null when the path does not exist. A reader that
8
+ * cannot honour an existing path throws, and the throw becomes a problem:
9
+ * "unreadable" must not quietly mean "unconfigured" (F7). */
10
+ readFile?: (path: string) => string | null;
11
+ }
12
+ export interface LoadedConfig {
13
+ /** False when any present file failed to parse or validate → refuse dispatch. */
14
+ ok: boolean;
15
+ /** The merged configuration; the shipped default whenever `ok` is false. */
16
+ config: EffectiveConfig;
17
+ problems: ConfigProblem[];
18
+ }
19
+ export declare function loadConfig({ agentDir, cwd, projectTrusted, readFile }: LoadConfigInput): LoadedConfig;
@@ -0,0 +1,76 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { DEFAULT_CONFIG } from "./defaults.js";
4
+ import { mergeConfigs } from "./merge.js";
5
+ import { parseConfigFile } from "./schema.js";
6
+ /**
7
+ * Load the two dedicated JSON files into one effective configuration
8
+ * (SPEC S5, S11; D-E5, D-P6).
9
+ *
10
+ * The three rules this module owns, and nothing else:
11
+ * - a MISSING file resolves to the shipped default (`inherit`);
12
+ * - a PRESENT-but-invalid file is a problem — never a silent `inherit`, so an
13
+ * operator can't believe a strong model ran when it did not;
14
+ * - a file that EXISTS but cannot be read is the same kind of problem: it is
15
+ * present, so its routing intentions are unknown, and unknown is refused
16
+ * rather than defaulted (F7);
17
+ * - the project file is not even read while the project is untrusted, because
18
+ * a cloned repository must not be able to steer routing (S11).
19
+ *
20
+ * Reading is injected so tests drive every branch from memory; the caller
21
+ * supplies the directories (Pi's `getAgentDir()` and `ctx.cwd`), never this
22
+ * module.
23
+ */
24
+ const CONFIG_FILE_NAME = "pi-agentic-workflow.json";
25
+ const PROJECT_CONFIG_DIR_NAME = ".pi";
26
+ export function configFilePaths(agentDir, cwd) {
27
+ return {
28
+ global: join(agentDir, CONFIG_FILE_NAME),
29
+ project: join(cwd, PROJECT_CONFIG_DIR_NAME, CONFIG_FILE_NAME),
30
+ };
31
+ }
32
+ const NOT_THERE = new Set(["ENOENT", "ENOTDIR"]);
33
+ /** Absent is `null`; present-but-unreadable throws so the caller records it. */
34
+ const readIfExists = (path) => {
35
+ try {
36
+ return readFileSync(path, "utf8");
37
+ }
38
+ catch (error) {
39
+ if (NOT_THERE.has(error.code ?? ""))
40
+ return null;
41
+ throw error;
42
+ }
43
+ };
44
+ function loadScope(scope, path, readFile, problems) {
45
+ let text;
46
+ try {
47
+ text = readFile(path);
48
+ }
49
+ catch (error) {
50
+ const code = error.code ?? "read error";
51
+ problems.push({ scope, path: "$", message: `the file could not be read (${code})` });
52
+ return {};
53
+ }
54
+ if (text === null)
55
+ return {};
56
+ const result = parseConfigFile(text);
57
+ if (!result.ok) {
58
+ for (const issue of result.issues)
59
+ problems.push({ scope, path: issue.path, message: issue.message });
60
+ return {};
61
+ }
62
+ return result.config;
63
+ }
64
+ export function loadConfig({ agentDir, cwd, projectTrusted, readFile = readIfExists }) {
65
+ const paths = configFilePaths(agentDir, cwd);
66
+ const problems = [];
67
+ const globalFile = loadScope("global", paths.global, readFile, problems);
68
+ const projectFile = projectTrusted ? loadScope("project", paths.project, readFile, problems) : {};
69
+ return {
70
+ ok: problems.length === 0,
71
+ // Fail closed: a broken file hands the caller the unrouted default and the
72
+ // problems that explain why dispatch is refused.
73
+ config: problems.length === 0 ? mergeConfigs(globalFile, projectFile) : DEFAULT_CONFIG,
74
+ problems,
75
+ };
76
+ }
@@ -0,0 +1,4 @@
1
+ import type { ConfigFile, EffectiveConfig, Route } from "./types.js";
2
+ export declare function mergeConfigs(globalFile?: ConfigFile, projectFile?: ConfigFile): EffectiveConfig;
3
+ /** The route a command runs under: its own resolved override, else the default route. */
4
+ export declare function effectiveRoute(config: EffectiveConfig, command: string): Route;
@@ -0,0 +1,43 @@
1
+ import { DEFAULT_CONFIG, DEFAULT_ROUTE } from "./defaults.js";
2
+ /**
3
+ * Project-over-global merge (SPEC S5, S6).
4
+ *
5
+ * Merge granularity is the individual route key, not the route object: a
6
+ * project that sets only `commands.x.model` keeps the global `thinking` for
7
+ * that command. A command neither scope mentions falls back to the resolved
8
+ * default route, and an absent default is the shipped `inherit`.
9
+ *
10
+ * Inputs are validated files — an invalid file never reaches this function
11
+ * (D-E5) — and both are treated as read-only: the result is built from fresh
12
+ * objects so no caller can alias through it.
13
+ */
14
+ function pick(fallback, ...values) {
15
+ for (const value of values)
16
+ if (value !== undefined)
17
+ return value;
18
+ return fallback;
19
+ }
20
+ function resolveRoute(defaults, globalRoute, projectRoute) {
21
+ return {
22
+ model: pick(DEFAULT_ROUTE.model, projectRoute?.model, globalRoute?.model, defaults.model),
23
+ thinking: pick(DEFAULT_ROUTE.thinking, projectRoute?.thinking, globalRoute?.thinking, defaults.thinking),
24
+ };
25
+ }
26
+ export function mergeConfigs(globalFile = {}, projectFile = {}) {
27
+ const effectiveDefault = resolveRoute(DEFAULT_ROUTE, globalFile.default, projectFile.default);
28
+ const globalCommands = globalFile.commands ?? {};
29
+ const projectCommands = projectFile.commands ?? {};
30
+ const commands = {};
31
+ for (const name of new Set([...Object.keys(globalCommands), ...Object.keys(projectCommands)])) {
32
+ commands[name] = resolveRoute(effectiveDefault, globalCommands[name], projectCommands[name]);
33
+ }
34
+ return {
35
+ default: effectiveDefault,
36
+ commands,
37
+ onUnavailableRoute: projectFile.onUnavailableRoute ?? globalFile.onUnavailableRoute ?? DEFAULT_CONFIG.onUnavailableRoute,
38
+ };
39
+ }
40
+ /** The route a command runs under: its own resolved override, else the default route. */
41
+ export function effectiveRoute(config, command) {
42
+ return config.commands[command] ?? config.default;
43
+ }
@@ -0,0 +1,21 @@
1
+ import type { ConfigFile, ConfigIssue } from "./types.js";
2
+ /** The split Pi performs when resolving a reference: provider before the first slash. */
3
+ export interface ModelParts {
4
+ provider: string;
5
+ id: string;
6
+ }
7
+ /** Same rule as `isModelReference`, plus the split Pi performs when resolving. */
8
+ export declare function parseModelReference(value: unknown): ModelParts | undefined;
9
+ export type ParseResult = {
10
+ ok: true;
11
+ config: ConfigFile;
12
+ } | {
13
+ ok: false;
14
+ issues: ConfigIssue[];
15
+ };
16
+ /**
17
+ * Parse and validate one config file's text. A blank file is an empty config so
18
+ * the loader can treat "nothing declared" like "nothing present" without a
19
+ * second read.
20
+ */
21
+ export declare function parseConfigFile(text: string): ParseResult;
@@ -0,0 +1,144 @@
1
+ import { THINKING_LEVELS, UNAVAILABLE_ROUTE_POLICIES } from "./types.js";
2
+ /**
3
+ * Strict validator for one config file (SPEC S5-S8, D-E5).
4
+ *
5
+ * Strict means: only the documented keys, only the documented value shapes, and
6
+ * no coercion. A file that is *present but invalid* must produce issues,
7
+ * because silently falling back to `inherit` would let an operator believe a
8
+ * strong model ran when it did not. Only a *missing* file resolves to the
9
+ * default, and that decision belongs to the loader.
10
+ */
11
+ const ROOT_KEYS = new Set(["default", "commands", "onUnavailableRoute"]);
12
+ const ROUTE_KEYS = new Set(["model", "thinking"]);
13
+ const COMMAND_NAME = /^[a-z0-9][a-z0-9._-]*$/u;
14
+ function isRecord(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ /** Bracket-quoted when a key is not a plain slug, so the path stays parseable. */
18
+ function displayKey(key) {
19
+ return COMMAND_NAME.test(key) ? key : JSON.stringify(key);
20
+ }
21
+ /** An exact `provider/modelId`. The id may contain slashes: Pi splits at the first one. */
22
+ function isModelReference(value) {
23
+ return parseModelReference(value) !== undefined;
24
+ }
25
+ /** Same rule as `isModelReference`, plus the split Pi performs when resolving. */
26
+ export function parseModelReference(value) {
27
+ if (typeof value !== "string" || value !== value.trim() || /\s/u.test(value))
28
+ return undefined;
29
+ const slash = value.indexOf("/");
30
+ if (slash <= 0 || slash >= value.length - 1)
31
+ return undefined;
32
+ return { provider: value.slice(0, slash), id: value.slice(slash + 1) };
33
+ }
34
+ function isThinkingSetting(value) {
35
+ return value === "inherit" || THINKING_LEVELS.includes(value);
36
+ }
37
+ function isUnavailableRoutePolicy(value) {
38
+ return UNAVAILABLE_ROUTE_POLICIES.includes(value);
39
+ }
40
+ function describe(value) {
41
+ return typeof value === "string" ? `"${value}"` : JSON.stringify(value) ?? String(value);
42
+ }
43
+ function checkRoute(value, path, issues) {
44
+ if (!isRecord(value)) {
45
+ issues.push({ path, message: "must be an object with optional model/thinking keys" });
46
+ return undefined;
47
+ }
48
+ const route = {};
49
+ for (const [key, entry] of Object.entries(value)) {
50
+ if (!ROUTE_KEYS.has(key)) {
51
+ issues.push({ path: `${path}.${displayKey(key)}`, message: `unknown route key "${key}" (allowed: model, thinking)` });
52
+ continue;
53
+ }
54
+ if (key === "model") {
55
+ if (entry !== "inherit" && !isModelReference(entry)) {
56
+ issues.push({
57
+ path: `${path}.model`,
58
+ message: `must be "inherit" or "provider/modelId", got ${describe(entry)}`,
59
+ });
60
+ continue;
61
+ }
62
+ route.model = entry;
63
+ continue;
64
+ }
65
+ if (!isThinkingSetting(entry)) {
66
+ issues.push({
67
+ path: `${path}.thinking`,
68
+ message: `must be "inherit" or one of ${THINKING_LEVELS.join(", ")}, got ${describe(entry)}`,
69
+ });
70
+ continue;
71
+ }
72
+ route.thinking = entry;
73
+ }
74
+ return route;
75
+ }
76
+ /** Validate an already-parsed JSON value. */
77
+ function validateConfig(value) {
78
+ if (!isRecord(value)) {
79
+ return { ok: false, issues: [{ path: "$", message: "config root must be a JSON object" }] };
80
+ }
81
+ const issues = [];
82
+ const config = {};
83
+ for (const key of Object.keys(value)) {
84
+ if (!ROOT_KEYS.has(key)) {
85
+ issues.push({
86
+ path: `$.${displayKey(key)}`,
87
+ message: `unknown config key "${key}" (allowed: default, commands, onUnavailableRoute)`,
88
+ });
89
+ }
90
+ }
91
+ if (value.default !== undefined) {
92
+ const route = checkRoute(value.default, "$.default", issues);
93
+ if (route)
94
+ config.default = route;
95
+ }
96
+ if (value.commands !== undefined) {
97
+ if (!isRecord(value.commands)) {
98
+ issues.push({ path: "$.commands", message: "must be an object mapping command names to routes" });
99
+ }
100
+ else {
101
+ const commands = {};
102
+ for (const [name, raw] of Object.entries(value.commands)) {
103
+ if (!COMMAND_NAME.test(name)) {
104
+ issues.push({ path: `$.commands.${displayKey(name)}`, message: "command name must be a single lowercase slug" });
105
+ continue;
106
+ }
107
+ const route = checkRoute(raw, `$.commands.${displayKey(name)}`, issues);
108
+ if (route)
109
+ commands[name] = route;
110
+ }
111
+ config.commands = commands;
112
+ }
113
+ }
114
+ if (value.onUnavailableRoute !== undefined) {
115
+ if (!isUnavailableRoutePolicy(value.onUnavailableRoute)) {
116
+ issues.push({
117
+ path: "$.onUnavailableRoute",
118
+ message: `must be "stop" or "inherit", got ${describe(value.onUnavailableRoute)}`,
119
+ });
120
+ }
121
+ else {
122
+ config.onUnavailableRoute = value.onUnavailableRoute;
123
+ }
124
+ }
125
+ return issues.length > 0 ? { ok: false, issues } : { ok: true, config };
126
+ }
127
+ /**
128
+ * Parse and validate one config file's text. A blank file is an empty config so
129
+ * the loader can treat "nothing declared" like "nothing present" without a
130
+ * second read.
131
+ */
132
+ export function parseConfigFile(text) {
133
+ if (text.trim() === "")
134
+ return { ok: true, config: {} };
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(text);
138
+ }
139
+ catch (error) {
140
+ const reason = error instanceof Error ? error.message : String(error);
141
+ return { ok: false, issues: [{ path: "$", message: `invalid JSON: ${reason}` }] };
142
+ }
143
+ return validateConfig(parsed);
144
+ }