@gtrabanco/pi-agentic-workflow 0.8.0 → 0.9.1
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.
- package/README.es.md +26 -9
- package/README.md +26 -8
- package/dist/config/defaults.js +1 -0
- package/dist/config/merge.js +1 -0
- package/dist/config/schema.js +17 -3
- package/dist/config/types.d.ts +11 -0
- package/dist/config/types.js +1 -0
- package/dist/routing/dispatch.js +24 -2
- package/dist/settings/console.d.ts +2 -0
- package/dist/settings/console.js +16 -1
- package/dist/settings/view.js +1 -0
- package/package.json +1 -1
- package/skills/workflow-status/SKILL.md +50 -75
- package/skills/workflow-status/references/ENVELOPE_CORE.md +17 -2
- package/skills/workflow-status/references/ENVELOPE_FIELDS.md +9 -0
- package/skills/workflow-status/references/SENSOR_CORE.md +9 -9
- package/skills/workflow-status/references/SENSOR_SIGNALS.md +8 -5
package/README.es.md
CHANGED
|
@@ -77,7 +77,8 @@ Dos archivos JSON, ambos opcionales:
|
|
|
77
77
|
"plan-feature": { "model": ["anthropic/claude-sonnet-4-5", "openai/gpt-5.2"], "thinking": "medium" },
|
|
78
78
|
"review-change": { "thinking": "max" }
|
|
79
79
|
},
|
|
80
|
-
"onUnavailableRoute": "stop"
|
|
80
|
+
"onUnavailableRoute": "stop",
|
|
81
|
+
"onSettle": "keep"
|
|
81
82
|
}
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -97,6 +98,8 @@ que ya tuviera la sesión, porque la ruta por defecto del paquete es
|
|
|
97
98
|
prueba `anthropic/claude-sonnet-4-5` primero y luego `openai/gpt-5.2`.
|
|
98
99
|
- `thinking` es uno de `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`,
|
|
99
100
|
o `"inherit"`.
|
|
101
|
+
- `` `onSettle` `` es uno de `"keep"` (el default) o `"restore"` — ver
|
|
102
|
+
[Tu sesión después de un comando](#tu-sesión-después-de-un-comando).
|
|
100
103
|
- Claves desconocidas, `null` y referencias mal formadas se **rechazan**, no se
|
|
101
104
|
ignoran: un error tipográfico que no hace nada en silencio es el bug que nunca
|
|
102
105
|
encuentras.
|
|
@@ -116,14 +119,28 @@ aun así:
|
|
|
116
119
|
{ "onUnavailableRoute": "inherit" }
|
|
117
120
|
```
|
|
118
121
|
|
|
119
|
-
## Tu sesión
|
|
122
|
+
## Tu sesión después de un comando
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
Por defecto (**`onSettle: "keep"`**), el modelo enrutado y el nivel de thinking
|
|
125
|
+
**se quedan** en la ventana de chat abierta cuando el comando termina. Si
|
|
126
|
+
`plan-feature` corre en `glm-5` y quieres retocar el plan que produjo, tu siguiente
|
|
127
|
+
prompt sigue corriendo en `glm-5` — igual con una pregunta posterior. Cuando no
|
|
128
|
+
necesitas el modelo pesado, lo cambias tú con `/model` (o Ctrl+P) a algo más
|
|
129
|
+
barato; nada restaura sobre tu elección.
|
|
130
|
+
|
|
131
|
+
Para volver a traer el modelo y el nivel de thinking previos al comando cuando este
|
|
132
|
+
termina, pon:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{ "onSettle": "restore" }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
(El modo `restore` es el histórico contrato AC8: tras un comando enrutado la
|
|
139
|
+
sesión vuelve a como estaba — el modelo *y* el nivel de thinking, porque
|
|
140
|
+
seleccionar un modelo puede mover el nivel. Si cambias el modelo tú mismo a
|
|
141
|
+
mitad de turno, con `/model` por ejemplo, no se restaura nada: tu elección gana,
|
|
142
|
+
y el comando lo dice. Si mueves solo el nivel de thinking, lo conservas mientras
|
|
143
|
+
el modelo vuelve.)
|
|
127
144
|
|
|
128
145
|
## Consola de configuración
|
|
129
146
|
|
|
@@ -159,7 +176,7 @@ consola nunca se queda sin salida.
|
|
|
159
176
|
| `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. |
|
|
160
177
|
| `refused: the agent is busy` | Hay un turno en ejecución. Espera a que termine. |
|
|
161
178
|
| `is still routed` | El comando enrutado anterior no ha terminado. |
|
|
162
|
-
| `leaving the model you chose in place` | Cambiaste el modelo durante
|
|
179
|
+
| `leaving the model you chose in place` | (en modo `restore`) Cambiaste el modelo durante un turno enrutado, así que no se restauró nada: tu elección ganó. |
|
|
163
180
|
| `these configured routes match no command` | Una clave de `commands` no nombra nada. Corrige la escritura o elimina la entrada. |
|
|
164
181
|
|
|
165
182
|
## Notas
|
package/README.md
CHANGED
|
@@ -75,7 +75,8 @@ Two JSON files, both optional:
|
|
|
75
75
|
"plan-feature": { "model": ["anthropic/claude-sonnet-4-5", "openai/gpt-5.2"], "thinking": "medium" },
|
|
76
76
|
"review-change": { "thinking": "max" }
|
|
77
77
|
},
|
|
78
|
-
"onUnavailableRoute": "stop"
|
|
78
|
+
"onUnavailableRoute": "stop",
|
|
79
|
+
"onSettle": "keep"
|
|
79
80
|
}
|
|
80
81
|
```
|
|
81
82
|
|
|
@@ -95,6 +96,8 @@ whatever the session already had, because the shipped default route is
|
|
|
95
96
|
then `openai/gpt-5.2`.
|
|
96
97
|
- `thinking` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`,
|
|
97
98
|
or `"inherit"`.
|
|
99
|
+
- `` `onSettle` `` is one of `"keep"` (the default) or `"restore"` — see
|
|
100
|
+
[Your session after a command](#your-session-after-a-command).
|
|
98
101
|
- Unknown keys, `null`s and malformed references are **rejected**, not ignored: a
|
|
99
102
|
typo that silently did nothing is the bug you would never find.
|
|
100
103
|
|
|
@@ -112,13 +115,28 @@ nothing runs on a model you did not pick. To run anyway on the current model, se
|
|
|
112
115
|
{ "onUnavailableRoute": "inherit" }
|
|
113
116
|
```
|
|
114
117
|
|
|
115
|
-
## Your session
|
|
118
|
+
## Your session after a command
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
By default (**`onSettle: "keep"`**), the routed model and thinking level **stay**
|
|
121
|
+
in the open chat window once the command settles. If `plan-feature` runs on
|
|
122
|
+
`glm-5` and you want to tweak the plan it produced, your next prompt keeps
|
|
123
|
+
running on `glm-5` — same for a follow-up question. When you don't need the
|
|
124
|
+
heavy model, switch it yourself with `/model` (or Ctrl+P) to something cheaper;
|
|
125
|
+
nothing restores over your choice.
|
|
126
|
+
|
|
127
|
+
To bring back the pre-command model and thinking level after a command settles,
|
|
128
|
+
set:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{ "onSettle": "restore" }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
(The `restore` mode is the historical AC8 contract: after a routed command the
|
|
135
|
+
session is put back the way you had it — the model *and* the thinking level,
|
|
136
|
+
because selecting a model can move the level. If you change the model yourself
|
|
137
|
+
mid-turn, with `/model` say, nothing is restored: your choice wins, and the
|
|
138
|
+
command says so. Change only the thinking level and you keep it while the model
|
|
139
|
+
still comes back.)
|
|
122
140
|
|
|
123
141
|
## Settings console
|
|
124
142
|
|
|
@@ -152,7 +170,7 @@ fall back to a plain prompt, so the console never dead-ends.
|
|
|
152
170
|
| `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. |
|
|
153
171
|
| `refused: the agent is busy` | A turn is running. Wait for it to settle. |
|
|
154
172
|
| `is still routed` | The previous routed command has not settled yet. |
|
|
155
|
-
| `leaving the model you chose in place` | You changed the model during a routed turn, so nothing was restored — your choice won. |
|
|
173
|
+
| `leaving the model you chose in place` | (in `restore` mode) You changed the model during a routed turn, so nothing was restored — your choice won. |
|
|
156
174
|
| `these configured routes match no command` | A `commands` key names nothing. Fix the spelling or delete the entry. |
|
|
157
175
|
|
|
158
176
|
## Notes
|
package/dist/config/defaults.js
CHANGED
package/dist/config/merge.js
CHANGED
|
@@ -35,6 +35,7 @@ export function mergeConfigs(globalFile = {}, projectFile = {}) {
|
|
|
35
35
|
default: effectiveDefault,
|
|
36
36
|
commands,
|
|
37
37
|
onUnavailableRoute: projectFile.onUnavailableRoute ?? globalFile.onUnavailableRoute ?? DEFAULT_CONFIG.onUnavailableRoute,
|
|
38
|
+
onSettle: projectFile.onSettle ?? globalFile.onSettle ?? DEFAULT_CONFIG.onSettle,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
/** The route a command runs under: its own resolved override, else the default route. */
|
package/dist/config/schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_MODEL_CHAIN, THINKING_LEVELS, UNAVAILABLE_ROUTE_POLICIES } from "./types.js";
|
|
1
|
+
import { MAX_MODEL_CHAIN, SETTLE_POLICIES, THINKING_LEVELS, UNAVAILABLE_ROUTE_POLICIES } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Strict validator for one config file (SPEC S5-S8, D-E5).
|
|
4
4
|
*
|
|
@@ -8,7 +8,7 @@ import { MAX_MODEL_CHAIN, THINKING_LEVELS, UNAVAILABLE_ROUTE_POLICIES } from "./
|
|
|
8
8
|
* strong model ran when it did not. Only a *missing* file resolves to the
|
|
9
9
|
* default, and that decision belongs to the loader.
|
|
10
10
|
*/
|
|
11
|
-
const ROOT_KEYS = new Set(["default", "commands", "onUnavailableRoute"]);
|
|
11
|
+
const ROOT_KEYS = new Set(["default", "commands", "onUnavailableRoute", "onSettle"]);
|
|
12
12
|
const ROUTE_KEYS = new Set(["model", "thinking"]);
|
|
13
13
|
const COMMAND_NAME = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
14
14
|
function isRecord(value) {
|
|
@@ -37,6 +37,9 @@ function isThinkingSetting(value) {
|
|
|
37
37
|
function isUnavailableRoutePolicy(value) {
|
|
38
38
|
return UNAVAILABLE_ROUTE_POLICIES.includes(value);
|
|
39
39
|
}
|
|
40
|
+
function isSettlePolicy(value) {
|
|
41
|
+
return SETTLE_POLICIES.includes(value);
|
|
42
|
+
}
|
|
40
43
|
function describe(value) {
|
|
41
44
|
return typeof value === "string" ? `"${value}"` : JSON.stringify(value) ?? String(value);
|
|
42
45
|
}
|
|
@@ -115,7 +118,7 @@ function validateConfig(value) {
|
|
|
115
118
|
if (!ROOT_KEYS.has(key)) {
|
|
116
119
|
issues.push({
|
|
117
120
|
path: `$.${displayKey(key)}`,
|
|
118
|
-
message: `unknown config key "${key}" (allowed: default, commands, onUnavailableRoute)`,
|
|
121
|
+
message: `unknown config key "${key}" (allowed: default, commands, onUnavailableRoute, onSettle)`,
|
|
119
122
|
});
|
|
120
123
|
}
|
|
121
124
|
}
|
|
@@ -153,6 +156,17 @@ function validateConfig(value) {
|
|
|
153
156
|
config.onUnavailableRoute = value.onUnavailableRoute;
|
|
154
157
|
}
|
|
155
158
|
}
|
|
159
|
+
if (value.onSettle !== undefined) {
|
|
160
|
+
if (!isSettlePolicy(value.onSettle)) {
|
|
161
|
+
issues.push({
|
|
162
|
+
path: "$.onSettle",
|
|
163
|
+
message: `must be "keep" or "restore", got ${describe(value.onSettle)}`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
config.onSettle = value.onSettle;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
156
170
|
return issues.length > 0 ? { ok: false, issues } : { ok: true, config };
|
|
157
171
|
}
|
|
158
172
|
/**
|
package/dist/config/types.d.ts
CHANGED
|
@@ -37,18 +37,29 @@ export interface ConfigFile {
|
|
|
37
37
|
default?: RouteFile;
|
|
38
38
|
commands?: Record<string, RouteFile>;
|
|
39
39
|
onUnavailableRoute?: UnavailableRoutePolicy;
|
|
40
|
+
/** What happens to the routed model and thinking level after the command settles. */
|
|
41
|
+
onSettle?: SettlePolicy;
|
|
40
42
|
}
|
|
41
43
|
export type UnavailableRoutePolicy = "stop" | "inherit";
|
|
44
|
+
/**
|
|
45
|
+
* What the session holds after a routed command settles.
|
|
46
|
+
* - `"keep"` — the routed model and thinking level stay in the open chat window,
|
|
47
|
+
* so a follow-up edit or question keeps running on the model that planned it.
|
|
48
|
+
* - `"restore"` — the pre-dispatch model and thinking level come back (AC8).
|
|
49
|
+
*/
|
|
50
|
+
export type SettlePolicy = "keep" | "restore";
|
|
42
51
|
/** One rejected field inside one config file, addressed by a JSON-path-ish string. */
|
|
43
52
|
export interface ConfigIssue {
|
|
44
53
|
path: string;
|
|
45
54
|
message: string;
|
|
46
55
|
}
|
|
47
56
|
export declare const UNAVAILABLE_ROUTE_POLICIES: readonly UnavailableRoutePolicy[];
|
|
57
|
+
export declare const SETTLE_POLICIES: readonly SettlePolicy[];
|
|
48
58
|
export interface EffectiveConfig {
|
|
49
59
|
default: Route;
|
|
50
60
|
commands: Record<string, Route>;
|
|
51
61
|
onUnavailableRoute: UnavailableRoutePolicy;
|
|
62
|
+
onSettle: SettlePolicy;
|
|
52
63
|
}
|
|
53
64
|
export interface ConfigProblem {
|
|
54
65
|
scope: "global" | "project";
|
package/dist/config/types.js
CHANGED
|
@@ -12,3 +12,4 @@ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhig
|
|
|
12
12
|
/** Cap on chain length (issue deliberation: 8 was "almost certainly a mistake"; 4 keeps the view readable). */
|
|
13
13
|
export const MAX_MODEL_CHAIN = 4;
|
|
14
14
|
export const UNAVAILABLE_ROUTE_POLICIES = ["stop", "inherit"];
|
|
15
|
+
export const SETTLE_POLICIES = ["keep", "restore"];
|
package/dist/routing/dispatch.js
CHANGED
|
@@ -79,6 +79,12 @@ export function createRouter({ surface, loadConfig, hint, settingsCommand, known
|
|
|
79
79
|
// Cleared first: the restore fires its own select events, and a turn that is
|
|
80
80
|
// already over must not read them as operator changes.
|
|
81
81
|
pending = undefined;
|
|
82
|
+
// `keep`: leave the routed model and thinking level in the open chat window,
|
|
83
|
+
// so a follow-up edit or question keeps running on the model that planned it.
|
|
84
|
+
// The operator's own mid-turn choice is still untouched (nothing is restored,
|
|
85
|
+
// so it cannot be overwritten). Only "restore" puts the snapshot back (AC8).
|
|
86
|
+
if (turn.settlePolicy === "keep")
|
|
87
|
+
return;
|
|
82
88
|
await restore(turn, surface(ctx), ctx, "finished");
|
|
83
89
|
},
|
|
84
90
|
async dispatch(command, args, ctx) {
|
|
@@ -198,7 +204,14 @@ export function createRouter({ surface, loadConfig, hint, settingsCommand, known
|
|
|
198
204
|
ctx.notify(`/${command.name} is running. Per-command models are optional: configure them with /${settingsCommand} or the pi-agentic-workflow.json files. This hint appears once.`, "info");
|
|
199
205
|
}
|
|
200
206
|
if (applied.model || applied.thinking) {
|
|
201
|
-
pending = {
|
|
207
|
+
pending = {
|
|
208
|
+
command: command.name,
|
|
209
|
+
snapshot,
|
|
210
|
+
applied,
|
|
211
|
+
settlePolicy: loaded.config.onSettle,
|
|
212
|
+
userChangedModel: false,
|
|
213
|
+
userChangedThinking: false,
|
|
214
|
+
};
|
|
202
215
|
}
|
|
203
216
|
// Pi expands `/skill:<x>` by the skill's frontmatter `name:`, and passes an
|
|
204
217
|
// unknown key through as literal text — so the name is the only correct
|
|
@@ -217,7 +230,16 @@ export function createRouter({ surface, loadConfig, hint, settingsCommand, known
|
|
|
217
230
|
pending = undefined;
|
|
218
231
|
const rolledBack = Boolean(applied.model || applied.thinking);
|
|
219
232
|
if (rolledBack) {
|
|
220
|
-
await restore({
|
|
233
|
+
await restore({
|
|
234
|
+
command: command.name,
|
|
235
|
+
snapshot,
|
|
236
|
+
applied,
|
|
237
|
+
// The turn never started, so the rollback always restores — it is not a
|
|
238
|
+
// settle, and `onSettle` does not apply. Explicit, not read from config.
|
|
239
|
+
settlePolicy: "restore",
|
|
240
|
+
userChangedModel: false,
|
|
241
|
+
userChangedThinking: false,
|
|
242
|
+
}, session, ctx, `dispatch failed (${error.message}); the session was put back`);
|
|
221
243
|
}
|
|
222
244
|
return refuse(ctx, "dispatch-failed", `/${command.name} was not dispatched: ${error.message}. Nothing was sent${rolledBack ? ", and the session model was put back" : ""}.`);
|
|
223
245
|
}
|
|
@@ -40,6 +40,7 @@ export declare const prompts: {
|
|
|
40
40
|
readonly undone: "The routing was undone and the session put back.";
|
|
41
41
|
readonly command: "Which command?";
|
|
42
42
|
readonly policyChoice: "What should happen when a configured model is unavailable?";
|
|
43
|
+
readonly settleChoice: "What should happen after a routed command settles?";
|
|
43
44
|
readonly saveTo: (path: string) => string;
|
|
44
45
|
readonly discard: "Discard the draft?";
|
|
45
46
|
readonly setDefaultRoute: "Set the default route";
|
|
@@ -49,6 +50,7 @@ export declare const prompts: {
|
|
|
49
50
|
readonly bulkClear: "Clear several overrides";
|
|
50
51
|
readonly addAnother: "Add another?";
|
|
51
52
|
readonly policy: "Set the unavailable-route policy";
|
|
53
|
+
readonly settle: "Set the post-command model keep/restore policy";
|
|
52
54
|
readonly save: "Save";
|
|
53
55
|
readonly cancel: "Cancel";
|
|
54
56
|
readonly model: (target: string) => string;
|
package/dist/settings/console.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { loadConfig, configFilePaths } from "../config/load.js";
|
|
11
11
|
import { effectiveRoute } from "../config/merge.js";
|
|
12
12
|
import { parseConfigFile, parseModelReference } from "../config/schema.js";
|
|
13
|
-
import { MAX_MODEL_CHAIN, THINKING_LEVELS, UNAVAILABLE_ROUTE_POLICIES } from "../config/types.js";
|
|
13
|
+
import { MAX_MODEL_CHAIN, SETTLE_POLICIES, THINKING_LEVELS, UNAVAILABLE_ROUTE_POLICIES } from "../config/types.js";
|
|
14
14
|
import { renderMergedConfig, routePath, DEFAULT_ROUTE } from "./view.js";
|
|
15
15
|
/** The console's questions. `test/settings-console.test.mjs` drives the flow
|
|
16
16
|
* through these strings, so renaming one fails the tests that use it rather than
|
|
@@ -23,6 +23,7 @@ export const prompts = {
|
|
|
23
23
|
undone: "The routing was undone and the session put back.",
|
|
24
24
|
command: "Which command?",
|
|
25
25
|
policyChoice: "What should happen when a configured model is unavailable?",
|
|
26
|
+
settleChoice: "What should happen after a routed command settles?",
|
|
26
27
|
saveTo: (path) => `Save the draft to ${path}?`,
|
|
27
28
|
discard: "Discard the draft?",
|
|
28
29
|
setDefaultRoute: "Set the default route",
|
|
@@ -32,6 +33,7 @@ export const prompts = {
|
|
|
32
33
|
bulkClear: "Clear several overrides",
|
|
33
34
|
addAnother: "Add another?",
|
|
34
35
|
policy: "Set the unavailable-route policy",
|
|
36
|
+
settle: "Set the post-command model keep/restore policy",
|
|
35
37
|
save: "Save",
|
|
36
38
|
cancel: "Cancel",
|
|
37
39
|
model: (target) => `Model for ${target}?`,
|
|
@@ -71,6 +73,7 @@ export async function runSettingsConsole(deps) {
|
|
|
71
73
|
prompts.bulkApply,
|
|
72
74
|
prompts.bulkClear,
|
|
73
75
|
prompts.policy,
|
|
76
|
+
prompts.settle,
|
|
74
77
|
...(deps.routing?.inFlight() ? [prompts.undoInFlight] : []),
|
|
75
78
|
prompts.save,
|
|
76
79
|
prompts.cancel,
|
|
@@ -142,6 +145,12 @@ export async function runSettingsConsole(deps) {
|
|
|
142
145
|
draft = { ...draft, onUnavailableRoute: picked };
|
|
143
146
|
continue;
|
|
144
147
|
}
|
|
148
|
+
if (choice === prompts.settle) {
|
|
149
|
+
const picked = await deps.ui.select(prompts.settleChoice, [...SETTLE_POLICIES]);
|
|
150
|
+
if (isSettle(picked))
|
|
151
|
+
draft = { ...draft, onSettle: picked };
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
145
154
|
if (choice === prompts.save) {
|
|
146
155
|
const saved = await saveScope(deps, scope, path, draft);
|
|
147
156
|
if (saved)
|
|
@@ -405,6 +414,7 @@ function describeRouting(file) {
|
|
|
405
414
|
file.default ? `default: ${file.default.model ?? "inherit"} / ${file.default.thinking ?? "inherit"}` : "default: inherit / inherit",
|
|
406
415
|
...Object.entries(file.commands ?? {}).map(([name, route]) => `${name}: ${route.model ?? "inherit"} / ${route.thinking ?? "inherit"}`),
|
|
407
416
|
`unavailable: ${file.onUnavailableRoute ?? "stop"}`,
|
|
417
|
+
`settle: ${file.onSettle ?? "keep"}`,
|
|
408
418
|
];
|
|
409
419
|
return routes.join(" · ");
|
|
410
420
|
}
|
|
@@ -428,6 +438,8 @@ function clean(draft) {
|
|
|
428
438
|
file.commands = commands;
|
|
429
439
|
if (draft.onUnavailableRoute)
|
|
430
440
|
file.onUnavailableRoute = draft.onUnavailableRoute;
|
|
441
|
+
if (draft.onSettle)
|
|
442
|
+
file.onSettle = draft.onSettle;
|
|
431
443
|
return file;
|
|
432
444
|
}
|
|
433
445
|
function parseFile(text, scope) {
|
|
@@ -448,3 +460,6 @@ function isThinkingLevel(value) {
|
|
|
448
460
|
function isPolicy(value) {
|
|
449
461
|
return value === "stop" || value === "inherit";
|
|
450
462
|
}
|
|
463
|
+
function isSettle(value) {
|
|
464
|
+
return value === "keep" || value === "restore";
|
|
465
|
+
}
|
package/dist/settings/view.js
CHANGED
|
@@ -31,6 +31,7 @@ function configLines(config, commands) {
|
|
|
31
31
|
` ${overrides.length} of ${total} commands override the default route`,
|
|
32
32
|
]),
|
|
33
33
|
` when a configured model is unavailable: ${config.onUnavailableRoute}`,
|
|
34
|
+
` after a routed command settles: ${config.onSettle}`,
|
|
34
35
|
];
|
|
35
36
|
}
|
|
36
37
|
function route(value) {
|
package/package.json
CHANGED
|
@@ -1,62 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workflow-status
|
|
3
3
|
user-invocable: true
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.4.0
|
|
5
5
|
author: "Gabriel Trabanco <gtrabanco@users.noreply.github.com>"
|
|
6
6
|
license: MIT
|
|
7
7
|
argument-hint: "[--json-only] [--last-envelope <json|path>]"
|
|
8
8
|
description: >
|
|
9
|
-
Read-only workflow sensor:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
next", "state of the run".
|
|
9
|
+
Read-only workflow sensor: run the deterministic script, read the fixed
|
|
10
|
+
machine envelope, interpret the recommendation. Never edits. Triggers:
|
|
11
|
+
"workflow-status", "workflow status", "what can I build next".
|
|
13
12
|
---
|
|
14
13
|
|
|
15
14
|
# Workflow Status (the orchestrator's sensor)
|
|
16
15
|
|
|
17
|
-
One read-only pass
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
One read-only pass that answers, in a single fixed JSON envelope: **what exists,
|
|
17
|
+
what is blocked on what, what is startable right now, and what the recommended
|
|
18
|
+
next command is.** The **script is the deterministic producer**:
|
|
19
|
+
`scripts/workflow-status.mjs` executes the published `SENSOR_CORE` sequence
|
|
20
|
+
(steps 1–9 including 6a) and prints the envelope. This skill runs the script,
|
|
21
|
+
reads the JSON, interprets it against the references below, and prints the human
|
|
22
|
+
report — it never assembles the envelope by hand.
|
|
22
23
|
|
|
23
24
|
## Turn contract — verify before ending the turn
|
|
24
25
|
|
|
25
26
|
```
|
|
26
|
-
✓
|
|
27
|
-
|
|
27
|
+
✓ The script was RUN — `bun scripts/workflow-status.mjs [--json-only]
|
|
28
|
+
[--last-envelope <json|path>]` (node is the fallback when bun is absent, per
|
|
29
|
+
the repository's runtime convention); the envelope is the script's stdout, never
|
|
30
|
+
assembled by the model
|
|
28
31
|
✓ Nothing was edited, committed, pushed, or created — read-only, always
|
|
29
32
|
✓ `next.recommended` is non-bare (carries the unit's slug/NN, never a bare
|
|
30
|
-
`/plan-feature`) AND
|
|
31
|
-
current pre-execution evidence: `idea`/undesigned →
|
|
32
|
-
`defined` → `/plan-feature <slug>` only on a current
|
|
33
|
-
`/review-spec <slug>`; `planned`/`in-progress` → `/execute-phase <NN>` only on a
|
|
33
|
+
`/plan-feature`) AND the script computed it from the unit's resolved status
|
|
34
|
+
**and** its current pre-execution evidence: `idea`/undesigned →
|
|
35
|
+
`/design-feature <slug>`; `defined` → `/plan-feature <slug>` only on a current
|
|
36
|
+
`SPEC-REVIEW-PASS`, else `/review-spec <slug>`; `planned`/`in-progress` → `/execute-phase <NN>` only on a
|
|
34
37
|
current `PLAN-REVIEW-PASS`, else `/review-plan <NN>` (step 6a)
|
|
35
|
-
✓
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
✓ Every `detail.design_candidates[].next` begins with `/design-feature `
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
`## Machine envelope` (never guessed)
|
|
48
|
-
✓ Per-unit `review`/`closure`/`issues_born` (steps 10–12) were computed per
|
|
49
|
-
their fixed rules — `adversarial.ran`/`n` stayed `null` unless real
|
|
50
|
-
evidence exists, never guessed — and any fired `next.suggested[]` entries
|
|
51
|
-
(step 13) quote their owning skill's condition verbatim, never a second
|
|
52
|
-
copy of the trigger logic
|
|
53
|
-
✓ The envelope is emitted on **every** invocation of this skill, including a
|
|
54
|
-
same-session natural-language follow-up about state — never replaced by prose
|
|
55
|
-
✓ The emitted envelope was checked against the shape reminders in
|
|
56
|
-
`## Machine envelope` (mirroring
|
|
57
|
-
`packages/agentic-workflow-schema/envelope.schema.json`) before printing
|
|
58
|
-
✓ The human-readable summary is printed, then the machine envelope (fenced
|
|
59
|
-
```json — see ## Machine envelope) is the ABSOLUTE last output
|
|
38
|
+
✓ `detail.crash_recovery` carries a verdict from the decision table and the
|
|
39
|
+
envelope `state` matches it (CLEAN→OK, RESUMABLE→CONTINUE,
|
|
40
|
+
AMBIGUOUS→NEEDS_INPUT)
|
|
41
|
+
✓ Every `detail.design_candidates[].next` begins with `/design-feature `
|
|
42
|
+
✓ Every degraded dimension is named in `detail.degradations` as
|
|
43
|
+
`unavailable-<source>-<cause>` — and, when `--last-envelope` was supplied, the
|
|
44
|
+
no-progress guard's `workflow_observations` note is present (never a silently
|
|
45
|
+
repeated bland recommendation)
|
|
46
|
+
✓ The envelope is emitted on **every** invocation, including a same-session
|
|
47
|
+
natural-language follow-up about state — never replaced by prose
|
|
48
|
+
✓ The human-readable summary is printed, then the machine envelope (the script's
|
|
49
|
+
JSON) is the ABSOLUTE last output
|
|
60
50
|
```
|
|
61
51
|
|
|
62
52
|
With `--json-only`, skip the human-readable summary: print the envelope alone.
|
|
@@ -66,43 +56,35 @@ With `--json-only`, skip the human-readable summary: print the envelope alone.
|
|
|
66
56
|
- Between orchestration steps: an external driver runs it to decide the next
|
|
67
57
|
command and model tier without parsing prose.
|
|
68
58
|
- Before picking work manually: "what can I start right now?"
|
|
69
|
-
- **Not** for judging quality (
|
|
70
|
-
|
|
59
|
+
- **Not** for judging quality (`review-change`/`audit-pr`) or product health
|
|
60
|
+
(`product-audit`) — this skill reports state, it never judges.
|
|
71
61
|
|
|
72
62
|
## Step 0 — Discover the project (always first)
|
|
73
63
|
|
|
74
|
-
Per the agent guide's **Workflow conventions** + **documentation map
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`progress.md` + `
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
Per the agent guide's **Workflow conventions** + **documentation map**. The
|
|
65
|
+
script reads what THIS skill needs (`docs/features/ROADMAP.md`, the fix index
|
|
66
|
+
`docs/fix/README.md`, every in-flight feature folder's `TASKS.md` +
|
|
67
|
+
`progress.md` + `review-findings.md`, and `docs/workflow/REPOSITORY_STATE.md`);
|
|
68
|
+
read them yourself only to interpret a field the script emitted.
|
|
80
69
|
|
|
81
70
|
## Progressive loading — fixed sensor route
|
|
82
71
|
|
|
83
72
|
The reference allowlist is exactly the seven linked paths below. Never invent or
|
|
84
|
-
read another `references/` path. This skill is a read-only sensor.
|
|
85
|
-
invocation loads this baseline in order:
|
|
73
|
+
read another `references/` path. This skill is a read-only sensor.
|
|
86
74
|
|
|
87
|
-
1. [sensor core](references/SENSOR_CORE.md)
|
|
75
|
+
1. [sensor core](references/SENSOR_CORE.md) — the sequence the script executes
|
|
88
76
|
2. [crash recovery](references/CRASH_RECOVERY.md)
|
|
89
77
|
3. [envelope core](references/ENVELOPE_CORE.md)
|
|
90
78
|
4. [envelope fields](references/ENVELOPE_FIELDS.md)
|
|
91
79
|
5. [pre-execution evidence](references/PRE_EXECUTION.md)
|
|
92
80
|
6. [guardrails](references/GUARDRAILS.md)
|
|
93
81
|
|
|
94
|
-
Add [pre-execution evidence](references/PRE_EXECUTION.md) whenever a unit is
|
|
95
|
-
`defined`, `planned` or `in-progress` — it defines step 6a (receipt sensing, the
|
|
96
|
-
one-label-per-stage table, and the legacy-adoption route).
|
|
97
82
|
Add [sensor signals](references/SENSOR_SIGNALS.md) only when a unit, issue,
|
|
98
83
|
finding, or recommendation exists; an empty project skips that file but still
|
|
99
84
|
emits the empty shapes defined by envelope fields. Add
|
|
100
85
|
[portability](references/PORTABILITY.md) only when the platform actually lacks a
|
|
101
86
|
named primitive. `--json-only` does not skip any baseline file.
|
|
102
87
|
|
|
103
|
-
All resources are one hop from this file. Missing required detail means
|
|
104
|
-
`BLOCKED`; do not fabricate a partial envelope.
|
|
105
|
-
|
|
106
88
|
## Portability
|
|
107
89
|
|
|
108
90
|
The sensor uses repository and forge commands only. When a named agent feature
|
|
@@ -122,28 +104,21 @@ the JSON contract.
|
|
|
122
104
|
|
|
123
105
|
## Done when
|
|
124
106
|
|
|
125
|
-
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
107
|
+
- The script ran and every claim in the report comes from its envelope — nothing
|
|
108
|
+
inferred from memory, nothing assembled by hand.
|
|
109
|
+
- `detail.design_candidates`, `detail.features`, `detail.fixes`,
|
|
110
|
+
`detail.startable_now`, `detail.blocked_units`, `detail.open_prs`,
|
|
111
|
+
`detail.untriaged_issues`, `detail.urgent`, `detail.degradations`, and
|
|
112
|
+
`detail.crash_recovery` were read from the envelope, and the envelope `state`
|
|
113
|
+
matches the crash-recovery verdict.
|
|
131
114
|
- With `--last-envelope` supplied: the no-progress guard ran — a stalled
|
|
132
115
|
`/plan-feature`/`/design-feature` hint surfaces as a `workflow_observations`
|
|
133
116
|
note, never a silent bland repeat, with no new write path introduced.
|
|
134
|
-
- The human summary (unless `--json-only`) and the envelope
|
|
135
|
-
|
|
136
|
-
blocked_units, open_prs, pending_triage, `untriaged_issues`
|
|
137
|
-
(count + oldest_open) and `urgent` (labels-only issue list +
|
|
138
|
-
interruptibility facts) — are printed, envelope last.
|
|
139
|
-
- Each `detail.features[]`/`detail.fixes[]` entry additionally carries
|
|
140
|
-
`review`, `closure`, and `issues_born` (steps 10–12) — `detail`-scoped, no
|
|
141
|
-
schema change — and any fired triggers appear in a top-level
|
|
142
|
-
`next.suggested[]` (step 13), single-sourced from the owning skill's own
|
|
143
|
-
condition text.
|
|
117
|
+
- The human summary (unless `--json-only`) and the envelope are printed, envelope
|
|
118
|
+
last.
|
|
144
119
|
- Nothing was modified anywhere.
|
|
145
120
|
|
|
146
121
|
→ Next: the envelope's `next.recommended` command — it is computed from the
|
|
147
122
|
actual state, so it IS the recommendation
|
|
148
123
|
· a human overview → read the printed table
|
|
149
|
-
· orchestrating programmatically → parse the
|
|
124
|
+
· orchestrating programmatically → parse the script's JSON
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
## Machine envelope
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`scripts/workflow-status.mjs` is the **deterministic producer** of this
|
|
4
|
+
envelope: it performs the assembly, runs the `validateEnvelope` self-check
|
|
5
|
+
(diagnostic only, exit 0), and prints the one JSON document. The skill runs the
|
|
6
|
+
script and interprets the JSON — it never assembles the envelope. Schema and
|
|
7
|
+
placement per the installed `orchestration-envelope` skill. The
|
|
4
8
|
`state` maps 1:1 from the crash-recovery verdict — **no new schema fields or
|
|
5
9
|
states** (the schema package needs no release):
|
|
6
10
|
|
|
@@ -68,6 +72,15 @@ these need **no package change**:
|
|
|
68
72
|
scope-bleed detection (widened by `#79`/`#89` to also match an issue
|
|
69
73
|
linked from an `## Amendments` row).
|
|
70
74
|
|
|
75
|
+
**Not yet mechanized (read this before consuming the three fields above).** The
|
|
76
|
+
`scripts/workflow-status.mjs` sensor does **not** emit `review`, `closure`, or
|
|
77
|
+
`issues_born` today: they are steps 10–12 of the published sequence and remain
|
|
78
|
+
skill-side work, so no `detail.features[]`/`detail.fixes[]` entry carries them
|
|
79
|
+
until a sensor phase owns them. A consumer must treat their absence as "not
|
|
80
|
+
computed", never as "empty": absence is not a `closure: absent-legacy` verdict
|
|
81
|
+
and not an `issues_born.n: 0`. Same discipline for `next.suggested[]` (step 13),
|
|
82
|
+
which is optional by contract and omitted when no trigger fired.
|
|
83
|
+
|
|
71
84
|
**`next.suggested[]`** — step 13's trigger-attributed suggestion surface,
|
|
72
85
|
`{command, trigger, source_skill}[]`, **optional** (mirrors
|
|
73
86
|
`packages/agentic-workflow-schema` 2.1.0's optional `EnvelopeSuggestion[]`).
|
|
@@ -77,7 +90,7 @@ second, drifting copy of that skill's logic. Advisory only: it rides beside
|
|
|
77
90
|
trigger this run → `next.suggested` is omitted entirely (an empty/absent
|
|
78
91
|
field, not an error).
|
|
79
92
|
|
|
80
|
-
**Envelope shape reminders (
|
|
93
|
+
**Envelope shape reminders (the script validates these before printing — mirrors
|
|
81
94
|
`packages/agentic-workflow-schema/envelope.schema.json`):**
|
|
82
95
|
|
|
83
96
|
- `blockers[].scope` ∈ `{"unit","run"}` — there is **no** `"code"` value;
|
|
@@ -101,6 +114,8 @@ field, not an error).
|
|
|
101
114
|
| `/triage-issue` | `strong` |
|
|
102
115
|
| `/product-audit` | `strong` |
|
|
103
116
|
| `/execute-phase` | `cheap` |
|
|
117
|
+
| `/review-spec` | `strong` |
|
|
118
|
+
| `/review-plan` | `strong` |
|
|
104
119
|
|
|
105
120
|
`next.tier` is read off this map by matching the resolved `next.recommended`
|
|
106
121
|
command's name (ignoring its arguments) — never guessed and never copied from
|
|
@@ -9,6 +9,15 @@ exact shape:
|
|
|
9
9
|
(swap `defined`/`/plan-feature` for `idea`/`/design-feature` on the design
|
|
10
10
|
side). The recommendation itself is unaffected — this only adds visibility.
|
|
11
11
|
|
|
12
|
+
**Shape vs. current output — read this before trusting the example below.** The
|
|
13
|
+
example is the envelope's **intended shape**, not everything the deterministic
|
|
14
|
+
producer emits today: per-unit `review`, `closure`, and `issues_born` (steps
|
|
15
|
+
10–12) and the `next.suggested[]` surface (step 13) are **not yet mechanized**
|
|
16
|
+
by `scripts/workflow-status.mjs` and do not appear in its output. A consumer
|
|
17
|
+
must read their absence as "not computed", never as `closure:
|
|
18
|
+
absent-legacy` or `issues_born.n: 0`; the owning note is
|
|
19
|
+
`ENVELOPE_CORE.md` §Not yet mechanized.
|
|
20
|
+
|
|
12
21
|
```json
|
|
13
22
|
{
|
|
14
23
|
"skill": "workflow-status",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
## Process (fixed sequence —
|
|
1
|
+
## Process (fixed sequence — executed by the script)
|
|
2
|
+
|
|
3
|
+
`bun scripts/workflow-status.mjs` (node when bun is absent — the repository's
|
|
4
|
+
runtime convention) executes this whole sequence (git/forge
|
|
5
|
+
collection, urgency labels-only scan, roadmap + fix-index parse, dependency
|
|
6
|
+
closure, readiness + step-6a receipt sensing, phase progress, review-mark
|
|
7
|
+
currency, fix-now fold projection) and prints the fixed Envelope v2 JSON. The
|
|
8
|
+
numbered blocks below state the semantics the script implements; the model reads
|
|
9
|
+
the JSON and interprets it.
|
|
2
10
|
|
|
3
11
|
### Normalized Repository State
|
|
4
12
|
|
|
@@ -12,14 +20,6 @@ readiness classification, if the ledger is missing, `draft`, `contradicted`, or
|
|
|
12
20
|
sensor remains read-only; it never edits or resolves the ledger. Conflicting
|
|
13
21
|
live evidence against a frozen ledger remains a contradiction candidate.
|
|
14
22
|
|
|
15
|
-
1. **Git state.** `git branch --show-current`, `git status --porcelain`,
|
|
16
|
-
`git fetch` + `git status -sb`. A dirty tree or unpushed branch is reported
|
|
17
|
-
as-is (a `workflow`-kind observation in `detail`), never cleaned up.
|
|
18
|
-
2. **Forge state.** List open + recently merged PRs and open issues with the
|
|
19
|
-
declared forge CLI (examples use `gh`):
|
|
20
|
-
`gh pr list --state open --json number,title,headRefName,url,statusCheckRollup`,
|
|
21
|
-
`gh pr list --state merged --limit 20 --json number,headRefName`,
|
|
22
|
-
`gh issue list --state open --json number,title,labels`.
|
|
23
23
|
3. **Urgency labels (`detail.urgent`) — labels-only, presence-only, never
|
|
24
24
|
decides.** Reuse the open-issue list from step 2 (`gh issue list --json
|
|
25
25
|
labels` — the JSON labels array already carried by that call); no separate
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
persisted marker, see `#76`") — never infer `true`/`false` from
|
|
29
29
|
absence of evidence.
|
|
30
30
|
11. **Per-unit closure state (`detail.*.closure`).** `{state}` ∈
|
|
31
|
-
`present | absent-legacy | blocked` — reuse `audit-pr`'s own
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
`present | absent-legacy | blocked` — reuse `audit-pr`'s own check
|
|
32
|
+
verbatim (`skills/audit-pr/SKILL.md`, "Closure integrity"): grep the SPEC
|
|
33
|
+
for a `Capability closure` heading.
|
|
34
34
|
Fix-governed unit → `n/a` (fix SPECs carry no closure block by design,
|
|
35
35
|
same carve-out `audit-pr` applies). Feature SPEC, block absent →
|
|
36
36
|
`absent-legacy`. Feature SPEC, block present with any blank row or a
|
|
37
37
|
resolved non-`n/a` row unmapped to an acceptance criterion → `blocked`.
|
|
38
38
|
Feature SPEC, block present and every row filled or `n/a`-justified and
|
|
39
|
-
mapped → `present`. Single-sourced: never re-derive
|
|
40
|
-
|
|
39
|
+
mapped → `present`. Single-sourced: never re-derive it — re-run
|
|
40
|
+
`audit-pr`'s grep.
|
|
41
41
|
12. **Per-unit descope provenance (`detail.*.issues_born`).**
|
|
42
42
|
`{n, with_descope_amendment}` — reuse `audit-pr`'s scope-bleed gate
|
|
43
43
|
detection verbatim (`skills/audit-pr/SKILL.md` "Scope integrity
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
slug/number text match `audit-pr` itself defines — **never** an issue's
|
|
51
51
|
free-text body beyond that defined match (injection-safety, mirrors
|
|
52
52
|
`detail.urgent`'s labels-only discipline).
|
|
53
|
+
|
|
54
|
+
*Steps 10–12's fields are **not yet emitted** — see `ENVELOPE_CORE.md` §Not yet
|
|
55
|
+
mechanized.*
|
|
53
56
|
13. **`next.suggested[]` — single-sourced trigger surface.** One entry per
|
|
54
57
|
**fired** trigger the driver can act on now, `{command, trigger,
|
|
55
58
|
source_skill}` — the `trigger` string **quotes**, never paraphrases, the
|