@gtrabanco/pi-agentic-workflow 0.15.2 → 0.16.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 (63) hide show
  1. package/README.md +49 -0
  2. package/dist/conductor/loop.js +23 -1
  3. package/dist/conductor/types.d.ts +6 -0
  4. package/dist/config/defaults.js +11 -0
  5. package/dist/config/merge.js +93 -1
  6. package/dist/config/profiles.d.ts +36 -0
  7. package/dist/config/profiles.js +102 -0
  8. package/dist/config/recommended.d.ts +14 -0
  9. package/dist/config/recommended.js +28 -0
  10. package/dist/config/schema.js +159 -3
  11. package/dist/config/types.d.ts +52 -0
  12. package/dist/config/types.js +10 -0
  13. package/dist/extension/conductor-command.d.ts +2 -5
  14. package/dist/extension/conductor-command.js +18 -6
  15. package/dist/extension/factory.d.ts +2 -0
  16. package/dist/extension/factory.js +10 -3
  17. package/dist/routing/dispatch.d.ts +9 -2
  18. package/dist/routing/dispatch.js +115 -46
  19. package/dist/routing/state.d.ts +26 -0
  20. package/dist/routing/state.js +67 -1
  21. package/dist/routing/types.d.ts +7 -0
  22. package/dist/settings/console.d.ts +7 -0
  23. package/dist/settings/console.js +214 -35
  24. package/dist/settings/view.d.ts +3 -1
  25. package/dist/settings/view.js +8 -1
  26. package/package.json +1 -1
  27. package/skills/audit-docs/SKILL.md +3 -3
  28. package/skills/audit-pr/SKILL.md +8 -8
  29. package/skills/audit-pr/references/01_MERGE_GATES.md +3 -3
  30. package/skills/audit-pr/references/02_CLOSURE_AND_SCOPE_GATES.md +4 -5
  31. package/skills/audit-pr/references/03_AUDIT_PROCESS.md +1 -1
  32. package/skills/audit-pr/references/04_VERDICT.md +3 -3
  33. package/skills/audit-pr/references/05_ROUTING_AND_GUARDRAILS.md +2 -2
  34. package/skills/discover-repository-state/SKILL.md +3 -4
  35. package/skills/execute-phase/SKILL.md +1 -1
  36. package/skills/execute-phase/references/DESCOPE.md +1 -1
  37. package/skills/fold-findings/SKILL.md +8 -8
  38. package/skills/fold-findings/references/FOLD_PROCESS.md +6 -5
  39. package/skills/orchestration-envelope/SKILL.md +4 -3
  40. package/skills/phase-contract/SKILL.md +3 -4
  41. package/skills/pre-execution-review/SKILL.md +10 -7
  42. package/skills/pre-execution-review/references/POLICY.md +17 -8
  43. package/skills/product-audit/SKILL.md +12 -13
  44. package/skills/product-audit/references/AUDIT_PROCESS.md +1 -1
  45. package/skills/resolve-repository-state/SKILL.md +2 -3
  46. package/skills/review-change/SKILL.md +3 -3
  47. package/skills/review-change/references/ADVERSARIAL_RECOMMENDATION.md +1 -1
  48. package/skills/review-change/references/ADVERSARIAL_SETUP.md +1 -1
  49. package/skills/review-change/references/OUTPUT_AND_GUARDRAILS.md +5 -5
  50. package/skills/review-change/references/PERSIST_AND_DECIDE.md +7 -6
  51. package/skills/review-implementation/SKILL.md +1 -1
  52. package/skills/review-implementation/references/CLASSIFY.md +13 -11
  53. package/skills/triage-issue/SKILL.md +11 -10
  54. package/skills/triage-issue/references/ISSUE_PROCESS.md +11 -10
  55. package/skills/triage-issue/references/LABELS.md +3 -3
  56. package/skills/triage-issue/references/REVIEW_FINDING_PROCESS.md +6 -6
  57. package/skills/unit-lane/SKILL.md +24 -9
  58. package/skills/workflow-status/SKILL.md +12 -13
  59. package/skills/workflow-status/references/CRASH_RECOVERY.md +6 -6
  60. package/skills/workflow-status/references/ENVELOPE_CORE.md +1 -4
  61. package/skills/workflow-status/references/ENVELOPE_FIELDS.md +7 -7
  62. package/skills/workflow-status/references/PRE_EXECUTION.md +5 -0
  63. package/skills/workflow-status/references/SENSOR_SIGNALS.md +1 -1
@@ -7,6 +7,11 @@
7
7
  // a scope whose file does not parse cannot be overwritten (that file is evidence
8
8
  // the operator must fix, not noise to clobber), and the project scope is refused
9
9
  // while the project is untrusted — the same gate the loader applies (AC13).
10
+ //
11
+ // P5 (feature 63): the flow is scope → profile → routes. Every command operates
12
+ // on the active profile's route (for "default" this is top-level; for a named
13
+ // profile it is `profiles.<name>`). The profile defaults to "default", so
14
+ // existing tests that never answer a profile prompt keep working unchanged.
10
15
  import { loadConfig, configFilePaths } from "../config/load.js";
11
16
  import { effectiveRoute } from "../config/merge.js";
12
17
  import { parseConfigFile, parseModelReference } from "../config/schema.js";
@@ -32,6 +37,13 @@ export const prompts = {
32
37
  clearOverride: "Clear a command override",
33
38
  bulkApply: "Apply one route to several commands",
34
39
  bulkClear: "Clear several overrides",
40
+ switchProfile: "Switch the profile being edited",
41
+ rotateProfile: "Rotate the active profile",
42
+ toggleRecommended: "Toggle recommended models",
43
+ profile: "Which profile?",
44
+ newProfile: "Name the new profile",
45
+ profileBuiltIn: (name) => `The "${name}" profile is built into the package and cannot be edited — create a profile to override it.`,
46
+ profileNamed: (name) => `Editing profile "${name}"`,
35
47
  addAnother: "Add another?",
36
48
  policy: "Set the unavailable-route policy",
37
49
  settle: "Set the post-command model keep/restore policy",
@@ -57,23 +69,27 @@ const INHERIT = "inherit";
57
69
  export async function runSettingsConsole(deps) {
58
70
  const paths = configFilePaths(deps.agentDir, deps.cwd);
59
71
  const merged = loadConfig({ agentDir: deps.agentDir, cwd: deps.cwd, projectTrusted: deps.projectTrusted, readFile: deps.readFile });
60
- deps.ui.notify(renderMergedConfig(merged, deps.commands).join("\n"), "info");
61
- /** The value in force for a target (the merged route, or the default when the target is "the default route"). */
62
- const currentFor = (target) => effectiveRoute(merged.config, target);
72
+ deps.ui.notify(renderMergedConfig(merged, deps.commands, merged.config.profiles?.nan !== undefined || (deps.models ?? []).some((m) => m.startsWith("nan/"))
73
+ ? { providerAvailable: (provider) => (deps.models ?? []).some((m) => m.startsWith(`${provider}/`)) }
74
+ : undefined).join("\n"), "info");
63
75
  const opened = await openAScope(deps, paths.global, paths.project);
64
76
  if (!opened)
65
77
  return { status: "cancelled", edited: false };
66
78
  const { scope, path, original } = opened;
67
79
  let draft = original;
80
+ let active = "default";
68
81
  for (;;) {
69
82
  // Only offered while a turn actually holds the latch: an option that does
70
83
  // nothing is how a console becomes noise.
71
84
  const choice = await deps.ui.select(prompts.menu, [
85
+ prompts.switchProfile,
72
86
  prompts.setDefaultRoute,
73
87
  prompts.setOverride,
74
88
  prompts.clearOverride,
75
89
  prompts.bulkApply,
76
90
  prompts.bulkClear,
91
+ prompts.rotateProfile,
92
+ prompts.toggleRecommended,
77
93
  prompts.policy,
78
94
  prompts.settle,
79
95
  ...(deps.routing?.inFlight() ? [prompts.undoInFlight] : []),
@@ -88,57 +104,172 @@ export async function runSettingsConsole(deps) {
88
104
  if (choice === undefined || choice === prompts.cancel) {
89
105
  return { status: "cancelled", edited: dirty(draft, original) ? await discard(deps.ui) : false };
90
106
  }
107
+ // Profile: switch to another profile (scope → profile → routes)
108
+ if (choice === prompts.switchProfile) {
109
+ const picked = await pickProfile(deps, draft, { allowNew: true });
110
+ if (picked === undefined)
111
+ continue;
112
+ // P5: the built-in "nan" profile cannot be edited — create a profile
113
+ // to override it.
114
+ if (picked === "nan" && draft.profiles?.nan === undefined) {
115
+ deps.ui.notify(prompts.profileBuiltIn("nan"), "warning");
116
+ continue;
117
+ }
118
+ if (picked !== "default" && !Object.prototype.hasOwnProperty.call(draft.profiles ?? {}, picked)) {
119
+ draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [picked]: {} } };
120
+ }
121
+ active = picked;
122
+ deps.ui.notify(prompts.profileNamed(active), "info");
123
+ continue;
124
+ }
125
+ // Rotate: move a profile to the front of profileOrder, clear demotion
126
+ if (choice === prompts.rotateProfile) {
127
+ const picked = await pickProfile(deps, draft, { allowNew: false });
128
+ if (picked === undefined)
129
+ continue;
130
+ const base = draft.profileOrder && draft.profileOrder.length > 0 ? draft.profileOrder : ["default"];
131
+ draft = { ...draft, profileOrder: [picked, ...base.filter((n) => n !== picked)] };
132
+ deps.routing?.clearProfileDemotion?.();
133
+ continue;
134
+ }
135
+ // Toggle recommended models
136
+ if (choice === prompts.toggleRecommended) {
137
+ const current = draft.recommendedModels ?? merged.config.recommendedModels;
138
+ draft = { ...draft, recommendedModels: !current };
139
+ continue;
140
+ }
141
+ // Set default route on the active profile
91
142
  if (choice === prompts.setDefaultRoute) {
92
- const edited = await editRoute(deps, DEFAULT_ROUTE, currentFor(DEFAULT_ROUTE));
93
- if (edited)
94
- draft = { ...draft, default: edited };
143
+ const target = active === "default" ? DEFAULT_ROUTE : `profile ${active} default`;
144
+ const routeFile = active === "default"
145
+ ? (draft.default ?? {})
146
+ : (draft.profiles?.[active]?.default ?? {});
147
+ const currentRoute = {
148
+ model: routeFile.model ?? "inherit",
149
+ thinking: routeFile.thinking ?? "inherit",
150
+ };
151
+ const edited = await editRoute(deps, target, currentRoute);
152
+ if (edited) {
153
+ if (active === "default") {
154
+ draft = { ...draft, default: edited };
155
+ }
156
+ else {
157
+ draft = {
158
+ ...draft,
159
+ profiles: { ...(draft.profiles ?? {}), [active]: { ...(draft.profiles?.[active] ?? {}), default: edited } },
160
+ };
161
+ }
162
+ }
95
163
  continue;
96
164
  }
165
+ // Set override on active profile's commands
97
166
  if (choice === prompts.setOverride) {
98
- const name = await pickCommand(deps, commandChoices(deps, draft));
167
+ // Use the full command registry so operators can add new overrides (AC4/OB-3).
168
+ const cmdNames = [...new Set([...deps.commands, ...Object.keys(draft.commands ?? {})])];
169
+ const name = await pickCommand(deps, cmdNames);
99
170
  if (name === undefined)
100
171
  continue;
101
- const route = await editRoute(deps, name, currentFor(name));
102
- if (route)
103
- draft = { ...draft, commands: { ...draft.commands, [name]: route } };
172
+ // The initial value comes from the merged config so the picker shows the
173
+ // value in force, matching the original `currentFor(name)` behavior.
174
+ const effective = effectiveRoute(merged.config, name);
175
+ const routeFile = active === "default"
176
+ ? (draft.commands?.[name] ?? {})
177
+ : (draft.profiles?.[active]?.commands?.[name] ?? {});
178
+ const currentRoute = {
179
+ model: typeof routeFile?.model !== "undefined" ? routeFile.model : effective.model,
180
+ thinking: typeof routeFile?.thinking !== "undefined" ? routeFile.thinking : effective.thinking,
181
+ };
182
+ const route = await editRoute(deps, name, currentRoute);
183
+ if (route) {
184
+ if (active === "default") {
185
+ draft = { ...draft, commands: { ...draft.commands, [name]: route } };
186
+ }
187
+ else {
188
+ draft = {
189
+ ...draft,
190
+ profiles: {
191
+ ...(draft.profiles ?? {}),
192
+ [active]: { ...(draft.profiles?.[active] ?? {}), commands: { ...(draft.profiles?.[active]?.commands ?? {}), [name]: route } },
193
+ },
194
+ };
195
+ }
196
+ }
104
197
  continue;
105
198
  }
199
+ // Clear override on active profile's commands
106
200
  if (choice === prompts.clearOverride) {
107
- const name = await pickCommand(deps, Object.keys(draft.commands ?? {}));
201
+ const cmdNames = Object.keys(profileOf(draft, active).commands ?? {});
202
+ const name = await pickCommand(deps, cmdNames);
108
203
  if (name === undefined)
109
204
  continue;
110
- const rest = { ...draft.commands };
111
- delete rest[name];
112
- draft = { ...draft, commands: rest };
205
+ if (active === "default") {
206
+ const rest = { ...draft.commands };
207
+ delete rest[name];
208
+ draft = { ...draft, commands: rest };
209
+ }
210
+ else {
211
+ const profile = draft.profiles?.[active] ?? {};
212
+ const rest = { ...(profile.commands ?? {}) };
213
+ delete rest[name];
214
+ draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [active]: { ...profile, commands: rest } } };
215
+ }
113
216
  continue;
114
217
  }
218
+ // Bulk apply on active profile's commands
115
219
  if (choice === prompts.bulkApply) {
116
- // One field pass (model via the chain builder + thinking), applied to every
117
- // selected command. A reference missing from the live registry warns per
118
- // command but never blocks the write (OB-4 — dispatch's probe stays the
119
- // authoritative availability gate).
120
- const selected = await pickCommandsMulti(deps, commandChoices(deps, draft));
220
+ // Use the full command registry so operators can apply to new commands (AC4/OB-3).
221
+ const cmdNames = [...new Set([...deps.commands, ...Object.keys(draft.commands ?? {})])];
222
+ const selected = await pickCommandsMulti(deps, cmdNames);
121
223
  if (selected === undefined || selected.length === 0)
122
224
  continue;
123
- const route = await editRoute(deps, selected[0], currentFor(selected[0]));
225
+ const routeFile = active === "default"
226
+ ? (draft.commands?.[selected[0]] ?? {})
227
+ : (draft.profiles?.[active]?.commands?.[selected[0]] ?? {});
228
+ const currentRoute = {
229
+ model: routeFile.model ?? "inherit",
230
+ thinking: routeFile.thinking ?? "inherit",
231
+ };
232
+ const route = await editRoute(deps, selected[0], currentRoute);
124
233
  if (route === undefined)
125
234
  continue;
126
- const nextCommands = { ...draft.commands };
127
- for (const name of selected) {
128
- nextCommands[name] = route;
129
- warnMissingModel(deps, name, route.model ?? INHERIT);
235
+ if (active === "default") {
236
+ const nextCommands = { ...draft.commands };
237
+ for (const name of selected) {
238
+ nextCommands[name] = route;
239
+ warnMissingModel(deps, name, route.model ?? INHERIT);
240
+ }
241
+ draft = { ...draft, commands: nextCommands };
242
+ }
243
+ else {
244
+ const profile = draft.profiles?.[active] ?? {};
245
+ const nextCommands = { ...profile.commands };
246
+ for (const name of selected) {
247
+ nextCommands[name] = route;
248
+ warnMissingModel(deps, name, route.model ?? INHERIT);
249
+ }
250
+ draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [active]: { ...profile, commands: nextCommands } } };
130
251
  }
131
- draft = { ...draft, commands: nextCommands };
132
252
  continue;
133
253
  }
254
+ // Bulk clear on active profile's commands
134
255
  if (choice === prompts.bulkClear) {
135
- const names = await pickCommandsMulti(deps, Object.keys(draft.commands ?? {}));
256
+ const cmdNames = Object.keys(profileOf(draft, active).commands ?? {});
257
+ const names = await pickCommandsMulti(deps, cmdNames);
136
258
  if (names === undefined || names.length === 0)
137
259
  continue;
138
- const rest = { ...draft.commands };
139
- for (const name of names)
140
- delete rest[name];
141
- draft = { ...draft, commands: rest };
260
+ if (active === "default") {
261
+ const rest = { ...draft.commands };
262
+ for (const name of names)
263
+ delete rest[name];
264
+ draft = { ...draft, commands: rest };
265
+ }
266
+ else {
267
+ const profile = draft.profiles?.[active] ?? {};
268
+ const rest = { ...(profile.commands ?? {}) };
269
+ for (const name of names)
270
+ delete rest[name];
271
+ draft = { ...draft, profiles: { ...(draft.profiles ?? {}), [active]: { ...profile, commands: rest } } };
272
+ }
142
273
  continue;
143
274
  }
144
275
  if (choice === prompts.policy) {
@@ -154,7 +285,7 @@ export async function runSettingsConsole(deps) {
154
285
  continue;
155
286
  }
156
287
  if (choice === prompts.save) {
157
- const saved = await saveScope(deps, scope, path, draft);
288
+ const saved = await saveScope(deps, scope, path, draft, merged);
158
289
  if (saved)
159
290
  return { status: "saved", scope, path, file: clean(draft) };
160
291
  continue;
@@ -167,6 +298,38 @@ export async function runSettingsConsole(deps) {
167
298
  * without being read, and a scope whose file does not parse is refused so the
168
299
  * operator's own file survives (AC10, AC13).
169
300
  */
301
+ // ---------------------------------------------------------------------------
302
+ // Profile helpers (P5, feature 63)
303
+ // ---------------------------------------------------------------------------
304
+ /** The profile file for a named profile or the default. */
305
+ function profileOf(draft, name) {
306
+ if (name === "default")
307
+ return { ...(draft.default ? { default: draft.default } : {}), ...(draft.commands ? { commands: draft.commands } : {}) };
308
+ return draft.profiles?.[name] ?? {};
309
+ }
310
+ /**
311
+ * Pick a profile from the list. `allowNew` adds "Name the new profile" to the
312
+ * options; the input must match a lowercase slug pattern.
313
+ */
314
+ async function pickProfile(deps, draft, { allowNew }) {
315
+ const names = ["default", ...Object.keys(draft.profiles ?? {}).filter((n) => n !== "default")];
316
+ // "nan" is always listed (built-in unless user-defined)
317
+ if (!names.includes("nan"))
318
+ names.push("nan");
319
+ const options = allowNew ? [...names, prompts.newProfile] : names;
320
+ const picked = await pagedSelect((title, list) => deps.ui.select(title, list), prompts.profile, options);
321
+ if (picked === undefined)
322
+ return undefined;
323
+ if (picked === prompts.newProfile) {
324
+ const name = await deps.ui.input(prompts.newProfile, "lowercase slug");
325
+ if (name === undefined || !/^[a-z0-9][a-z0-9._-]*$/u.test(name.trim())) {
326
+ deps.ui.notify("Rejected: a profile name must be a lowercase slug.", "error");
327
+ return undefined;
328
+ }
329
+ return name.trim();
330
+ }
331
+ return picked;
332
+ }
170
333
  async function openAScope(deps, globalPath, projectPath) {
171
334
  for (;;) {
172
335
  const label = await deps.ui.select(prompts.scope, [GLOBAL_LABEL, PROJECT_LABEL]);
@@ -439,11 +602,14 @@ function warnMissingModel(deps, target, model) {
439
602
  }
440
603
  }
441
604
  }
442
- function commandChoices(deps, draft) {
443
- return [...new Set([...deps.commands, ...Object.keys(draft.commands ?? {})])];
444
- }
445
- async function saveScope(deps, scope, path, draft) {
605
+ async function saveScope(deps, scope, path, draft, merged) {
446
606
  const file = clean(draft);
607
+ // P5: materialize recommendedModels when the nan provider is available but
608
+ // the key is absent — the save writes the effective value (AC11).
609
+ const nanAvailable = (deps.models ?? []).some((m) => m.startsWith("nan/"));
610
+ if (file.recommendedModels === undefined && nanAvailable) {
611
+ file.recommendedModels = merged.config.recommendedModels;
612
+ }
447
613
  const text = `${JSON.stringify(file, null, 2)}\n`;
448
614
  const problems = problemsFor(file, scope);
449
615
  if (problems.length > 0) {
@@ -477,6 +643,8 @@ function describeRouting(file) {
477
643
  const routes = [
478
644
  file.default ? `default: ${file.default.model ?? "inherit"} / ${file.default.thinking ?? "inherit"}` : "default: inherit / inherit",
479
645
  ...Object.entries(file.commands ?? {}).map(([name, route]) => `${name}: ${route.model ?? "inherit"} / ${route.thinking ?? "inherit"}`),
646
+ ...(file.profileOrder && file.profileOrder.length > 0 ? [`profiles: ${file.profileOrder.join(" → ")}`] : []),
647
+ ...(file.recommendedModels !== undefined ? [`recommended models: ${file.recommendedModels}`] : []),
480
648
  `unavailable: ${file.onUnavailableRoute ?? "stop"}`,
481
649
  `settle: ${file.onSettle ?? "keep"}`,
482
650
  ];
@@ -504,6 +672,17 @@ function clean(draft) {
504
672
  file.onUnavailableRoute = draft.onUnavailableRoute;
505
673
  if (draft.onSettle)
506
674
  file.onSettle = draft.onSettle;
675
+ if (draft.recommendedModels !== undefined)
676
+ file.recommendedModels = draft.recommendedModels;
677
+ if (draft.profiles && Object.keys(draft.profiles).length > 0) {
678
+ file.profiles = structuredClone(draft.profiles);
679
+ }
680
+ if (draft.profileOrder && draft.profileOrder.length > 0) {
681
+ file.profileOrder = [...draft.profileOrder];
682
+ }
683
+ if (draft.profileFallback && Object.keys(draft.profileFallback).length > 0) {
684
+ file.profileFallback = { ...draft.profileFallback };
685
+ }
507
686
  return file;
508
687
  }
509
688
  function parseFile(text, scope) {
@@ -8,4 +8,6 @@ export declare const DEFAULT_ROUTE = "the default route";
8
8
  */
9
9
  export declare function routePath(target: string): string;
10
10
  /** What the operator reads when the console opens. */
11
- export declare function renderMergedConfig(loaded: LoadedConfig, commands: readonly string[]): string[];
11
+ export declare function renderMergedConfig(loaded: LoadedConfig, commands: readonly string[], opts?: {
12
+ providerAvailable?: (provider: string) => boolean;
13
+ }): string[];
@@ -1,6 +1,7 @@
1
1
  // src/settings/view.ts
2
2
  // The settings console's display half (AC10, SPEC S4): what is in effect right
3
3
  // now, and which files the values came from.
4
+ import { effectiveProfileOrder } from "../config/profiles.js";
4
5
  /** The label for the unqualified route, so `default` is never mistaken for a command name. */
5
6
  export const DEFAULT_ROUTE = "the default route";
6
7
  /**
@@ -12,9 +13,15 @@ export function routePath(target) {
12
13
  return target === DEFAULT_ROUTE ? "$.default" : `$.commands.${target}`;
13
14
  }
14
15
  /** What the operator reads when the console opens. */
15
- export function renderMergedConfig(loaded, commands) {
16
+ export function renderMergedConfig(loaded, commands, opts) {
17
+ const order = effectiveProfileOrder(loaded.config, {
18
+ providerAvailable: opts?.providerAvailable ?? (() => false),
19
+ });
16
20
  return [
17
21
  "agentic-workflow routing — what each command runs on right now",
22
+ ` profiles: ${order.join(" → ")}`,
23
+ ` active profile: ${order[0] ?? "default"}`,
24
+ ` recommended models: ${loaded.config.recommendedModels ? "on" : "off"}${loaded.config.recommendedModels && opts?.providerAvailable?.("nan") ? " (built-in nan profile in the chain)" : ""}`,
18
25
  ...configLines(loaded.config, commands),
19
26
  ...problems(loaded.problems),
20
27
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtrabanco/pi-agentic-workflow",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "Pi package: canonical agentic-workflow skills, friendly slash commands, and per-command model routing.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: audit-docs
3
3
  user-invocable: true
4
- version: 2.1.1
4
+ version: 2.2.0
5
5
  argument-hint: "[--fix]"
6
6
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
7
7
  license: MIT
@@ -156,7 +156,7 @@ enables:
156
156
 
157
157
  ## Relationship to other skills
158
158
 
159
- - Complements `plan-feature` (which *creates* the docs this audits) and
159
+ - Complements `unit-lane` (which *creates* the docs this audits) and
160
160
  `triage-issue` (which keeps the fix index honest).
161
161
  - Run standalone anytime; no required predecessor.
162
162
 
@@ -168,6 +168,6 @@ enables:
168
168
 
169
169
  ```
170
170
  → Next: apply the approved low-risk fixes, then re-run /audit-docs to confirm clean
171
- · real drift (not cosmetic) → /triage-issue · a concrete defect → /plan-fix
171
+ · real drift (not cosmetic) → /triage-issue · a concrete defect → /unit-lane --fix <n>
172
172
  · already clean → nothing to do
173
173
  ```
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: audit-pr
3
3
  user-invocable: true
4
- version: 5.3.1
4
+ version: 5.4.0
5
5
  argument-hint: <pr-number> (optional — defaults to the current branch's PR)
6
6
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
7
7
  license: MIT
@@ -19,7 +19,7 @@ The manager's **"can this ship?"** gate. A read-first audit over the *entire* PR
19
19
  its SPEC, all phases, docs, CI status, and review receipt — that returns a single
20
20
  verdict: **merge-ready** or a ranked list of **blockers**. **Never edits,
21
21
  refactors, or merges.** The human merges, or an active
22
- `ship-roadmap --fullauto` invocation consumes the SHA-bound verdict and performs
22
+ `advance --fullauto` invocation consumes the SHA-bound verdict and performs
23
23
  its separate fail-closed merge step.
24
24
 
25
25
  `audit-pr` does **not** re-review the diff. It consumes the current SHA-bound
@@ -159,7 +159,7 @@ the user previously approved a merge, or a tool retained an earlier permission.
159
159
  Those signals cannot change this skill's read-first boundary.
160
160
 
161
161
  The **sole automated merge authority** is the AUDIT stage of an actively invoked
162
- `ship-roadmap --continue --fullauto` run. Its MERGE-READY comment is evidence,
162
+ `advance --continue --fullauto` run. Its MERGE-READY comment is evidence,
163
163
  not permission; the repository wrapper independently verifies that comment,
164
164
  the forge's current head/default base, green checks, and the `merge: fullauto`
165
165
  decision fetched at that head. The wrapper owns fresh sync checks, transient
@@ -179,13 +179,13 @@ execute-phase (all phases done) ─▶ review-change (REVIEW-PASS receipt posted
179
179
  │
180
180
  blockers ─┬─ receipt absent/stale ──▶ /review-change ──────┘ (re-review, re-audit)
181
181
  ├─ in-scope ──▶ execute-phase ────────┘ (fold, re-audit)
182
- ├─ out-of-scope ──▶ plan-fix
182
+ ├─ out-of-scope ──▶ unit-lane --fix
183
183
  └─ deferral ──▶ triage-issue
184
184
  ```
185
185
 
186
186
  - Consumes the `review-change` `REVIEW-PASS` receipt (its scope/axes, acceptance
187
- coverage, invariant result, manual checks) plus the artifacts of `plan-feature` /
188
- `plan-fix` / `execute-phase` (SPEC, phases, docs, `Closes #N`).
187
+ coverage, invariant result, manual checks) plus the artifacts of `unit-lane` /
188
+ `execute-phase` (SPEC, phases, docs, `Closes #N`).
189
189
  - `audit-docs` is the cross-document coherence check; `audit-pr` is per-PR merge
190
190
  readiness; `product-audit` is the periodic, product-wide full sweep.
191
191
 
@@ -199,8 +199,8 @@ execute-phase (all phases done) ─▶ review-change (REVIEW-PASS receipt posted
199
199
  reported **with the PR's full URL in the header**, each blocker routed, with the
200
200
  human's manual-verification list explicit.
201
201
  - On MERGE-READY the merge owner is explicit: a standalone audit hands the URL
202
- to the human; an active `ship-roadmap --fullauto` AUDIT stage receives the
202
+ to the human; an active `advance --fullauto` AUDIT stage receives the
203
203
  SHA-bound verdict and owns every later merge check.
204
204
  - The **closing `→ Next:` block is printed** (merge link → then the next unit via
205
- `/plan-feature --next` or `/triage-issue`; BLOCKED → the routed fix, then re-audit).
205
+ `/unit-lane <unit>` or `/triage-issue`; BLOCKED → the routed fix, then re-audit).
206
206
  - Nothing was edited, refactored, or merged.
@@ -6,14 +6,14 @@ A gate that can't be confirmed is a **blocker**, not a pass — never assume gre
6
6
  | Gate | What it means | Blocker when |
7
7
  |---|---|---|
8
8
  | **Acceptance coverage** | The receipt's acceptance-coverage field accounts for every SPEC acceptance criterion (review-change verified the mapping). This gate confirms the receipt *names* the criteria — it never remaps diff hunks to criteria itself. | The receipt omits a criterion the SPEC lists, or its coverage field is absent/generic. |
9
- | **All phases complete** | Feature: every phase in `PLAN.md`/`TASKS.md` is done and logged in `progress.md`. Fix: the SPEC is fully implemented. | Any unchecked task or unimplemented phase without an explicit, tracked deferral. |
9
+ | **All phases complete** | Feature: every task in the unit doc's `Tasks` section is done and logged in its `Progress log`. Fix: the SPEC is fully implemented. | Any unchecked task or unimplemented phase without an explicit, tracked deferral. |
10
10
  | **Scope integrity (creep)** | The PR implements the SPEC and no more; out-of-scope work was split out. | Undocumented scope creep, or in-scope work missing. |
11
11
  | **Docs updated** | Every "Affected docs" criterion is satisfied; per-phase docs (`progress`/`testing`/`known-issues`/`decisions`) reflect reality; the doc map still resolves. **Never merge with documentation still pending.** | A doc the map or SPEC requires is stale, missing, pending, or contradicts the code. |
12
- | **Traceability** | `Closes #N` is in the PR body when the work is issue-born (from `plan-feature-from-issue` or `plan-fix`); the roadmap/fix-index entry matches, is **still present** (removed only *after* merge, never before), and carries the linked PR reference (`done · [#<pr>](<pr-url>)`). | Issue-born work without `Closes #N`; a roadmap/index entry out of sync; the entry dropped before merge; or a `done` row without its PR link. |
12
+ | **Traceability** | `Closes #N` is in the PR body when the work is issue-born (from `/unit-lane --from-issue <n>` or `/unit-lane --fix <n>`); the roadmap/fix-index entry matches, is **still present** (removed only *after* merge, never before), and carries the linked PR reference (`done · [#<pr>](<pr-url>)`). | Issue-born work without `Closes #N`; a roadmap/index entry out of sync; the entry dropped before merge; or a `done` row without its PR link. |
13
13
  | **Verification gate / CI** | The project's gate passes — type-check, tests, build — and `statusCheckRollup` is green at the current head. | Any required check failing, pending, or absent where the project requires one. |
14
14
  | **Mergeability** | Branch is off the default base, independently mergeable (no conflicts), not stacked on another PR, not draft. | Wrong base, conflicts, stacked dependency, or still draft. |
15
15
  | **Review receipt** | A current `REVIEW-PASS` receipt is posted on the PR: the newest comment carrying `<!-- review-change:pass sha=<40-hex> contract=v1 -->` whose `sha` equals the PR's current head SHA. Its scope/axes, acceptance coverage, invariant result, and manual checks are the review evidence this audit consumes. | Receipt absent, or its marker `sha` predates the current head (any later commit voids it). The blocker routes to `/review-change`; never re-review the diff from here (AC 13). |
16
- | **Closure integrity** | The governing **feature** SPEC's capability closure was taken and recorded — `design-feature` was actually run, not bypassed. Fix-governed PRs: `n/a` (no closure block by design). | A present `Capability closure` block has a blank row, or a resolved non-`n/a` row with no matching acceptance criterion. |
16
+ | **Closure integrity** | The governing **feature** SPEC's capability closure was taken and recorded — the lane's `design` step was actually run, not bypassed. Fix-governed PRs: `n/a` (no closure block by design). | A present `Capability closure` block has a blank row, or a resolved non-`n/a` row with no matching acceptance criterion. |
17
17
  | **Scope integrity (descope)** | An issue born during this unit that maps to an unmet SPEC acceptance criterion or phase task has a matching, user-approved, dated `## Amendments` entry — descoped scope was recorded, not silently exported. Detection is two-path: a slug/issue-number text match, **or** an issue linked from an `## Amendments` row (`#89`) — either is sufficient to enumerate the issue, so a descoped issue with a generic title/body is not invisible to the gate. | An issue born since branch divergence that references this unit (by either detection path) maps to an unmet criterion/task with no matching `## Amendments` entry, or an `## Amendments` row that is undated, unapproved, or unlinked to an issue. |
18
18
  | **Architectural invariants** | The **receipt** records the invariant result review-change took against the optional project invariant document; this gate confirms that result is explicit (`pass | blocker | n-a`) and does not reclassify it (AC 13). No document → `n/a: no project invariants declared`, not a blocker. | The receipt's invariant result is absent/ambiguous, or a recorded `blocker`/`violates`/`introduces`/`changes` lacks the cited decision the review surfaced. |
19
19
  | **Hygiene — tree clean** | `git status --porcelain` is empty at the audited head: every artifact is committed, so nothing rides along to the next conversation (the field failure of issue #182). | Any modified or untracked path — the blocker names each one. |
@@ -17,15 +17,15 @@
17
17
  > decision was *taken and recorded*, never that UI/API surface exists. Never
18
18
  > push a blank row into inventing surface to pass this gate.
19
19
  > - **Feature SPEC, block absent** → the SPEC predates or bypassed
20
- > `design-feature`. Never a blocker — emit a dated **warning**, PR still
20
+ > `unit-lane`. Never a blocker — emit a dated **warning**, PR still
21
21
  > merges:
22
22
  > ```
23
23
  > design-debt: closure absent, SPEC predates the rule (dated <YYYY-MM-DD>)
24
24
  > ```
25
25
  > This warning is itself the **retrofit trigger**: the next unit of work that
26
- > touches this feature must fill the closure via `/design-feature <slug>`
26
+ > touches this feature must fill the closure via `/unit-lane <slug>`
27
27
  > (upsert — fills only the missing rows, destroys nothing recorded) *before*
28
- > that new work is planned. See `design-feature`'s upsert semantics for the
28
+ > that new work is planned. See `unit-lane`'s design-step upsert semantics for the
29
29
  > other half of this contract.
30
30
 
31
31
  > **`done` ≠ merge-ready.** A unit flips to `done` when its PR opens (built, not
@@ -96,8 +96,7 @@ verdict:
96
96
  feature unit — its named `— spec` parent re-derives the same way. Bound artifacts
97
97
  are frozen: new implementation-phase files are allowed, edits to a bound artifact
98
98
  are not. Stale, missing, wrong-stage **or impossible-timeline** lineage → **BLOCKED**,
99
- `→ Next: /review-plan <unit>` (or `/review-spec <unit>` when the parent is the
100
- broken link). A `SPEC-REVIEW-PASS` never satisfies the plan hop, and vice versa.
99
+ `→ Next: /unit-lane <unit>` (the lane's `review` step re-derives the artifact). A `SPEC-REVIEW-PASS` never satisfies the plan hop, and vice versa.
101
100
  2. **Obligations are closed.** Every row of the unit's obligation ledger is
102
101
  `verified` — with the validator that ran on this candidate — or an explicit
103
102
  `n/a: <reason>`. Any `planned`, `in-progress`, blank, or `deferred` row is
@@ -53,5 +53,5 @@
53
53
  SHA re-posts (newest wins) — and it **refuses to post for a BLOCKED verdict**,
54
54
  so the page never shows a stale green flag.
55
55
  8. **Report** — the verdict block below, always headed by the PR's full URL.
56
- In an active `ship-roadmap --fullauto` AUDIT stage, return the verdict to the
56
+ In an active `advance --fullauto` AUDIT stage, return the verdict to the
57
57
  conductor; never run its merge wrapper from this skill.
@@ -27,14 +27,14 @@ Before merge, a human should still verify:
27
27
  also print the closure bullet (a warning never blocks, so it co-occurs with a
28
28
  MERGE-READY verdict; the two lines print together, never one instead of the other):
29
29
  · MERGE-READY, standalone/manual audit → you merge: <full PR URL>, then
30
- /plan-feature --next (the next roadmap unit) or pick an issue with /triage-issue
31
- · MERGE-READY inside active ship-roadmap --fullauto → return this SHA-bound
30
+ /unit-lane <next-roadmap-unit> (the next roadmap unit) or pick an issue with /triage-issue
31
+ · MERGE-READY inside active advance --fullauto → return this SHA-bound
32
32
  verdict to the conductor; it runs the transient merge wrapper
33
33
  · BLOCKED → clear the top blocker (routed above), then re-run /audit-pr
34
34
  · Receipt absent/stale blocker → /review-change (re-review at the head), then
35
35
  re-run /audit-pr — never re-review from this audit
36
36
  · Closure warning (in addition to the verdict above) or a closure blocker →
37
- /design-feature <slug> — fills the missing closure rows (upsert, destroys
37
+ /unit-lane <slug> — re-runs the design step to fill the missing closure rows (upsert, destroys
38
38
  nothing) before further work on this feature is planned; re-run /audit-pr after
39
39
  · Scope-bleed blocker → record the missing `## Amendments` entry (user-approved,
40
40
  dated, linking the issue) in the governing SPEC, or re-classify the issue as
@@ -4,7 +4,7 @@
4
4
  re-run `audit-pr`) — never re-review from here.
5
5
  - **Incomplete in-scope work** → fold into this branch via `execute-phase`
6
6
  (the relevant phase or `--fix`); re-run `audit-pr` after.
7
- - **Out-of-scope defect surfaced** → `plan-fix` (new fix entry), not this PR.
7
+ - **Out-of-scope defect surfaced** → `/unit-lane --fix <n>` (new fix entry), not this PR.
8
8
  - **Deferred finding lacking a home** → `triage-issue` to file + classify it.
9
9
  - **Stale/missing docs** → update per the doc map (often a quick `execute-phase`
10
10
  doc commit), then re-audit.
@@ -15,7 +15,7 @@
15
15
  - **Read-first verdict. Never push, edit, refactor, or merge.** Its only forge
16
16
  write is the **MERGE-READY comment** (Process step 7 — idempotent,
17
17
  comment-only, never a commit tag). Fullauto merge execution belongs only to
18
- the active `ship-roadmap --fullauto` conductor.
18
+ the active `advance --fullauto` conductor.
19
19
  - **Never re-review the diff.** The `REVIEW-PASS` receipt is the review evidence;
20
20
  a missing/stale receipt is a blocker routed to `/review-change`. The audit does
21
21
  not compose `review-change`, rescan axes, judge test quality, remap diff hunks
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: discover-repository-state
3
3
  user-invocable: true
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  description: >
6
6
  Discover repository evidence and write a frozen Normalized Repository State.
7
7
  Produces verified repository evidence and keeps facts, decisions, planned
@@ -79,6 +79,5 @@ If a contradiction was recorded:
79
79
 
80
80
  Otherwise:
81
81
 
82
- → Next: /plan-feature <slug> — plan from frozen facts
83
- · implementation-ready feature → /execute-phase <NN>
84
- (planned is not executable: no current `PLAN-REVIEW-PASS` → /review-plan <NN>)
82
+ → Next: /unit-lane <slug> — triage and run the lane from frozen facts
83
+ · already-triaged unit → /execute-phase <NN>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: execute-phase
3
3
  user-invocable: true
4
- version: 5.0.0
4
+ version: 5.0.1
5
5
  argument-hint: <NN-slug> [P<k>|step] [--max-attempts N] [--force]
6
6
  allowed-tools: [Bash, Read, Edit, Write, MultiEdit]
7
7
  author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
@@ -42,4 +42,4 @@ from the triage catalog, not step descopes):
42
42
  `audit-pr`'s obligation-closure check all key off this same `## References` log
43
43
  — it is the single authoritative record of every descope, defined once here.
44
44
  Nothing else in the pre-execution route creates an issue: a missing or failed
45
- `PLAN-REVIEW-PASS` is closed by `/review-plan`, never by a tracker item.
45
+ the lane's `review` step closes it, never a tracker item.