@ngockhoale/ukit 3.3.3 → 3.4.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/CHANGELOG.md +40 -0
- package/manifests/engineConformance.yaml +17 -1
- package/manifests/hostCapabilities.yaml +68 -1
- package/manifests/platform.full.yaml +138 -0
- package/manifests/platform.user.yaml +255 -3
- package/package.json +1 -1
- package/scripts/bench/subagent-orchestrator-corpus.mjs +275 -0
- package/scripts/bench/subagent-orchestrator-eval.mjs +565 -0
- package/scripts/probe/codex-capability-probe.mjs +169 -0
- package/src/cli/commands/doctor.js +168 -0
- package/src/cli/commands/indexTools.js +7 -0
- package/src/cli/commands/metrics.js +66 -2
- package/src/cli/commands/playbook.js +4 -4
- package/src/cli/commands/vm.js +49 -8
- package/src/core/agentRuntime/adapters.js +328 -27
- package/src/core/agentRuntime/artifacts.js +89 -0
- package/src/core/agentRuntime/context.js +345 -1
- package/src/core/agentRuntime/contract.js +296 -0
- package/src/core/agentRuntime/eventStore.js +176 -0
- package/src/core/agentRuntime/shadowRun.js +481 -5
- package/src/core/agentRuntime/telemetry.js +121 -0
- package/src/core/observability/emit/lifecycle.js +68 -1
- package/src/core/observability/emit/sessionBoot.js +393 -0
- package/src/core/observability/privacy/allowlist.js +10 -1
- package/src/core/observability/schema/registry.js +10 -0
- package/src/core/runtimeConfig.js +133 -0
- package/src/core/userPlaybooks.js +18 -3
- package/src/decision/registry.js +19 -0
- package/src/diagnostics/feedbackEvents.js +7 -4
- package/src/diagnostics/routeOutcomes.js +51 -6
- package/src/diagnostics/skillAccuracy.js +43 -3
- package/src/index/crossCheckMatrix.js +412 -0
- package/src/index/fixLoopEscalation.js +453 -0
- package/src/index/playbookRegistry.js +691 -0
- package/src/index/reviewPolicy.js +368 -0
- package/src/index/routeResolver.js +915 -0
- package/src/index/sessionHistoryExtractor.js +359 -0
- package/src/index/taskRouting.js +764 -581
- package/src/index/tierSelection.js +308 -0
- package/src/index/verificationMap.js +404 -0
- package/template_project/.claude/hooks/observability-emit.mjs +14 -0
- package/template_project/.claude/hooks/record-execution.mjs +19 -1
- package/template_project/.claude/hooks/skill-router.sh +691 -25
- package/template_project/.claude/hooks/verification-guard.sh +230 -1
- package/template_project/.claude/settings.json +2 -2
- package/template_project/.claude/ukit/index/cross-check-matrix.mjs +415 -0
- package/template_project/.claude/ukit/index/fix-loop-escalation.mjs +456 -0
- package/template_project/.claude/ukit/index/playbook-registry.mjs +690 -0
- package/template_project/.claude/ukit/index/review-panel-aggregate.mjs +20 -2
- package/template_project/.claude/ukit/index/review-policy.mjs +376 -0
- package/template_project/.claude/ukit/index/route-resolver.mjs +1059 -0
- package/template_project/.claude/ukit/index/route-task.mjs +1253 -846
- package/template_project/.claude/ukit/index/session-history-extractor.mjs +362 -0
- package/template_project/.claude/ukit/index/tier-selection.mjs +309 -0
- package/template_project/.claude/ukit/index/verification-map.mjs +403 -0
- package/template_project/.claude/ukit/index/worktree-sweep.mjs +195 -0
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +789 -11
- package/template_project/.claude/ukit/runtime/observability-emit.mjs +1102 -0
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +9 -1
- package/template_project/.claude/ukit/runtime/resumable-run.mjs +149 -5
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +323 -6
- package/template_project/.codex/README.md +8 -0
- package/template_project/.omp/hooks/pre/ukit-bridge.js +8 -1
- package/template_project/ukit/README.md +1 -1
- package/template_project/ukit/storage/config.json +20 -0
- package/template_user/playbooks/architecture-decision.md +28 -0
- package/template_user/playbooks/autonomous-run.md +43 -0
- package/template_user/playbooks/autopilot-full.md +59 -0
- package/template_user/playbooks/autopilot-stack.md +54 -0
- package/template_user/playbooks/babysit.md +39 -0
- package/template_user/playbooks/bug-fix.md +3 -1
- package/template_user/playbooks/{issue-implementation.md → feature-implementation.md} +4 -2
- package/template_user/playbooks/hillclimb.md +44 -0
- package/template_user/playbooks/investigation.md +21 -0
- package/template_user/playbooks/migration.md +21 -0
- package/template_user/playbooks/open-pr.md +48 -0
- package/template_user/playbooks/orchestrate.md +45 -0
- package/template_user/playbooks/performance.md +33 -0
- package/template_user/playbooks/prototype.md +28 -0
- package/template_user/playbooks/refactor.md +19 -0
- package/template_user/playbooks/release.md +28 -0
- package/template_user/playbooks/runtime-forensics.md +23 -0
- package/template_user/playbooks/session-pickup.md +31 -0
- package/template_user/playbooks/shipping.md +53 -0
- package/template_user/playbooks/skill-evaluation.md +48 -0
- package/template_user/playbooks/small-feature.md +20 -0
- package/template_user/playbooks/verification-map.json +153 -0
- package/template_user/playbooks/verification.md +22 -0
- package/template_user/playbooks/worktree-cleanup.md +37 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// codex-capability-probe.mjs — TASK-001 / BL-001 + BL-002 scripted probe.
|
|
3
|
+
//
|
|
4
|
+
// Probes the installed `.codex/` surface of a UKit workspace and records what
|
|
5
|
+
// can be shown WITHOUT a live Codex CLI session:
|
|
6
|
+
// 1. codex CLI presence on PATH (live-session gate).
|
|
7
|
+
// 2. `.codex/` surface inventory (symlinks + files shipped by install).
|
|
8
|
+
// 3. `.codex/agents` resolution into real agent definitions.
|
|
9
|
+
// 4. Review-lane script reachability: `review-verdict.mjs` and
|
|
10
|
+
// `review-panel-aggregate.mjs` invoked THROUGH the `.codex/ukit` symlink
|
|
11
|
+
// (the exact path instruction-mediated Codex Bash would use).
|
|
12
|
+
// 5. Instruction mediation: `.codex/README.md` / `settings.json` point the
|
|
13
|
+
// model at `.codex/ukit/index/*` helpers.
|
|
14
|
+
//
|
|
15
|
+
// Honesty contract (SPEC §14): output states are PROVEN / ADVISORY-REACHABLE /
|
|
16
|
+
// INCONCLUSIVE / UNSUPPORTED — a cell can never be promoted beyond what the
|
|
17
|
+
// printed evidence shows. `codex-cli: absent` ⇒ subagent consumption is
|
|
18
|
+
// INCONCLUSIVE by construction; no claim of tested capability is emitted.
|
|
19
|
+
//
|
|
20
|
+
// Usage: node scripts/probe/codex-capability-probe.mjs [workspace-root]
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import os from 'node:os';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import process from 'node:process';
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
|
|
28
|
+
// The probe targets an INSTALLED workspace (.codex/ exists after `ukit
|
|
29
|
+
// install`); pass the workspace root explicitly when it differs from cwd.
|
|
30
|
+
const ROOT = path.resolve(process.argv[2] ?? process.cwd());
|
|
31
|
+
const CODEX_DIR = path.join(ROOT, '.codex');
|
|
32
|
+
|
|
33
|
+
const out = [];
|
|
34
|
+
const say = (line = '') => { out.push(line); console.log(line); };
|
|
35
|
+
|
|
36
|
+
function commandOnPath(cmd) {
|
|
37
|
+
const r = spawnSync('sh', ['-c', `command -v ${cmd}`], { encoding: 'utf8' });
|
|
38
|
+
return r.status === 0 ? r.stdout.trim() : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function lstatKind(p) {
|
|
42
|
+
try {
|
|
43
|
+
const st = fs.lstatSync(p);
|
|
44
|
+
if (st.isSymbolicLink()) return `symlink -> ${fs.readlinkSync(p)}`;
|
|
45
|
+
if (st.isDirectory()) return 'dir';
|
|
46
|
+
return `file (${st.size}B)`;
|
|
47
|
+
} catch {
|
|
48
|
+
return 'ABSENT';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
say(`probe-root: ${ROOT}`);
|
|
53
|
+
say(`probe-at: ${new Date().toISOString()}`);
|
|
54
|
+
|
|
55
|
+
// --- 1. codex CLI presence -------------------------------------------------
|
|
56
|
+
const codexBin = commandOnPath('codex');
|
|
57
|
+
say(`codex-cli: ${codexBin ? `present (${codexBin})` : 'absent'}`);
|
|
58
|
+
|
|
59
|
+
// --- 2. .codex surface inventory ------------------------------------------
|
|
60
|
+
say('--- .codex surface inventory ---');
|
|
61
|
+
let codexEntries = [];
|
|
62
|
+
try {
|
|
63
|
+
codexEntries = fs.readdirSync(CODEX_DIR).sort();
|
|
64
|
+
} catch {
|
|
65
|
+
say('.codex/: ABSENT (run `node bin/ukit install` first)');
|
|
66
|
+
}
|
|
67
|
+
for (const name of codexEntries) {
|
|
68
|
+
say(` .codex/${name}: ${lstatKind(path.join(CODEX_DIR, name))}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- 3. .codex/agents resolution -------------------------------------------
|
|
72
|
+
say('--- .codex/agents resolution ---');
|
|
73
|
+
const agentsDir = path.join(CODEX_DIR, 'agents');
|
|
74
|
+
let agentFiles = [];
|
|
75
|
+
try {
|
|
76
|
+
agentFiles = fs.readdirSync(agentsDir).filter((f) => f.endsWith('.md')).sort();
|
|
77
|
+
} catch {
|
|
78
|
+
// absent
|
|
79
|
+
}
|
|
80
|
+
say(` resolved-agent-files: ${agentFiles.length} [${agentFiles.join(', ')}]`);
|
|
81
|
+
for (const wanted of ['code-reviewer.md', 'handoff-planner.md', 'handoff-review.md']) {
|
|
82
|
+
say(` ${wanted}: ${agentFiles.includes(wanted) ? 'present' : 'absent'}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- 4. review-lane reachability via .codex/ukit ---------------------------
|
|
86
|
+
say('--- review-lane reachability (via .codex/ukit symlink) ---');
|
|
87
|
+
const verdictCli = path.join(CODEX_DIR, 'ukit', 'index', 'review-verdict.mjs');
|
|
88
|
+
const panelCli = path.join(CODEX_DIR, 'ukit', 'index', 'review-panel-aggregate.mjs');
|
|
89
|
+
say(` .codex/ukit/index/review-verdict.mjs: ${fs.existsSync(verdictCli) ? 'resolves' : 'missing'}`);
|
|
90
|
+
say(` .codex/ukit/index/review-panel-aggregate.mjs: ${fs.existsSync(panelCli) ? 'resolves' : 'missing'}`);
|
|
91
|
+
|
|
92
|
+
let verdictReachable = false;
|
|
93
|
+
let panelReachable = false;
|
|
94
|
+
if (fs.existsSync(verdictCli) || fs.existsSync(panelCli)) {
|
|
95
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ukit-codex-probe-'));
|
|
96
|
+
try {
|
|
97
|
+
if (fs.existsSync(verdictCli)) {
|
|
98
|
+
const findings = path.join(tmp, 'findings.json');
|
|
99
|
+
fs.writeFileSync(findings, JSON.stringify({
|
|
100
|
+
findings: [{ id: 'P1', severity: 'minor', file: 'probe.js', line: 1, claim: 'probe finding' }],
|
|
101
|
+
}));
|
|
102
|
+
// Isolate config: empty root/home => decisionPlane stage resolves 'off',
|
|
103
|
+
// so no gateway call — deterministic fallback is the demonstrated path.
|
|
104
|
+
const r = spawnSync('node', [verdictCli, '--file', findings], {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
env: { ...process.env, UKIT_TEST_ROOT: tmp, UKIT_TEST_HOME: tmp },
|
|
107
|
+
timeout: 15000,
|
|
108
|
+
});
|
|
109
|
+
const parsed = JSON.parse(r.stdout.trim() || '{}');
|
|
110
|
+
say(` review-verdict run: exit=${r.status} verdict=${parsed.verdict ?? 'UNPARSED'} `
|
|
111
|
+
+ `stage=${parsed.stage ?? '-'} verdictSource=${parsed.verdictSource ?? '-'} `
|
|
112
|
+
+ `outcomeClass=${parsed.outcomeClass ?? '-'}`);
|
|
113
|
+
verdictReachable = r.status === 0 && typeof parsed.verdict === 'string';
|
|
114
|
+
}
|
|
115
|
+
if (fs.existsSync(panelCli)) {
|
|
116
|
+
const task = path.join(tmp, 'PROBE-TASK.md');
|
|
117
|
+
fs.writeFileSync(task, [
|
|
118
|
+
'# probe task',
|
|
119
|
+
'## Reviewer Verdict',
|
|
120
|
+
'MEMBER: probe-reviewer-a',
|
|
121
|
+
'VERDICT: APPROVED',
|
|
122
|
+
'## Reviewer Verdict',
|
|
123
|
+
'MEMBER: probe-reviewer-b',
|
|
124
|
+
'VERDICT: APPROVED',
|
|
125
|
+
'## Other',
|
|
126
|
+
'tail',
|
|
127
|
+
'',
|
|
128
|
+
].join('\n'));
|
|
129
|
+
const r = spawnSync('node', [panelCli, task], { encoding: 'utf8', timeout: 15000 });
|
|
130
|
+
const verdictLine = (r.stdout || '').split('\n').find((l) => /^VERDICT:/.test(l)) ?? '(no VERDICT line)';
|
|
131
|
+
say(` review-panel-aggregate run: exit=${r.status} ${verdictLine.trim()}`);
|
|
132
|
+
panelReachable = r.status === 0 && /^VERDICT: approved/.test(verdictLine.trim());
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- 5. instruction mediation ----------------------------------------------
|
|
140
|
+
say('--- instruction mediation ---');
|
|
141
|
+
const readmePath = path.join(CODEX_DIR, 'README.md');
|
|
142
|
+
const settingsPath = path.join(CODEX_DIR, 'settings.json');
|
|
143
|
+
try {
|
|
144
|
+
const readme = fs.readFileSync(readmePath, 'utf8');
|
|
145
|
+
const hits = (readme.match(/\.codex\/ukit\/index\/[\w-]+\.mjs/g) ?? []);
|
|
146
|
+
say(` .codex/README.md helper references: ${[...new Set(hits)].join(', ') || 'none'}`);
|
|
147
|
+
} catch {
|
|
148
|
+
say(' .codex/README.md: ABSENT');
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
152
|
+
const helper = settings?.skillRouter?.helperCommand ?? '(none)';
|
|
153
|
+
say(` .codex/settings.json skillRouter.helperCommand: ${helper}`);
|
|
154
|
+
} catch {
|
|
155
|
+
say(' .codex/settings.json: ABSENT/unparseable');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- verdicts ---------------------------------------------------------------
|
|
159
|
+
say('--- verdicts ---');
|
|
160
|
+
const agentsSurface = agentFiles.length > 0;
|
|
161
|
+
say(`codex.subagents: ${codexBin ? 'PENDING-LIVE' : 'INCONCLUSIVE'}`
|
|
162
|
+
+ ` (.codex/agents surface ${agentsSurface ? 'shipped+resolved' : 'not resolvable'};`
|
|
163
|
+
+ ` host consumption ${codexBin ? 'requires live-session drive' : 'unprovable — codex CLI absent'})`);
|
|
164
|
+
say(`codex.review-lane: ${verdictReachable && panelReachable ? 'ADVISORY-REACHABLE' : 'INCONCLUSIVE'}`
|
|
165
|
+
+ ` (instruction-mediated Bash via .codex/ukit symlink;`
|
|
166
|
+
+ ` review-verdict=${verdictReachable ? 'ran' : 'failed'},`
|
|
167
|
+
+ ` panel-aggregate=${panelReachable ? 'ran' : 'failed'};`
|
|
168
|
+
+ ' enforcement: advisory — Codex exposes no hook surface)');
|
|
169
|
+
say('probe-end');
|
|
@@ -31,6 +31,10 @@ import { findOpencodeArtifacts, opencodeSteerMessage } from '../../core/opencode
|
|
|
31
31
|
import { buildUserPaths } from '../../core/userPaths.js';
|
|
32
32
|
import { loadPlaybooks } from '../../core/userPlaybooks.js';
|
|
33
33
|
import { userMemoryStats } from '../../core/memory/userMemory.js';
|
|
34
|
+
import {
|
|
35
|
+
inspectVerificationMap,
|
|
36
|
+
doctorRecipeCheck,
|
|
37
|
+
} from '../../index/verificationMap.js';
|
|
34
38
|
|
|
35
39
|
import { resolveSeamStages } from '../../core/observability/rollout.js';
|
|
36
40
|
import {
|
|
@@ -248,6 +252,97 @@ function printFlightRecorderSection(flightRecorder) {
|
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
// TASK-006 (BL-008) — Codex surface report. Advisory only: the section prints
|
|
256
|
+
// what was measured, never an assumption — hook-surface absence, the
|
|
257
|
+
// .codex/agents + .codex/ukit symlink state, the subagents capability cell read
|
|
258
|
+
// from manifests/hostCapabilities.yaml (TASK-001 probe values), and the
|
|
259
|
+
// advisory enforcement ceiling. It never enters `checks`/`projectChecks`, so
|
|
260
|
+
// it can never move the exit code.
|
|
261
|
+
|
|
262
|
+
// '.codex/agents' ships as a symlink to '../.claude/agents'. readdir follows
|
|
263
|
+
// the symlink, so a broken link and a missing real directory collapse into
|
|
264
|
+
// the same 'broken' state — readdir failing while the path itself exists
|
|
265
|
+
// means the entry is present but unreadable (dangling link, or a file).
|
|
266
|
+
async function inspectCodexDirEntry(projectRoot, name) {
|
|
267
|
+
const entryPath = path.join(projectRoot, '.codex', name);
|
|
268
|
+
// lstat (not pathExists/stat): the entry ships as a symlink, and a dangling
|
|
269
|
+
// symlink stats ENOENT — indistinguishable from absent. lstat sees the link
|
|
270
|
+
// itself, so 'broken' reports the dangling-link truth.
|
|
271
|
+
let stat = null;
|
|
272
|
+
try {
|
|
273
|
+
stat = await fs.lstat(entryPath);
|
|
274
|
+
} catch {
|
|
275
|
+
return { state: 'absent', count: null };
|
|
276
|
+
}
|
|
277
|
+
if (!stat.isDirectory() && !stat.isSymbolicLink()) return { state: 'broken', count: null };
|
|
278
|
+
try {
|
|
279
|
+
const entries = await fs.readdir(entryPath);
|
|
280
|
+
return {
|
|
281
|
+
state: 'resolves',
|
|
282
|
+
count: entries.filter((e) => e.endsWith('.md')).length,
|
|
283
|
+
};
|
|
284
|
+
} catch {
|
|
285
|
+
return { state: 'broken', count: null };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function inspectCodexSurface({ projectRoot, packageRoot }) {
|
|
290
|
+
const codexDir = path.join(projectRoot, '.codex');
|
|
291
|
+
if (!(await pathExists(codexDir))) return { installed: false };
|
|
292
|
+
|
|
293
|
+
const subagentsCell = { state: null, note: null };
|
|
294
|
+
try {
|
|
295
|
+
const caps = parse(await fs.readFile(
|
|
296
|
+
path.join(packageRoot, 'manifests', 'hostCapabilities.yaml'), 'utf8',
|
|
297
|
+
));
|
|
298
|
+
const cell = caps?.hosts?.codex?.subagents;
|
|
299
|
+
subagentsCell.state = typeof cell?.state === 'string' ? cell.state : null;
|
|
300
|
+
subagentsCell.note = typeof cell?.note === 'string' ? cell.note
|
|
301
|
+
: typeof cell?.evidence === 'string' ? cell.evidence : null;
|
|
302
|
+
} catch {
|
|
303
|
+
// manifest absent/unreadable — state stays unknown, never assumed.
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
installed: true,
|
|
308
|
+
readme: await pathExists(path.join(codexDir, 'README.md')),
|
|
309
|
+
settings: await pathExists(path.join(codexDir, 'settings.json')),
|
|
310
|
+
settingsLocal: await pathExists(path.join(codexDir, 'settings.local.json')),
|
|
311
|
+
agents: await inspectCodexDirEntry(projectRoot, 'agents'),
|
|
312
|
+
ukit: await inspectCodexDirEntry(projectRoot, 'ukit'),
|
|
313
|
+
subagents: subagentsCell,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function printCodexSurfaceSection(surface) {
|
|
318
|
+
const mark = (v) => (v ? '✓' : '✗');
|
|
319
|
+
console.log('[UKit] Codex surface checks (advisory — never affects exit code):');
|
|
320
|
+
if (!surface.installed) {
|
|
321
|
+
console.log('[UKit] - codex surface not installed (no .codex/ directory — run ukit install to add it)');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const entryLine = (label, entry) => {
|
|
325
|
+
if (entry.state === 'resolves') {
|
|
326
|
+
console.log(`[UKit] ✓ ${label}: resolves${entry.count !== null ? ` (${entry.count} .md)` : ''}`);
|
|
327
|
+
} else if (entry.state === 'broken') {
|
|
328
|
+
console.log(`[UKit] ✗ ${label}: broken (present but unreadable — rerun ukit install)`);
|
|
329
|
+
} else {
|
|
330
|
+
console.log(`[UKit] - ${label}: absent`);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
console.log(`[UKit] ${mark(surface.readme)} .codex/README.md`);
|
|
334
|
+
console.log(`[UKit] ${mark(surface.settings)} .codex/settings.json`);
|
|
335
|
+
console.log(`[UKit] ${mark(surface.settingsLocal)} .codex/settings.local.json`);
|
|
336
|
+
entryLine('.codex/agents', surface.agents);
|
|
337
|
+
entryLine('.codex/ukit', surface.ukit);
|
|
338
|
+
const subagents = surface.subagents;
|
|
339
|
+
console.log(
|
|
340
|
+
`[UKit] - codex.subagents capability: ${subagents.state ?? 'unknown'}${subagents.note ? ` — ${subagents.note}` : ''}`,
|
|
341
|
+
);
|
|
342
|
+
console.log('[UKit] - hook surface: absent — Codex exposes no hook surface; routing is instruction-mediated via route-task.mjs');
|
|
343
|
+
console.log('[UKit] - enforcement ceiling: advisory — codex outcome records carry enforcement: advisory');
|
|
344
|
+
}
|
|
345
|
+
|
|
251
346
|
// --json machine shape: null = unknown (mirrors `ukit telemetry status`).
|
|
252
347
|
function flightRecorderJson(flightRecorder) {
|
|
253
348
|
const { segments, queue, lastProjection, crashes } = flightRecorder;
|
|
@@ -276,6 +371,53 @@ function flightRecorderJson(flightRecorder) {
|
|
|
276
371
|
};
|
|
277
372
|
}
|
|
278
373
|
|
|
374
|
+
|
|
375
|
+
// TASK-C85-014 (BL-014): recipe-vs-package.json check. A verification recipe
|
|
376
|
+
// whose named harness script is absent degrades to a capability-negotiated
|
|
377
|
+
// fallback RECEIPT — the report returns the receipt spec verbatim and never
|
|
378
|
+
// fabricates a runnable command. Advisory row only: a missing/malformed map
|
|
379
|
+
// or an absent harness warns, it never fails the doctor run.
|
|
380
|
+
export async function inspectVerificationRecipe({ projectRoot, homeDir = null, builtinPath = null } = {}) {
|
|
381
|
+
const inspection = await inspectVerificationMap({ projectRoot, homeDir, builtinPath });
|
|
382
|
+
if (!inspection.map) {
|
|
383
|
+
return {
|
|
384
|
+
status: 'warn',
|
|
385
|
+
label: `verification-map recipes: unavailable (${inspection.reason || 'no-map'})`,
|
|
386
|
+
detail: 'guard and stop gate fall back to pre-map verification behavior',
|
|
387
|
+
remedy: 'Run ukit install to seed ~/.ukit/playbooks/verification-map.json',
|
|
388
|
+
missing: [],
|
|
389
|
+
checked: 0,
|
|
390
|
+
source: null,
|
|
391
|
+
reason: inspection.reason || 'no-map',
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
let scripts = {};
|
|
395
|
+
try {
|
|
396
|
+
const pkg = JSON.parse(await fs.readFile(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
397
|
+
scripts = pkg?.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
|
|
398
|
+
} catch {
|
|
399
|
+
scripts = {};
|
|
400
|
+
}
|
|
401
|
+
const check = doctorRecipeCheck({ map: inspection.map, packageJsonScripts: scripts });
|
|
402
|
+
if (check.missing.length === 0) {
|
|
403
|
+
return {
|
|
404
|
+
status: 'ok',
|
|
405
|
+
label: `verification-map recipes: ${check.checked} harness script(s) present`,
|
|
406
|
+
missing: [],
|
|
407
|
+
checked: check.checked,
|
|
408
|
+
source: inspection.source,
|
|
409
|
+
reason: null,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
status: 'warn',
|
|
414
|
+
label: `verification-map recipes: ${check.missing.length} harness script(s) absent from package.json`,
|
|
415
|
+
missing: check.missing,
|
|
416
|
+
checked: check.checked,
|
|
417
|
+
source: inspection.source,
|
|
418
|
+
reason: 'absent-harness',
|
|
419
|
+
};
|
|
420
|
+
}
|
|
279
421
|
export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir = os.homedir(), ompPath = 'omp' }) {
|
|
280
422
|
const unknownFlags = argv.filter((flag) => !KNOWN_FLAGS.has(flag));
|
|
281
423
|
if (unknownFlags.length > 0) {
|
|
@@ -616,6 +758,32 @@ export async function runDoctor({ packageRoot, projectRoot, argv = [], homeDir =
|
|
|
616
758
|
}
|
|
617
759
|
}
|
|
618
760
|
|
|
761
|
+
// TASK-006 (BL-008) — codex surface report. Statements, never assumptions:
|
|
762
|
+
// what is on disk and what the manifests record. Advisory only — it never
|
|
763
|
+
// enters `checks` or `projectChecks` and cannot move the exit code.
|
|
764
|
+
printCodexSurfaceSection(await inspectCodexSurface({ projectRoot, packageRoot }));
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
// TASK-C85-014 (BL-014) — verification-map recipe check. Advisory only:
|
|
768
|
+
// warn rows never enter the blocking set and never fabricate a command —
|
|
769
|
+
// a recipe naming an absent harness prints its fallback RECEIPT spec.
|
|
770
|
+
{
|
|
771
|
+
const recipe = await inspectVerificationRecipe({ projectRoot, homeDir });
|
|
772
|
+
console.log('[UKit] Verification-map checks (advisory — does not affect exit code):');
|
|
773
|
+
const sev = recipe.status === 'warn' ? ' (warning)' : '';
|
|
774
|
+
console.log(`[UKit] ${ok(recipe.status === 'ok')} ${recipe.label}${sev}`);
|
|
775
|
+
if (recipe.detail) console.log(`[UKit] detail: ${recipe.detail}`);
|
|
776
|
+
for (const entry of recipe.missing ?? []) {
|
|
777
|
+
console.log(`[UKit] missing: ${entry.artifactClass} → package.json script '${entry.script}'`);
|
|
778
|
+
if (entry.fallback) console.log(`[UKit] fallback: ${entry.fallback}`);
|
|
779
|
+
}
|
|
780
|
+
if (recipe.remedy && recipe.status === 'warn') {
|
|
781
|
+
console.log(`[UKit] remedy: ${recipe.remedy}`);
|
|
782
|
+
}
|
|
783
|
+
if (recipe.status === 'warn' && recipe.reason === 'absent-harness') {
|
|
784
|
+
console.log('[UKit] remedy: add the named script(s) to package.json, or rely on the fallback receipt attestation — no substitute command is fabricated.');
|
|
785
|
+
}
|
|
786
|
+
}
|
|
619
787
|
if (runtimeConfigInspection.errors.length > 0) {
|
|
620
788
|
console.log(`[UKit] Runtime config issues: ${runtimeConfigInspection.errors.join(' | ')}`);
|
|
621
789
|
}
|
|
@@ -226,6 +226,13 @@ export async function runIndexTools({ projectRoot, argv = [] }) {
|
|
|
226
226
|
if (nextDisplay) {
|
|
227
227
|
console.log(`next: ${nextDisplay}`);
|
|
228
228
|
}
|
|
229
|
+
// BL-018: the fix-loop escalation lane — 'blocked' on exhaustion.
|
|
230
|
+
if (result.routeSummary?.escalatedLane) {
|
|
231
|
+
console.log(`escalatedLane: ${result.routeSummary.escalatedLane}`);
|
|
232
|
+
if (result.routeSummary.escalateReason) {
|
|
233
|
+
console.log(`escalateReason: ${result.routeSummary.escalateReason}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
229
236
|
if (result.routeSummary?.delegateHint) {
|
|
230
237
|
console.log(`delegate: ${result.routeSummary.delegateHint}`);
|
|
231
238
|
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
// `ukit metrics` —
|
|
1
|
+
// `ukit metrics` — telemetry roll-up (SPEC §6, SI-102).
|
|
2
2
|
// Route outcomes + failure patterns (lazy, degrade to n/a) + v2 memory count.
|
|
3
3
|
// Never exits non-zero on missing telemetry; unknown flag → exit 1.
|
|
4
|
+
// BL-019 (SPEC FR-001): the one deliberate write — the joined corpus derives
|
|
5
|
+
// tier-map.json, the cache artifact the router reads for measured tier picks.
|
|
4
6
|
|
|
5
7
|
import fs from 'node:fs/promises';
|
|
6
8
|
import path from 'node:path';
|
|
7
9
|
import { collectRouteOutcomes } from '../../diagnostics/routeOutcomes.js';
|
|
10
|
+
import { MODEL_TIER_BY_CONTRACT } from '../../core/executionContracts.js';
|
|
11
|
+
import {
|
|
12
|
+
deriveTierStats,
|
|
13
|
+
evaluateTierPromotion,
|
|
14
|
+
buildTierMapArtifact,
|
|
15
|
+
writeTierMap,
|
|
16
|
+
} from '../../index/tierSelection.js';
|
|
8
17
|
import { buildRuntimePaths } from '../../core/runtimePaths.js';
|
|
9
18
|
|
|
10
19
|
const HELP_FLAGS = new Set(['--help', '-h', 'help']);
|
|
@@ -74,6 +83,57 @@ function printRouteOutcomes(routeOutcomes) {
|
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
// BL-019 (SPEC FR-001 §7): derive the measured tier map from the joined
|
|
87
|
+
// corpus and persist the cache artifact the router reads. Returns the rollup
|
|
88
|
+
// the print/JSON surfaces consume; null when there is no joined data at all
|
|
89
|
+
// (no artifact written — an empty corpus must not clobber a live promote map).
|
|
90
|
+
async function deriveAndWriteTierMap(projectRoot, routeOutcomes) {
|
|
91
|
+
try {
|
|
92
|
+
const joinedRows = Array.isArray(routeOutcomes?.joinedRows) ? routeOutcomes.joinedRows : [];
|
|
93
|
+
if (joinedRows.length === 0) return null;
|
|
94
|
+
const stats = deriveTierStats(joinedRows);
|
|
95
|
+
const evaluation = evaluateTierPromotion({ stats, staticMap: MODEL_TIER_BY_CONTRACT });
|
|
96
|
+
const artifact = buildTierMapArtifact({ stats, staticMap: MODEL_TIER_BY_CONTRACT });
|
|
97
|
+
await writeTierMap(projectRoot, artifact);
|
|
98
|
+
return {
|
|
99
|
+
joinedRoutes: stats.joinedRoutes,
|
|
100
|
+
agreement: evaluation.agreement,
|
|
101
|
+
cheaperShare: evaluation.cheaperShare,
|
|
102
|
+
regressionFree: evaluation.regressionFree,
|
|
103
|
+
decision: evaluation.decision,
|
|
104
|
+
reasons: evaluation.reasons,
|
|
105
|
+
byMode: evaluation.byMode,
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return { error: error?.message ?? String(error) };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function printTierSelection(tierSelection) {
|
|
113
|
+
console.log('tier-selection');
|
|
114
|
+
if (!tierSelection) {
|
|
115
|
+
console.log(' n/a (no joined route↔outcome data)');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (tierSelection.error) {
|
|
119
|
+
console.log(` n/a (${tierSelection.error})`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const reasons = Array.isArray(tierSelection.reasons) && tierSelection.reasons.length > 0
|
|
123
|
+
? ` reasons: ${tierSelection.reasons.join(', ')}`
|
|
124
|
+
: '';
|
|
125
|
+
console.log(
|
|
126
|
+
` joined: ${tierSelection.joinedRoutes} agreement: ${(tierSelection.agreement * 100).toFixed(1)}%`
|
|
127
|
+
+ ` cheaper-share: ${(tierSelection.cheaperShare * 100).toFixed(1)}%`
|
|
128
|
+
+ ` regression-free: ${tierSelection.regressionFree}`
|
|
129
|
+
+ ` decision: ${tierSelection.decision}${reasons}`,
|
|
130
|
+
);
|
|
131
|
+
const picks = Object.entries(tierSelection.byMode ?? {});
|
|
132
|
+
if (picks.length > 0) {
|
|
133
|
+
console.log(` measured: ${picks.map(([mode, tier]) => `${mode}→${tier}`).join(' ')}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
77
137
|
function printFailurePatterns(failurePatterns) {
|
|
78
138
|
console.log('failure-patterns');
|
|
79
139
|
if (!failurePatterns) {
|
|
@@ -211,8 +271,10 @@ export async function runMetrics({ projectRoot, argv = [] }) {
|
|
|
211
271
|
const skillAccuracy = await lazyCollect('../../diagnostics/skillAccuracy.js', 'collectSkillAccuracy', projectRoot);
|
|
212
272
|
const laneStats = await lazyCollect('../../diagnostics/laneStats.js', 'collectLaneStats', projectRoot);
|
|
213
273
|
const learning = await collectLearningSuggestions(projectRoot);
|
|
274
|
+
// BL-019: derive + persist the measured tier map from the joined corpus.
|
|
275
|
+
const tierSelection = await deriveAndWriteTierMap(projectRoot, routeOutcomes);
|
|
214
276
|
|
|
215
|
-
const rollup = { routeOutcomes, failurePatterns, memory, feedback, skillAccuracy, laneStats, learning };
|
|
277
|
+
const rollup = { routeOutcomes, failurePatterns, memory, feedback, skillAccuracy, laneStats, learning, tierSelection };
|
|
216
278
|
|
|
217
279
|
if (argv.includes('--json')) {
|
|
218
280
|
console.log(JSON.stringify(rollup, null, 2));
|
|
@@ -229,6 +291,8 @@ export async function runMetrics({ projectRoot, argv = [] }) {
|
|
|
229
291
|
console.log('');
|
|
230
292
|
printLaneStats(laneStats);
|
|
231
293
|
console.log('');
|
|
294
|
+
printTierSelection(tierSelection);
|
|
295
|
+
console.log('');
|
|
232
296
|
printLearning(learning);
|
|
233
297
|
console.log('');
|
|
234
298
|
printMemory(memory);
|
|
@@ -13,7 +13,7 @@ export function printPlaybookHelp() {
|
|
|
13
13
|
console.log('Resolution precedence: project (.ukit/playbooks) > user (~/.ukit/playbooks) > builtin.');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export async function runPlaybook({ projectRoot, homeDir, argv = [], builtins } = {}) {
|
|
16
|
+
export async function runPlaybook({ projectRoot, homeDir, argv = [], builtins, builtinDir } = {}) {
|
|
17
17
|
const [subcommandRaw, ...rest] = argv;
|
|
18
18
|
const subcommand = (subcommandRaw ?? 'list').toLowerCase();
|
|
19
19
|
|
|
@@ -23,9 +23,9 @@ export async function runPlaybook({ projectRoot, homeDir, argv = [], builtins }
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (subcommand === 'list') {
|
|
26
|
-
const rows = await listPlaybooks({ projectRoot, homeDir, builtins });
|
|
26
|
+
const rows = await listPlaybooks({ projectRoot, homeDir, builtins, builtinDir });
|
|
27
27
|
if (rows.length === 0) {
|
|
28
|
-
console.log('No playbooks found (built-ins: bug-fix,
|
|
28
|
+
console.log('No playbooks found (built-ins: bug-fix, feature-implementation)');
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
console.log('id | source | lanes');
|
|
@@ -40,7 +40,7 @@ export async function runPlaybook({ projectRoot, homeDir, argv = [], builtins }
|
|
|
40
40
|
if (!id) {
|
|
41
41
|
throw new Error('Missing playbook id. Usage: ukit playbook show <id>');
|
|
42
42
|
}
|
|
43
|
-
const playbook = await resolvePlaybook(id, { projectRoot, homeDir, builtins });
|
|
43
|
+
const playbook = await resolvePlaybook(id, { projectRoot, homeDir, builtins, builtinDir });
|
|
44
44
|
if (playbook === null) {
|
|
45
45
|
throw new Error(`Unknown playbook: ${id}`);
|
|
46
46
|
}
|
package/src/cli/commands/vm.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
// `ukit vm` — agent-VM live-path driver surface (AGENT_VM_DRIVER_PLAN D-01,
|
|
2
|
-
// D-02, D-03). Discovery + shadow-evidence
|
|
3
|
-
// language-compiled runtime;
|
|
4
|
-
// (
|
|
2
|
+
// D-02, D-03; BL-031 live `run` entry). Discovery + shadow-evidence
|
|
3
|
+
// commands over the language-compiled runtime; shadow runs never execute
|
|
4
|
+
// real operations (stub startFn), the `run` lane launches RUN-node specs
|
|
5
|
+
// through the real supervisor — canary/default only. Nothing mutates
|
|
6
|
+
// config.
|
|
5
7
|
//
|
|
6
8
|
// list Reference plans (planId, planVersion, irHash)
|
|
7
9
|
// show <planId> One plan's compiled node ledger
|
|
8
10
|
// status Stage + evidence counts + shadow dir
|
|
9
11
|
// shadow-run <planId> Deterministic replay through a real VmEngine;
|
|
10
12
|
// appends one evidence record per run (D-03)
|
|
13
|
+
// run <planId> Live execution through a real VmEngine +
|
|
14
|
+
// supervisor launches (canary/default only);
|
|
15
|
+
// appends one evidence record per run (BL-031)
|
|
11
16
|
// evidence <key> Read .ukit/storage/agent-runtime/evidence/<key>.jsonl
|
|
12
17
|
// and print the promote() verdict verbatim (G-D3)
|
|
13
18
|
//
|
|
14
19
|
// Every subcommand is stage-gated on `decisionRuntime.vm`: 'off' → typed
|
|
15
20
|
// refuse `stage_off`, exit 2, zero writes under
|
|
16
|
-
// .ukit/storage/agent-runtime/ (G-D1)
|
|
21
|
+
// .ukit/storage/agent-runtime/ (G-D1); 'run' additionally refuses any
|
|
22
|
+
// stage below canary with `live_stage_required`. Exit codes: 0 success,
|
|
17
23
|
// 2 typed failure, 1 usage error.
|
|
18
24
|
|
|
19
25
|
import fs from 'node:fs/promises';
|
|
@@ -28,12 +34,13 @@ import { loadPlan, loadPlanLibrary } from '../../core/agentRuntime/planLibrary.j
|
|
|
28
34
|
import { promote, promotionCriteriaFor } from '../../core/agentRuntime/promotion.js';
|
|
29
35
|
import {
|
|
30
36
|
runShadowPlan,
|
|
37
|
+
runLivePlan,
|
|
31
38
|
readEvidence,
|
|
32
39
|
} from '../../core/agentRuntime/shadowRun.js';
|
|
33
40
|
|
|
34
41
|
const HELP_FLAGS = new Set(['--help', '-h', 'help']);
|
|
35
|
-
const SUBCOMMANDS = new Set(['list', 'show', 'status', 'shadow-run', 'evidence']);
|
|
36
|
-
const OPERAND_SUBCOMMANDS = new Set(['show', 'shadow-run', 'evidence']);
|
|
42
|
+
const SUBCOMMANDS = new Set(['list', 'show', 'status', 'shadow-run', 'run', 'evidence']);
|
|
43
|
+
const OPERAND_SUBCOMMANDS = new Set(['show', 'shadow-run', 'run', 'evidence']);
|
|
37
44
|
|
|
38
45
|
const RUNTIME_REL = path.join('.ukit', 'storage', 'agent-runtime');
|
|
39
46
|
|
|
@@ -45,6 +52,8 @@ function printUsage() {
|
|
|
45
52
|
console.log(' show <planId> Show one plan\'s compiled node ledger');
|
|
46
53
|
console.log(' status Show decisionRuntime.vm stage + evidence summary');
|
|
47
54
|
console.log(' shadow-run <planId> Run one deterministic shadow pass (stub startFn)');
|
|
55
|
+
console.log(' run <planId> Execute live via the real VmEngine+supervisor path');
|
|
56
|
+
console.log(' (requires decisionRuntime.vm stage canary or default)');
|
|
48
57
|
console.log(' evidence <key> Print the promote() verdict over stored runs');
|
|
49
58
|
console.log('');
|
|
50
59
|
console.log('Options:');
|
|
@@ -168,6 +177,27 @@ async function shadowRunCommand({ projectRoot, config, planId }) {
|
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
179
|
|
|
180
|
+
// --- run (BL-031 live lane) -------------------------------------------------
|
|
181
|
+
|
|
182
|
+
async function runCommand({ projectRoot, config, planId }) {
|
|
183
|
+
// Stage narrowing lives inside runLivePlan: 'shadow' → live_stage_required
|
|
184
|
+
// typed refuse, exit 2, zero writes; canary/default execute + append
|
|
185
|
+
// exactly one evidence record through the same sink as shadow-run.
|
|
186
|
+
const res = await runLivePlan(planId, { projectRoot, config });
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
console.error(`[UKit] vm run: ${res.code} — ${res.reason}`);
|
|
189
|
+
process.exitCode = 2;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
console.log(`run ${res.planId}: ${res.status} (${res.code})`);
|
|
193
|
+
console.log(` planInstance ${res.planInstanceId}`);
|
|
194
|
+
console.log(` planVersion ${res.planVersion} irHash ${res.irHash}`);
|
|
195
|
+
console.log(` transitions ${res.transitions.length} wallMs ${res.wallMs}`);
|
|
196
|
+
for (const t of res.transitions) {
|
|
197
|
+
console.log(` ${t.node} -> ${t.to}${t.branch !== undefined ? ` (${t.branch})` : ''}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
// --- evidence -------------------------------------------------------------
|
|
172
202
|
|
|
173
203
|
async function evidenceCommand({ projectRoot, config, key, json }) {
|
|
@@ -177,15 +207,25 @@ async function evidenceCommand({ projectRoot, config, key, json }) {
|
|
|
177
207
|
process.exitCode = 2;
|
|
178
208
|
return;
|
|
179
209
|
}
|
|
180
|
-
|
|
210
|
+
// Comparison artifacts are recorded evidence, not sampled runs — they
|
|
211
|
+
// stay out of the promote() run set but their verdict prints verbatim.
|
|
212
|
+
const replay = [...res.runs].reverse().find((r) => r?.kind === 'replay-comparison');
|
|
213
|
+
const sampled = res.runs.filter((r) => r?.kind !== 'replay-comparison');
|
|
214
|
+
const verdict = promote(config, { key, runs: sampled });
|
|
181
215
|
const criteria = promotionCriteriaFor(key);
|
|
182
216
|
if (json) {
|
|
183
|
-
console.log(JSON.stringify({
|
|
217
|
+
console.log(JSON.stringify({
|
|
218
|
+
key, runs: res.runs.length, dropped: res.dropped, criteria, verdict,
|
|
219
|
+
...(replay ? { replay } : {}),
|
|
220
|
+
}));
|
|
184
221
|
return;
|
|
185
222
|
}
|
|
186
223
|
console.log(`evidence ${key}: ${res.runs.length} run(s)${res.dropped > 0 ? ` (${res.dropped} malformed dropped)` : ''}`);
|
|
187
224
|
// G-D3: verdict is advisory — printed verbatim, never written back.
|
|
188
225
|
console.log(`promote ${key}: stage=${verdict.stage} promoted=${verdict.promoted} reason=${verdict.reason}`);
|
|
226
|
+
if (replay) {
|
|
227
|
+
console.log(`replay ${key}: verdict=${replay.verdict} reason=${replay.reason} defectsCaught=${replay.defectsCaught} rows=${replay.rows?.length ?? 0}`);
|
|
228
|
+
}
|
|
189
229
|
}
|
|
190
230
|
|
|
191
231
|
// --- dispatch -------------------------------------------------------------
|
|
@@ -238,5 +278,6 @@ export async function runVm({ projectRoot, argv = [] }) {
|
|
|
238
278
|
if (sub === 'show') return showCommand({ config, planId: operand });
|
|
239
279
|
if (sub === 'status') return statusCommand({ projectRoot, config, json: flags.includes('--json') });
|
|
240
280
|
if (sub === 'shadow-run') return shadowRunCommand({ projectRoot, config, planId: operand });
|
|
281
|
+
if (sub === 'run') return runCommand({ projectRoot, config, planId: operand });
|
|
241
282
|
return evidenceCommand({ projectRoot, config, key: operand, json: flags.includes('--json') });
|
|
242
283
|
}
|