@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.
- package/README.md +49 -0
- package/dist/conductor/loop.js +23 -1
- package/dist/conductor/types.d.ts +6 -0
- package/dist/config/defaults.js +11 -0
- package/dist/config/merge.js +93 -1
- package/dist/config/profiles.d.ts +36 -0
- package/dist/config/profiles.js +102 -0
- package/dist/config/recommended.d.ts +14 -0
- package/dist/config/recommended.js +28 -0
- package/dist/config/schema.js +159 -3
- package/dist/config/types.d.ts +52 -0
- package/dist/config/types.js +10 -0
- package/dist/extension/conductor-command.d.ts +2 -5
- package/dist/extension/conductor-command.js +18 -6
- package/dist/extension/factory.d.ts +2 -0
- package/dist/extension/factory.js +10 -3
- package/dist/routing/dispatch.d.ts +9 -2
- package/dist/routing/dispatch.js +115 -46
- package/dist/routing/state.d.ts +26 -0
- package/dist/routing/state.js +67 -1
- package/dist/routing/types.d.ts +7 -0
- package/dist/settings/console.d.ts +7 -0
- package/dist/settings/console.js +214 -35
- package/dist/settings/view.d.ts +3 -1
- package/dist/settings/view.js +8 -1
- package/package.json +1 -1
- package/skills/audit-docs/SKILL.md +3 -3
- package/skills/audit-pr/SKILL.md +8 -8
- package/skills/audit-pr/references/01_MERGE_GATES.md +3 -3
- package/skills/audit-pr/references/02_CLOSURE_AND_SCOPE_GATES.md +4 -5
- package/skills/audit-pr/references/03_AUDIT_PROCESS.md +1 -1
- package/skills/audit-pr/references/04_VERDICT.md +3 -3
- package/skills/audit-pr/references/05_ROUTING_AND_GUARDRAILS.md +2 -2
- package/skills/discover-repository-state/SKILL.md +3 -4
- package/skills/execute-phase/SKILL.md +1 -1
- package/skills/execute-phase/references/DESCOPE.md +1 -1
- package/skills/fold-findings/SKILL.md +8 -8
- package/skills/fold-findings/references/FOLD_PROCESS.md +6 -5
- package/skills/orchestration-envelope/SKILL.md +4 -3
- package/skills/phase-contract/SKILL.md +3 -4
- package/skills/pre-execution-review/SKILL.md +10 -7
- package/skills/pre-execution-review/references/POLICY.md +17 -8
- package/skills/product-audit/SKILL.md +12 -13
- package/skills/product-audit/references/AUDIT_PROCESS.md +1 -1
- package/skills/resolve-repository-state/SKILL.md +2 -3
- package/skills/review-change/SKILL.md +3 -3
- package/skills/review-change/references/ADVERSARIAL_RECOMMENDATION.md +1 -1
- package/skills/review-change/references/ADVERSARIAL_SETUP.md +1 -1
- package/skills/review-change/references/OUTPUT_AND_GUARDRAILS.md +5 -5
- package/skills/review-change/references/PERSIST_AND_DECIDE.md +7 -6
- package/skills/review-implementation/SKILL.md +1 -1
- package/skills/review-implementation/references/CLASSIFY.md +13 -11
- package/skills/triage-issue/SKILL.md +11 -10
- package/skills/triage-issue/references/ISSUE_PROCESS.md +11 -10
- package/skills/triage-issue/references/LABELS.md +3 -3
- package/skills/triage-issue/references/REVIEW_FINDING_PROCESS.md +6 -6
- package/skills/unit-lane/SKILL.md +24 -9
- package/skills/workflow-status/SKILL.md +12 -13
- package/skills/workflow-status/references/CRASH_RECOVERY.md +6 -6
- package/skills/workflow-status/references/ENVELOPE_CORE.md +1 -4
- package/skills/workflow-status/references/ENVELOPE_FIELDS.md +7 -7
- package/skills/workflow-status/references/PRE_EXECUTION.md +5 -0
- package/skills/workflow-status/references/SENSOR_SIGNALS.md +1 -1
package/dist/settings/console.js
CHANGED
|
@@ -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).
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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) {
|
package/dist/settings/view.d.ts
CHANGED
|
@@ -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[]
|
|
11
|
+
export declare function renderMergedConfig(loaded: LoadedConfig, commands: readonly string[], opts?: {
|
|
12
|
+
providerAvailable?: (provider: string) => boolean;
|
|
13
|
+
}): string[];
|
package/dist/settings/view.js
CHANGED
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: audit-docs
|
|
3
3
|
user-invocable: true
|
|
4
|
-
version: 2.
|
|
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 `
|
|
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 → /
|
|
171
|
+
· real drift (not cosmetic) → /triage-issue · a concrete defect → /unit-lane --fix <n>
|
|
172
172
|
· already clean → nothing to do
|
|
173
173
|
```
|
package/skills/audit-pr/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: audit-pr
|
|
3
3
|
user-invocable: true
|
|
4
|
-
version: 5.
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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 ──▶
|
|
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 `
|
|
188
|
-
`
|
|
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 `
|
|
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
|
-
`/
|
|
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
|
|
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
|
|
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
|
|
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
|
-
> `
|
|
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 `/
|
|
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 `
|
|
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: /
|
|
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 `
|
|
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
|
-
/
|
|
31
|
-
· MERGE-READY inside active
|
|
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
|
-
/
|
|
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** →
|
|
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 `
|
|
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.
|
|
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: /
|
|
83
|
-
·
|
|
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.
|
|
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
|
-
`
|
|
45
|
+
the lane's `review` step closes it, never a tracker item.
|