@sabaiway/agent-workflow-kit 1.44.0 → 1.45.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/CHANGELOG.md +63 -0
- package/README.md +6 -3
- package/SKILL.md +9 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +87 -16
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +3 -0
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +4 -2
- package/bridges/antigravity-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +7 -7
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +3 -3
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +4 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +93 -22
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +4 -0
- package/bridges/codex-cli-bridge/capability.json +4 -3
- package/bridges/codex-cli-bridge/references/driving-codex.md +5 -4
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +5 -4
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/bootstrap.md +1 -1
- package/references/modes/bridge-settings.md +1 -1
- package/references/modes/grounding.md +8 -7
- package/references/modes/recommendations.md +14 -0
- package/references/modes/review-state.md +1 -1
- package/references/modes/sandbox-masks.md +15 -0
- package/references/modes/upgrade.md +10 -7
- package/references/modes/velocity.md +13 -2
- package/references/shared/composition-handoff.md +21 -16
- package/references/shared/report-footer.md +5 -1
- package/references/templates/AGENTS.md +2 -1
- package/references/templates/autonomy.json +3 -0
- package/tools/autonomy-config.mjs +13 -3
- package/tools/commands.mjs +15 -1
- package/tools/delegation.mjs +9 -5
- package/tools/detect-backends.mjs +2 -2
- package/tools/doc-parity.mjs +8 -0
- package/tools/engine-source.mjs +6 -0
- package/tools/family-registry.mjs +21 -7
- package/tools/fold-completeness-run.mjs +5 -0
- package/tools/grounding.mjs +139 -22
- package/tools/inject-methodology.mjs +117 -28
- package/tools/manifest/schema.md +20 -0
- package/tools/manifest/validate.mjs +26 -0
- package/tools/procedures.mjs +61 -8
- package/tools/recipes.mjs +94 -15
- package/tools/recommendations.mjs +469 -0
- package/tools/review-ledger-write.mjs +14 -0
- package/tools/review-ledger.mjs +3 -2
- package/tools/review-state.mjs +101 -24
- package/tools/run-gates.mjs +3 -0
- package/tools/sandbox-masks.mjs +370 -0
- package/tools/set-recipe.mjs +13 -1
- package/tools/velocity-profile.mjs +228 -22
package/tools/procedures.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import { plansInFlight, PLANS_REL } from './review-state.mjs';
|
|
|
36
36
|
// (an import-split test pins it). CONFIG_REL is RE-EXPORTED so existing importers (procedures.test.mjs,
|
|
37
37
|
// historically) keep their import site working.
|
|
38
38
|
import { CONFIG_REL, fail, loadConfig, assertSlotRecipe } from './orchestration-config.mjs';
|
|
39
|
+
import { AUTONOMY_REL, loadAutonomy, resolveAutonomy, isSparseSeedConfig } from './autonomy-config.mjs';
|
|
39
40
|
export { CONFIG_REL };
|
|
40
41
|
|
|
41
42
|
// ── argument + override parsing (usage errors → exit 2) ─────────────────────────────
|
|
@@ -224,7 +225,10 @@ const reviewLoopAdvice = (slots, activity) =>
|
|
|
224
225
|
// (the review-state plan-in-flight detector): exactly ONE plan in flight → render it populated;
|
|
225
226
|
// zero or several → the explicit `--plan <path>` placeholder + a one-line discovery caveat. The
|
|
226
227
|
// suggested --out lives OUTSIDE the repo (/tmp) — grounding.mjs refuses a non-scratch destination.
|
|
227
|
-
|
|
228
|
+
// Exported for the bridge-tier byte-parity pin (AD-044 Plan 4): the velocity tier seeds the
|
|
229
|
+
// grounding rule in EXACTLY this rendered spelling — `node "${GROUNDING_TOOL}"` — so seeded and
|
|
230
|
+
// rendered forms can never drift apart.
|
|
231
|
+
export const GROUNDING_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'grounding.mjs');
|
|
228
232
|
const GROUNDING_FACTS_OUT = '/tmp/review-facts.md';
|
|
229
233
|
const groundingPreStepAdvice = (activity, slots, plans) => {
|
|
230
234
|
if (!slots.some((s) => (s.backends ?? []).includes('agy-review'))) return [];
|
|
@@ -242,17 +246,42 @@ const groundingPreStepAdvice = (activity, slots, plans) => {
|
|
|
242
246
|
// Path arguments are double-quoted — a skill dir or plan name with a space must stay copy-pasteable.
|
|
243
247
|
const lines = [
|
|
244
248
|
'Grounding pre-step (agy is dispatched — assemble the verified facts BEFORE the review; grounding.mjs slices verbatim, judgment additions stay yours):',
|
|
245
|
-
` run: node "${GROUNDING_TOOL}" --constraints ${planArg} --out ${GROUNDING_FACTS_OUT}`,
|
|
249
|
+
` run: node "${GROUNDING_TOOL}" --constraints --autonomy ${planArg} --out ${GROUNDING_FACTS_OUT}`,
|
|
246
250
|
` then: ${reviewForm} --facts @${GROUNDING_FACTS_OUT}`,
|
|
247
251
|
];
|
|
248
252
|
if (plans.length === 0) {
|
|
249
|
-
lines.push(` ↳ plan discovery: no plan in flight under ${PLANS_REL} — substitute the plan file you are reviewing against, or drop --plan for constraints
|
|
253
|
+
lines.push(` ↳ plan discovery: no plan in flight under ${PLANS_REL} — substitute the plan file you are reviewing against, or drop --plan for constraints+autonomy facts.`);
|
|
250
254
|
} else if (plans.length > 1) {
|
|
251
255
|
lines.push(` ↳ plan discovery: ${plans.length} plans in flight under ${PLANS_REL} (${plans.join(', ')}) — populate --plan with the one under review.`);
|
|
252
256
|
}
|
|
253
257
|
return lines;
|
|
254
258
|
};
|
|
255
259
|
|
|
260
|
+
// The per-activity autonomy block (AD-044 Plan 4): the resolved level for THIS activity + what it
|
|
261
|
+
// implies, rendered from resolveAutonomy — never retyped constants. Read at session start beside
|
|
262
|
+
// the resolved recipes (the AGENTS.md autonomy pointer's read contract). A malformed policy
|
|
263
|
+
// surfaces LOUDLY here (the session-start read is exactly where "malformed → STOP, never guess"
|
|
264
|
+
// must be visible); an absent file states the computed-defaults origin honestly.
|
|
265
|
+
const autonomyAdvice = (activity, facts) => {
|
|
266
|
+
if (facts == null) return [];
|
|
267
|
+
if (facts.error) return [`Autonomy (${AUTONOMY_REL}): MALFORMED — ${facts.error} — STOP and fix the policy file, never guess around it.`];
|
|
268
|
+
const level = facts.activities?.[activity]?.autonomy;
|
|
269
|
+
if (!level) return [];
|
|
270
|
+
const origin = facts.source === 'none'
|
|
271
|
+
? `computed defaults — no ${AUTONOMY_REL}`
|
|
272
|
+
: facts.defaultsEquivalent
|
|
273
|
+
? `computed defaults — ${AUTONOMY_REL} is the sparse defaults-equivalent seed`
|
|
274
|
+
: `from ${facts.source}`;
|
|
275
|
+
const redlines = Object.entries(facts.redlines).map(([k, v]) => `${k}=${v}`).join(', ');
|
|
276
|
+
const implies = level === 'sandbox'
|
|
277
|
+
? 'the OS sandbox confines and auto-allows confined commands — work runs to the next checkpoint without per-command prompts'
|
|
278
|
+
: 'every non-allowlisted command prompts (the sandbox, where enabled, still confines)';
|
|
279
|
+
return [
|
|
280
|
+
`Autonomy for "${activity}" (${origin}): ${level} — ${implies}.`,
|
|
281
|
+
` red-lines (always): ${redlines} — commit/push/publish keep their maintainer asks regardless of level.`,
|
|
282
|
+
];
|
|
283
|
+
};
|
|
284
|
+
|
|
256
285
|
// The cost-lane advisory block (cost-tiered execution — orchestration.md §5 canon, paraphrased
|
|
257
286
|
// at the point of use like reviewLoopAdvice paraphrases §9/§4). Rendered UNCONDITIONALLY for
|
|
258
287
|
// every activity — the lanes route EVERY step, review-backed or not (unlike reviewLoopAdvice,
|
|
@@ -267,6 +296,7 @@ const costLanesAdvice = () => [
|
|
|
267
296
|
' • L2 subscription bridge (codex / agy) — reviews per the resolved recipe above, on frontier bridge models (quality-first).',
|
|
268
297
|
' • L3 frontier — judgment: plan/fold/synthesis, ADR/handover/changelog-entry wording, persuasive copy, go/no-go, real code.',
|
|
269
298
|
' • A step with no named guardrail does not move down a lane; red lines never move down (council review models · real code · memory/copy wording · the maintainer approval asks).',
|
|
299
|
+
' • Sandbox lanes (under an OS sandbox): the L0 surfaces are sandbox-safe — gates/ledger/state/fold checks, git reads, plain no-network tests; the bridge wrappers are genuinely unsandboxed (network); npm-cache-touching commands are COMMAND-SHAPE dependent — first try the sandbox-safe shape (cache under $TMPDIR, offline/notifier off). Move ONLY the failing command out of the sandbox, never its class; BATCH consecutive unsandboxed calls.',
|
|
270
300
|
];
|
|
271
301
|
|
|
272
302
|
// The verbatim per-backend DRIVING CONTRACT block (M-contract): the exact invocation descriptor(s),
|
|
@@ -297,7 +327,7 @@ const contractLines = ({ cmd, contract, settings }) => {
|
|
|
297
327
|
return lines;
|
|
298
328
|
};
|
|
299
329
|
|
|
300
|
-
const formatHuman = ({ activity, section, slots, warnings, plans }) => {
|
|
330
|
+
const formatHuman = ({ activity, section, slots, warnings, plans, autonomy }) => {
|
|
301
331
|
const lines = [
|
|
302
332
|
section,
|
|
303
333
|
'',
|
|
@@ -309,6 +339,8 @@ const formatHuman = ({ activity, section, slots, warnings, plans }) => {
|
|
|
309
339
|
if (s.reason) lines.push(` ↳ ${s.reason}`);
|
|
310
340
|
for (const c of s.contracts ?? []) lines.push(...contractLines(c));
|
|
311
341
|
}
|
|
342
|
+
const autonomyBlock = autonomyAdvice(activity, autonomy);
|
|
343
|
+
if (autonomyBlock.length) lines.push('', ...autonomyBlock);
|
|
312
344
|
const grounding = groundingPreStepAdvice(activity, slots, plans);
|
|
313
345
|
if (grounding.length) lines.push('', ...grounding);
|
|
314
346
|
const advice = reviewLoopAdvice(slots, activity);
|
|
@@ -321,7 +353,7 @@ const formatHuman = ({ activity, section, slots, warnings, plans }) => {
|
|
|
321
353
|
return lines.join('\n');
|
|
322
354
|
};
|
|
323
355
|
|
|
324
|
-
const buildJson = ({ activity, section, slots, configSource, warnings, plans }) => ({
|
|
356
|
+
const buildJson = ({ activity, section, slots, configSource, warnings, plans, autonomy }) => ({
|
|
325
357
|
activity,
|
|
326
358
|
section,
|
|
327
359
|
slots: Object.fromEntries(
|
|
@@ -334,6 +366,8 @@ const buildJson = ({ activity, section, slots, configSource, warnings, plans })
|
|
|
334
366
|
groundingPreStep: groundingPreStepAdvice(activity, slots, plans),
|
|
335
367
|
// ADDITIVE (cost-tiered execution): the unconditional cost-lane advisory, structured.
|
|
336
368
|
costLanes: costLanesAdvice(),
|
|
369
|
+
// ADDITIVE (AD-044 Plan 4): the per-activity autonomy block, structured (empty when unresolvable).
|
|
370
|
+
autonomy: autonomyAdvice(activity, autonomy),
|
|
337
371
|
configSource,
|
|
338
372
|
warnings,
|
|
339
373
|
});
|
|
@@ -354,7 +388,8 @@ ${CONFIG_REL} + the read-only backend detector, and prints both. A per-run
|
|
|
354
388
|
Read-only: never writes, never commits, never runs a subscription CLI.
|
|
355
389
|
|
|
356
390
|
Exit codes: 0 success (an unsatisfiable override degrades loudly, still 0);
|
|
357
|
-
2 usage (unknown activity / bad --override); 1 config or engine error
|
|
391
|
+
2 usage (unknown activity / bad --override); 1 config or engine error
|
|
392
|
+
(incl. a malformed ${AUTONOMY_REL} — the advisory still renders, the exit flips).`;
|
|
358
393
|
|
|
359
394
|
// ── main ───────────────────────────────────────────────────────────────────────────
|
|
360
395
|
|
|
@@ -387,9 +422,27 @@ export const main = (argv, ctx = {}) => {
|
|
|
387
422
|
const slots = resolveAllSlots({ activity, config, detection, overrides });
|
|
388
423
|
const warnings = [...detectWarnings, ...collectWarnings(slots)];
|
|
389
424
|
const plans = plansInFlight(cwd);
|
|
425
|
+
// The autonomy facts (AD-044 Plan 4): resolved levels + red-lines from the policy file. A
|
|
426
|
+
// malformed policy renders LOUDLY in the block AND flips the exit to 1 (config error) — the
|
|
427
|
+
// recipes and contracts still print, only the code changes.
|
|
428
|
+
const autonomy = (() => {
|
|
429
|
+
try {
|
|
430
|
+
const { config: autonomyConfig, source } = loadAutonomy(cwd, readFile, lstat);
|
|
431
|
+
const resolved = resolveAutonomy(autonomyConfig);
|
|
432
|
+
// Structural seed detection (shared predicate) — a declared-defaults policy reads as
|
|
433
|
+
// "from docs/ai/autonomy.json", never as the seed (codex, Segment B closing).
|
|
434
|
+
const defaultsEquivalent = source !== 'none' && isSparseSeedConfig(autonomyConfig);
|
|
435
|
+
return { source, defaultsEquivalent, ...resolved };
|
|
436
|
+
} catch (err) {
|
|
437
|
+
return { error: (err && err.message) || String(err) };
|
|
438
|
+
}
|
|
439
|
+
})();
|
|
390
440
|
const stdout = json
|
|
391
|
-
? JSON.stringify(buildJson({ activity, section, slots, configSource, warnings, plans }), null, 2)
|
|
392
|
-
: formatHuman({ activity, section, slots, warnings, plans });
|
|
441
|
+
? JSON.stringify(buildJson({ activity, section, slots, configSource, warnings, plans, autonomy }), null, 2)
|
|
442
|
+
: formatHuman({ activity, section, slots, warnings, plans, autonomy });
|
|
443
|
+
if (autonomy?.error) {
|
|
444
|
+
return { code: 1, stdout, stderr: `procedures: malformed ${AUTONOMY_REL} — ${autonomy.error}` };
|
|
445
|
+
}
|
|
393
446
|
return { code: 0, stdout, stderr: '' };
|
|
394
447
|
} catch (err) {
|
|
395
448
|
return { code: err.exitCode ?? 1, stdout: '', stderr: `procedures: ${err.message}` };
|
package/tools/recipes.mjs
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// orchestrator (the main agent) executes it via the bridge skills and always makes the single commit;
|
|
15
15
|
// a backend is advisory or delegated, never autonomous. Dependency-free, Node >= 18.
|
|
16
16
|
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
19
|
import { pathToFileURL } from 'node:url';
|
|
18
20
|
// The host-level bridge-settings snapshot (fact-only, best-effort). READ-ONLY core only — never the
|
|
19
21
|
// writer — so this read-only advisor never pulls in the atomic-write core.
|
|
@@ -296,7 +298,7 @@ export const resolveActivityRecipe = ({ config = {}, readiness = [], activity, s
|
|
|
296
298
|
// DISPLAY_ALIASES — the ONE alias table the recommendation clause already uses; ordering is the
|
|
297
299
|
// deterministic BACKEND_PRIORITY (codex before agy), independent of detection emission order.
|
|
298
300
|
// Always exactly one line: no part may carry a newline (pinned by tests).
|
|
299
|
-
export const composeStatusLine = (detection, recommendation, settings = null) => {
|
|
301
|
+
export const composeStatusLine = (detection, recommendation, settings = null, autonomy = null) => {
|
|
300
302
|
const backends = [...detection]
|
|
301
303
|
.sort((a, b) => priorityIndex(a.name) - priorityIndex(b.name))
|
|
302
304
|
.map((b) => `${DISPLAY_ALIASES[b.name] ?? b.name} ${b.readiness === READY ? '✓' : '✗'} ${b.readiness}`)
|
|
@@ -308,7 +310,71 @@ export const composeStatusLine = (detection, recommendation, settings = null) =>
|
|
|
308
310
|
const oneLine = (s) => String(s).replace(/[\s]+/g, ' ').trim();
|
|
309
311
|
const active = settings?.active ?? [];
|
|
310
312
|
const suffix = active.length ? ` · settings: ${active.map((s) => `${oneLine(s.key)}=${oneLine(s.value)}`).join(' · ')}` : '';
|
|
311
|
-
|
|
313
|
+
// The autonomy segment (AD-044 Plan 4): rendered ONLY when the caller supplies the computed
|
|
314
|
+
// facts (composeAutonomyFacts) — an omitted param keeps the line byte-identical (the settings-
|
|
315
|
+
// suffix precedent). Fact-only: effective per-activity levels + the render-sync state; an absent
|
|
316
|
+
// policy says "computed defaults" honestly; a malformed policy surfaces LOUDLY, never omitted.
|
|
317
|
+
const autonomySegment = autonomy == null ? '' : ` · autonomy: ${oneLine(formatAutonomySegment(autonomy))}`;
|
|
318
|
+
return base + suffix + autonomySegment;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// The one-segment autonomy renderer behind composeStatusLine's 4th param.
|
|
322
|
+
const formatAutonomySegment = (a) => {
|
|
323
|
+
if (a.error) return `MALFORMED policy — ${a.error}`;
|
|
324
|
+
const levels = Object.entries(a.activities ?? {}).map(([k, v]) => `${k}=${v.autonomy}`).join(', ');
|
|
325
|
+
const state = a.source === 'none'
|
|
326
|
+
? 'computed defaults — no policy file; declare with /agent-workflow-kit set-autonomy'
|
|
327
|
+
: a.defaultsEquivalent
|
|
328
|
+
? 'declared, defaults-equivalent — computed defaults apply; declare levels with /agent-workflow-kit set-autonomy'
|
|
329
|
+
: `declared; render ${a.renderState}`;
|
|
330
|
+
return `${levels} (${state})`;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// composeAutonomyFacts(cwd) → the fact object the status/active lines render (AD-044 Plan 4):
|
|
334
|
+
// { source, redlines, activities, renderState } or { error }. Lazy imports — autonomy-config
|
|
335
|
+
// statically imports THIS module (ACTIVITIES) and velocity-profile is heavy, so both load at call
|
|
336
|
+
// time only (the loadConfig precedent). Never throws: a malformed policy becomes { error } so the
|
|
337
|
+
// one-line surfaces render it loudly instead of dying.
|
|
338
|
+
// The policy lives at the PROJECT root; the report-footer invokes the paste surfaces without
|
|
339
|
+
// --cwd, so a subdirectory shell must still find it. Nearest-.git walk-up (dir or worktree file),
|
|
340
|
+
// fs-only — this advisor stays spawn-free; no repo found → the cwd itself (fixture behavior).
|
|
341
|
+
const projectTopOf = (cwd) => {
|
|
342
|
+
let dir = resolve(cwd);
|
|
343
|
+
for (;;) {
|
|
344
|
+
if (existsSync(join(dir, '.git'))) return dir;
|
|
345
|
+
const parent = dirname(dir);
|
|
346
|
+
if (parent === dir) return resolve(cwd);
|
|
347
|
+
dir = parent;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
export const composeAutonomyFacts = async (cwd) => {
|
|
352
|
+
try {
|
|
353
|
+
const root = projectTopOf(cwd);
|
|
354
|
+
const { loadAutonomy, resolveAutonomy, isSparseSeedConfig } = await import('./autonomy-config.mjs');
|
|
355
|
+
const { config, source } = loadAutonomy(root);
|
|
356
|
+
const resolved = resolveAutonomy(config);
|
|
357
|
+
if (source === 'none') return { source, redlines: resolved.redlines, activities: resolved.activities, renderState: null };
|
|
358
|
+
// The STRUCTURAL seed (_README only) is a fresh-deployment NORMAL — treating it as "declared"
|
|
359
|
+
// would report "render DRIFT" on every fresh upgrade (codex, Segment B). An EXPLICIT policy
|
|
360
|
+
// declaring the default values is a real declaration — its render state IS computed
|
|
361
|
+
// (structural detection, codex Segment B closing).
|
|
362
|
+
if (isSparseSeedConfig(config)) {
|
|
363
|
+
return { source, defaultsEquivalent: true, redlines: resolved.redlines, activities: resolved.activities, renderState: null };
|
|
364
|
+
}
|
|
365
|
+
let renderState;
|
|
366
|
+
try {
|
|
367
|
+
const { checkAutonomyProfile } = await import('./velocity-profile.mjs');
|
|
368
|
+
renderState = checkAutonomyProfile({ cwd: root }).inSync
|
|
369
|
+
? 'in sync'
|
|
370
|
+
: 'DRIFT — re-run the velocity --autonomy render';
|
|
371
|
+
} catch (err) {
|
|
372
|
+
renderState = `unchecked (${err?.message ?? err})`;
|
|
373
|
+
}
|
|
374
|
+
return { source, redlines: resolved.redlines, activities: resolved.activities, renderState };
|
|
375
|
+
} catch (err) {
|
|
376
|
+
return { error: err?.message ?? String(err) };
|
|
377
|
+
}
|
|
312
378
|
};
|
|
313
379
|
|
|
314
380
|
// ── the one-line ACTIVE-recipe line (the discovery line — configured, never recommended) ───────────
|
|
@@ -321,17 +387,21 @@ export const composeStatusLine = (detection, recommendation, settings = null) =>
|
|
|
321
387
|
// session-start checklist + the handover "Active recipes:" slot paste it verbatim, so no agent composes
|
|
322
388
|
// the configured-recipe facts by hand. `{ config, source }` is exactly what loadConfig returns (source
|
|
323
389
|
// 'none' when no config file exists). Always exactly one line: no part may carry a newline (pinned).
|
|
324
|
-
export const composeActiveRecipeLine = ({ config, source } = {}, detection) => {
|
|
390
|
+
export const composeActiveRecipeLine = ({ config, source } = {}, detection, autonomy = null) => {
|
|
325
391
|
const cells = [];
|
|
326
392
|
for (const [activity, def] of Object.entries(ACTIVITIES)) {
|
|
393
|
+
// The per-activity autonomy level rides each cell when the caller supplies the facts (AD-044
|
|
394
|
+
// Plan 4) — an omitted param keeps the line byte-identical (the composeStatusLine precedent).
|
|
395
|
+
const level = autonomy?.activities?.[activity]?.autonomy;
|
|
396
|
+
const auto = level ? `; autonomy ${level}` : '';
|
|
327
397
|
for (const slot of Object.keys(def.slots)) {
|
|
328
398
|
const r = resolveActivityRecipe({ config: config ?? {}, readiness: detection, activity, slot });
|
|
329
399
|
const { dispatch } = planRecipe(r.recipe, detection);
|
|
330
400
|
const wrappers = dispatch.map((d) => wrapperCmdFor(d.backend, d.role)).filter(Boolean);
|
|
331
401
|
const srcLabel = r.source === 'config' ? 'configured' : 'computed default';
|
|
332
402
|
const head = r.degradedFrom
|
|
333
|
-
? `${activity}.${slot} = ${r.degradedFrom} (${srcLabel}; degrades here to ${r.recipe} — ${r.reason})`
|
|
334
|
-
: `${activity}.${slot} = ${r.recipe} (${srcLabel})`;
|
|
403
|
+
? `${activity}.${slot} = ${r.degradedFrom} (${srcLabel}; degrades here to ${r.recipe} — ${r.reason}${auto})`
|
|
404
|
+
: `${activity}.${slot} = ${r.recipe} (${srcLabel}${auto})`;
|
|
335
405
|
const suffix =
|
|
336
406
|
wrappers.length >= 2
|
|
337
407
|
? ` → every backend every round: ${wrappers.join(' + ')}`
|
|
@@ -343,14 +413,19 @@ export const composeActiveRecipeLine = ({ config, source } = {}, detection) => {
|
|
|
343
413
|
}
|
|
344
414
|
const rec = recommendRecipe(detection);
|
|
345
415
|
const origin = source === 'none' || config == null ? 'no config file — computed defaults apply' : `from ${source}`;
|
|
346
|
-
|
|
416
|
+
// A MALFORMED policy must surface LOUDLY on this paste surface too — silently rendering cells
|
|
417
|
+
// without levels would hide the required STOP signal (codex R1, Segment B). One line always.
|
|
418
|
+
const malformed = autonomy?.error
|
|
419
|
+
? ` · autonomy: MALFORMED policy — ${String(autonomy.error).replace(/[\s]+/g, ' ').trim()}`
|
|
420
|
+
: '';
|
|
421
|
+
return `active recipes (${origin}): ${cells.join(' · ')} — the configured recipes above are what runs; readiness-recommended here: ${rec.recipe} (informational)${malformed}`;
|
|
347
422
|
};
|
|
348
423
|
|
|
349
424
|
// ── report + CLI ─────────────────────────────────────────────────────────────────
|
|
350
425
|
|
|
351
426
|
// The structured report behind `--json` — the recipes, the recommendation, a plan per recipe, and
|
|
352
427
|
// (additive) the pasteable one-line backend status composed from the same detection.
|
|
353
|
-
export const buildReport = (detection, settings = null) => {
|
|
428
|
+
export const buildReport = (detection, settings = null, autonomy = null) => {
|
|
354
429
|
const recommendation = recommendRecipe(detection);
|
|
355
430
|
return {
|
|
356
431
|
recipes: RECIPES.map(({ id, title, role, minBackends, degradesTo, summary }) => ({
|
|
@@ -363,7 +438,9 @@ export const buildReport = (detection, settings = null) => {
|
|
|
363
438
|
})),
|
|
364
439
|
recommendation,
|
|
365
440
|
plans: RECIPES.map((r) => planRecipe(r.id, detection)),
|
|
366
|
-
|
|
441
|
+
// The SAME autonomy facts the --status-line surface renders — the --json envelope must never
|
|
442
|
+
// expose a stale machine-composed status line (codex R1, Segment B).
|
|
443
|
+
statusLine: composeStatusLine(detection, recommendation, settings, autonomy),
|
|
367
444
|
};
|
|
368
445
|
};
|
|
369
446
|
|
|
@@ -404,10 +481,12 @@ Usage:
|
|
|
404
481
|
|
|
405
482
|
Lists the four recipes (Solo / Reviewed / Council / Delegated) and, from the read-only backend
|
|
406
483
|
detector, plans + recommends one for the current environment. --status-line prints exactly ONE
|
|
407
|
-
line — the machine-composed backend-status summary the bootstrap/upgrade reports paste verbatim
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
484
|
+
line — the machine-composed backend-status summary the bootstrap/upgrade reports paste verbatim
|
|
485
|
+
(incl. the per-activity autonomy segment: effective levels + render-sync state, honest
|
|
486
|
+
computed-defaults wording when no policy file exists). --active-line prints exactly ONE line — the
|
|
487
|
+
CONFIGURED recipe per activity/slot, resolved from the per-project docs/ai/orchestration.json (read
|
|
488
|
+
from the current directory) + live readiness, with degradation stated and each activity's autonomy
|
|
489
|
+
level beside its cells; paste it verbatim at session start / into the handover "Active recipes:" slot.
|
|
411
490
|
--json emits the structured report (incl. the same line as \`statusLine\`); the three are mutually
|
|
412
491
|
exclusive. Detection only — never writes, never commits, never runs a subscription CLI.`);
|
|
413
492
|
return;
|
|
@@ -428,13 +507,13 @@ exclusive. Detection only — never writes, never commits, never runs a subscrip
|
|
|
428
507
|
// so the config reader is pulled in at run time only — no static import cycle.
|
|
429
508
|
const { loadConfig } = await import('./orchestration-config.mjs');
|
|
430
509
|
try {
|
|
431
|
-
console.log(composeActiveRecipeLine(loadConfig(process.cwd()), detection));
|
|
510
|
+
console.log(composeActiveRecipeLine(loadConfig(process.cwd()), detection, await composeAutonomyFacts(process.cwd())));
|
|
432
511
|
} catch (err) {
|
|
433
512
|
console.error(`[agent-workflow-kit] ${err.message}`);
|
|
434
513
|
return err.exitCode ?? 1;
|
|
435
514
|
}
|
|
436
|
-
} else if (argv.includes('--status-line')) console.log(composeStatusLine(detection, recommendRecipe(detection), settingsSnapshot()));
|
|
437
|
-
else if (argv.includes('--json')) console.log(JSON.stringify(buildReport(detection, settingsSnapshot()), null, 2));
|
|
515
|
+
} else if (argv.includes('--status-line')) console.log(composeStatusLine(detection, recommendRecipe(detection), settingsSnapshot(), await composeAutonomyFacts(process.cwd())));
|
|
516
|
+
else if (argv.includes('--json')) console.log(JSON.stringify(buildReport(detection, settingsSnapshot(), await composeAutonomyFacts(process.cwd())), null, 2));
|
|
438
517
|
else console.log(formatRecipes(detection));
|
|
439
518
|
return 0;
|
|
440
519
|
};
|