ultra-builder-pro-cli 0.23.0 → 0.24.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/.ultra-template/docs/research/README.md +4 -4
- package/.ultra-template/tasks/tasks.json +4 -2
- package/CHANGELOG.md +72 -0
- package/README.md +55 -39
- package/adapters/_shared/codex-assets.cjs +121 -65
- package/adapters/_shared/interaction-contract.cjs +7 -1
- package/adapters/_shared/provenance.cjs +7 -2
- package/adapters/_shared/runtime-assets.cjs +3 -2
- package/adapters/_shared/runtime-native.cjs +132 -0
- package/adapters/claude.js +60 -30
- package/adapters/codex.js +178 -81
- package/adapters/grok.js +372 -0
- package/adapters/kimi.js +7 -3
- package/adapters/opencode.js +210 -32
- package/agents/code-reviewer.md +4 -2
- package/agents/debugger.md +4 -1
- package/agents/review-code.md +7 -3
- package/agents/review-comments.md +7 -4
- package/agents/review-coordinator.md +11 -7
- package/agents/review-design.md +8 -4
- package/agents/review-errors.md +8 -4
- package/agents/review-spec.md +8 -5
- package/agents/review-tests.md +7 -4
- package/agents/tdd-runner.md +5 -2
- package/bin/install.js +9 -4
- package/bin/orchestrator.js +4 -13
- package/docs/AGENT-CONTEXT.md +143 -229
- package/docs/ARCHITECTURE.md +205 -494
- package/docs/ARTIFACT-AUTHORITY.md +35 -28
- package/docs/LEGACY-CLI-CROSSWALK.md +1 -1
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
- package/docs/ROADMAP.md +85 -110
- package/docs/RUNTIME-COMPAT-MATRIX.md +139 -175
- package/docs/SKILL-AUTHORING.md +3 -3
- package/docs/STATE-DB-ACCESS-POLICY.md +13 -8
- package/docs/WORKFLOW-LIFECYCLE.md +189 -448
- package/hooks/README.md +11 -8
- package/hooks/active_task_context.py +16 -16
- package/hooks/adapters/grok.py +134 -0
- package/hooks/{context_spine.py → context_envelope.py} +26 -22
- package/hooks/health_check.py +29 -34
- package/hooks/pre_stop_check.py +22 -12
- package/hooks/subagent_tracker.py +1 -1
- package/hooks/workflow_checkpoint.py +14 -13
- package/hooks/workflow_context.py +11 -12
- package/hooks/workflow_resume.py +14 -13
- package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
- package/mcp-server/lib/baseline-workflow.cjs +108 -82
- package/mcp-server/lib/canonical-json.cjs +19 -0
- package/mcp-server/lib/change-authority.cjs +26 -0
- package/mcp-server/lib/change-workflow.cjs +630 -45
- package/mcp-server/lib/context-envelope.cjs +508 -0
- package/mcp-server/lib/decision-records.cjs +337 -0
- package/mcp-server/lib/doctor.cjs +91 -33
- package/mcp-server/lib/init-project.cjs +78 -39
- package/mcp-server/lib/managed-file-write.cjs +116 -0
- package/mcp-server/lib/plan-checkpoint.cjs +177 -0
- package/mcp-server/lib/project-context.cjs +178 -0
- package/mcp-server/lib/stage-checkpoints.cjs +309 -0
- package/mcp-server/lib/state-db.cjs +155 -24
- package/mcp-server/lib/state-ops.cjs +21 -3
- package/mcp-server/lib/task-ledger.cjs +522 -13
- package/mcp-server/lib/ultra-facade.cjs +757 -442
- package/mcp-server/lib/worker-packet.cjs +358 -0
- package/mcp-server/server.cjs +200 -1103
- package/orchestrator/daemon.cjs +27 -9
- package/orchestrator/parallel-orchestrator.cjs +111 -76
- package/orchestrator/session-runner.cjs +135 -34
- package/package.json +10 -3
- package/skills/ultra-change/SKILL.md +10 -8
- package/skills/ultra-deliver/SKILL.md +17 -19
- package/skills/ultra-dev/SKILL.md +20 -11
- package/skills/ultra-doctor/SKILL.md +4 -4
- package/skills/ultra-init/SKILL.md +6 -5
- package/skills/ultra-init/references/brownfield-adoption.md +20 -14
- package/skills/ultra-plan/SKILL.md +13 -9
- package/skills/ultra-research/SKILL.md +18 -9
- package/skills/ultra-review/SKILL.md +14 -9
- package/skills/ultra-review/references/unified-schema.md +2 -0
- package/skills/ultra-review/scripts/review_wait.py +8 -0
- package/skills/ultra-status/SKILL.md +2 -2
- package/skills/ultra-test/SKILL.md +10 -6
- package/skills/ultra-think/SKILL.md +8 -7
- package/skills/ultra-think/references/interaction-boundary.md +56 -0
- package/spec/cli-protocol.md +80 -199
- package/spec/mcp-tools.yaml +43 -2806
- package/spec/schemas/state-db.sql +107 -4
- package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
- package/ultra-tools/commands/status.cjs +369 -243
- package/mcp-server/lib/context-spine.cjs +0 -1208
- package/mcp-server/lib/decision-dialogue.cjs +0 -1128
- package/mcp-server/lib/project-breadcrumb.cjs +0 -173
- package/mcp-server/lib/spec-learning.cjs +0 -284
- package/mcp-server/lib/workflow-state.cjs +0 -3129
- package/skills/ultra-think/references/decision-dialogue.md +0 -155
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
// Ultra project status panel.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// The v0.24 status surface reads the same bounded Context Envelope used by
|
|
6
|
+
// Skills, hooks, sessions, and agents. It reports accepted Checkpoints and
|
|
7
|
+
// Decision Records; it does not resurrect the retired workflow supervisor or
|
|
8
|
+
// infer an authorized next command.
|
|
8
9
|
|
|
9
10
|
const fs = require('node:fs');
|
|
10
11
|
const path = require('node:path');
|
|
11
12
|
|
|
12
|
-
const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
13
|
-
const baselines = require('../../mcp-server/lib/baseline-workflow.cjs');
|
|
14
|
-
const workflowState = require('../../mcp-server/lib/workflow-state.cjs');
|
|
15
|
-
const decisionDialogue = require('../../mcp-server/lib/decision-dialogue.cjs');
|
|
16
13
|
const artifactRegistry = require('../../mcp-server/lib/artifact-registry.cjs');
|
|
17
|
-
const
|
|
14
|
+
const baselines = require('../../mcp-server/lib/baseline-workflow.cjs');
|
|
15
|
+
const contextEnvelope = require('../../mcp-server/lib/context-envelope.cjs');
|
|
16
|
+
const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
18
17
|
const runtimePaths = require('../../mcp-server/lib/runtime-paths.cjs');
|
|
19
18
|
|
|
20
19
|
const USAGE = `ultra-tools status [flags]
|
|
21
20
|
|
|
22
|
-
Reads .ultra/.runtime/state.db and prints
|
|
23
|
-
cost
|
|
21
|
+
Reads .ultra/.runtime/state.db and prints the canonical Context Envelope,
|
|
22
|
+
accepted Checkpoints, durable task/session state, diagnostics, and cost
|
|
23
|
+
observability. Route selection remains with the host model and user.
|
|
24
24
|
|
|
25
25
|
FLAGS:
|
|
26
26
|
--cost include per-runtime + top-tasks cost panel (default on)
|
|
@@ -39,227 +39,339 @@ function fail(code, message) {
|
|
|
39
39
|
return 2;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function parseJson(value, fallback) {
|
|
43
|
+
try { return value == null ? fallback : JSON.parse(value); }
|
|
44
|
+
catch { return fallback; }
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
function parseSince(value, nowMs = Date.now()) {
|
|
43
48
|
if (value === null || value === undefined || value === '') return null;
|
|
44
|
-
const
|
|
45
|
-
if (/^\d{4}-\d{2}-\d{2}T/.test(
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const normalized = String(value).trim();
|
|
50
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(normalized)) return normalized;
|
|
51
|
+
const match = normalized.match(/^(\d+)([dhm])$/i);
|
|
52
|
+
if (!match) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`invalid --since value: ${value} (expected 7d, 24h, 30m, or ISO-8601)`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const amount = Number(match[1]);
|
|
58
|
+
const unit = match[2].toLowerCase();
|
|
59
|
+
const milliseconds = unit === 'd'
|
|
60
|
+
? amount * 86400000
|
|
61
|
+
: unit === 'h'
|
|
62
|
+
? amount * 3600000
|
|
63
|
+
: amount * 60000;
|
|
64
|
+
return new Date(nowMs - milliseconds).toISOString();
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
function parseFlags(args) {
|
|
55
68
|
const out = { cost: true, json: false, since: null, limit: 3 };
|
|
56
|
-
for (let
|
|
57
|
-
const
|
|
58
|
-
switch (
|
|
59
|
-
case '--cost':
|
|
60
|
-
case '--json':
|
|
61
|
-
case '--since': out.since = args[++
|
|
62
|
-
case '--limit': out.limit = Number(args[++
|
|
63
|
-
case '-h':
|
|
69
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
70
|
+
const argument = args[index];
|
|
71
|
+
switch (argument) {
|
|
72
|
+
case '--cost': out.cost = true; break;
|
|
73
|
+
case '--json': out.json = true; break;
|
|
74
|
+
case '--since': out.since = args[++index]; break;
|
|
75
|
+
case '--limit': out.limit = Number(args[++index]); break;
|
|
76
|
+
case '-h':
|
|
77
|
+
case '--help':
|
|
78
|
+
return { help: true };
|
|
64
79
|
default:
|
|
65
|
-
if (
|
|
66
|
-
else if (
|
|
67
|
-
else throw new Error(`unknown flag: ${
|
|
80
|
+
if (argument.startsWith('--since=')) out.since = argument.split('=')[1];
|
|
81
|
+
else if (argument.startsWith('--limit=')) out.limit = Number(argument.split('=')[1]);
|
|
82
|
+
else throw new Error(`unknown flag: ${argument}`);
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
if (Number.isNaN(out.limit) || out.limit <= 0) out.limit = 3;
|
|
71
86
|
return out;
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
function
|
|
89
|
+
function countStatuses(db, tables, table, statuses) {
|
|
90
|
+
if (!tables.has(table)) {
|
|
91
|
+
return {
|
|
92
|
+
status: 'unavailable',
|
|
93
|
+
...Object.fromEntries(statuses.map((status) => [status, 0])),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const rows = db.prepare(
|
|
97
|
+
`SELECT status, COUNT(*) AS count FROM ${table} GROUP BY status`,
|
|
98
|
+
).all();
|
|
99
|
+
const counts = Object.fromEntries(statuses.map((status) => [status, 0]));
|
|
100
|
+
for (const row of rows) {
|
|
101
|
+
if (row.status in counts) counts[row.status] = Number(row.count);
|
|
102
|
+
}
|
|
103
|
+
return counts;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readContext(db, tables, rootDir) {
|
|
107
|
+
const required = [
|
|
108
|
+
'baselines', 'changes', 'tasks', 'stage_checkpoints', 'decision_records',
|
|
109
|
+
'context_envelopes', 'worker_packets',
|
|
110
|
+
];
|
|
111
|
+
if (!required.every((table) => tables.has(table))) {
|
|
112
|
+
return {
|
|
113
|
+
value: null,
|
|
114
|
+
error: {
|
|
115
|
+
code: 'CONTEXT_SCHEMA_MIGRATION_REQUIRED',
|
|
116
|
+
message: 'the v0.24 Context Envelope tables are unavailable',
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
return {
|
|
122
|
+
value: contextEnvelope.buildEnvelope(
|
|
123
|
+
db,
|
|
124
|
+
{ detail: 'summary', stage: 'status' },
|
|
125
|
+
{ rootDir, runtime: 'cli' },
|
|
126
|
+
),
|
|
127
|
+
error: null,
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
value: null,
|
|
132
|
+
error: {
|
|
133
|
+
code: error.code || 'CONTEXT_UNAVAILABLE',
|
|
134
|
+
message: error.message,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function checkpointPanel(db, tables) {
|
|
141
|
+
if (!tables.has('stage_checkpoints')) {
|
|
142
|
+
return {
|
|
143
|
+
status: 'unavailable',
|
|
144
|
+
draft: 0,
|
|
145
|
+
accepted: 0,
|
|
146
|
+
superseded: 0,
|
|
147
|
+
current: [],
|
|
148
|
+
diagnostics: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const rows = db.prepare(
|
|
152
|
+
`SELECT id, stage, scope_type, scope_id, revision, status, payload_json,
|
|
153
|
+
evidence_json, diagnostics_json, digest, updated_at
|
|
154
|
+
FROM stage_checkpoints
|
|
155
|
+
ORDER BY updated_at DESC, revision DESC, rowid DESC`,
|
|
156
|
+
).all();
|
|
157
|
+
const counts = { draft: 0, accepted: 0, superseded: 0 };
|
|
158
|
+
const current = [];
|
|
159
|
+
const diagnostics = [];
|
|
160
|
+
for (const row of rows) {
|
|
161
|
+
counts[row.status] = Number(counts[row.status] || 0) + 1;
|
|
162
|
+
const item = {
|
|
163
|
+
id: row.id,
|
|
164
|
+
stage: row.stage,
|
|
165
|
+
scope_type: row.scope_type,
|
|
166
|
+
scope_id: row.scope_id,
|
|
167
|
+
revision: row.revision,
|
|
168
|
+
status: row.status,
|
|
169
|
+
payload: parseJson(row.payload_json, {}),
|
|
170
|
+
evidence: parseJson(row.evidence_json, []),
|
|
171
|
+
diagnostics: parseJson(row.diagnostics_json, []),
|
|
172
|
+
digest: row.digest,
|
|
173
|
+
updated_at: row.updated_at,
|
|
174
|
+
};
|
|
175
|
+
if (row.status !== 'superseded') current.push(item);
|
|
176
|
+
diagnostics.push(...item.diagnostics.map((diagnostic) => ({
|
|
177
|
+
...diagnostic,
|
|
178
|
+
checkpoint_id: row.id,
|
|
179
|
+
stage: row.stage,
|
|
180
|
+
})));
|
|
181
|
+
}
|
|
182
|
+
const status = diagnostics.some((item) => item.severity === 'hard_conflict')
|
|
183
|
+
? 'fail'
|
|
184
|
+
: diagnostics.some((item) => item.severity === 'needs_attention')
|
|
185
|
+
? 'warning'
|
|
186
|
+
: 'pass';
|
|
187
|
+
return {
|
|
188
|
+
status,
|
|
189
|
+
...counts,
|
|
190
|
+
current: current.slice(0, 20),
|
|
191
|
+
diagnostics,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function decisionPanel(db, tables, context) {
|
|
196
|
+
if (!tables.has('decision_records')) {
|
|
197
|
+
return {
|
|
198
|
+
status: 'unavailable',
|
|
199
|
+
accepted: 0,
|
|
200
|
+
superseded: 0,
|
|
201
|
+
current: [],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const rows = db.prepare(
|
|
205
|
+
'SELECT status, COUNT(*) AS count FROM decision_records GROUP BY status',
|
|
206
|
+
).all();
|
|
207
|
+
const counts = { accepted: 0, superseded: 0 };
|
|
208
|
+
for (const row of rows) counts[row.status] = Number(row.count);
|
|
209
|
+
const current = Array.isArray(context?.envelope?.decisions)
|
|
210
|
+
? context.envelope.decisions
|
|
211
|
+
: [];
|
|
212
|
+
return {
|
|
213
|
+
status: 'pass',
|
|
214
|
+
...counts,
|
|
215
|
+
current,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function artifactPanel(db, tables, rootDir) {
|
|
220
|
+
if (!['artifacts', 'artifact_edges'].every((table) => tables.has(table))) {
|
|
221
|
+
return {
|
|
222
|
+
status: 'unavailable',
|
|
223
|
+
registered: 0,
|
|
224
|
+
managed: 0,
|
|
225
|
+
unmanaged: 0,
|
|
226
|
+
issues: [],
|
|
227
|
+
counts: {},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return artifactRegistry.inspectArtifactHealth(db, { rootDir });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
status: 'fail',
|
|
235
|
+
registered: 0,
|
|
236
|
+
managed: 0,
|
|
237
|
+
unmanaged: 0,
|
|
238
|
+
issues: [{ code: error.code || 'ARTIFACT_HEALTH_UNAVAILABLE', message: error.message }],
|
|
239
|
+
counts: {},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function checkpointEvidence(checkpoints, stage, changeId) {
|
|
245
|
+
const checkpoint = checkpoints.current.find((item) => (
|
|
246
|
+
item.stage === stage
|
|
247
|
+
&& item.status === 'accepted'
|
|
248
|
+
&& (!changeId || item.scope_id === changeId || item.payload?.change_id === changeId)
|
|
249
|
+
));
|
|
250
|
+
if (!checkpoint) {
|
|
251
|
+
return {
|
|
252
|
+
checkpoint_id: null,
|
|
253
|
+
status: 'missing',
|
|
254
|
+
report_path: null,
|
|
255
|
+
digest: null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
let status = 'accepted';
|
|
259
|
+
if (stage === 'test' && typeof checkpoint.payload.passed === 'boolean') {
|
|
260
|
+
status = checkpoint.payload.passed ? 'pass' : 'fail';
|
|
261
|
+
} else if (stage === 'review' && checkpoint.payload.verdict) {
|
|
262
|
+
status = checkpoint.payload.verdict;
|
|
263
|
+
} else if (stage === 'deliver' && checkpoint.payload.archive_status) {
|
|
264
|
+
status = checkpoint.payload.archive_status === 'archived' ? 'delivered' : 'incomplete';
|
|
265
|
+
}
|
|
266
|
+
const reference = checkpoint.payload.report_path
|
|
267
|
+
|| checkpoint.evidence.find((item) => item && (item.ref || item.path))?.ref
|
|
268
|
+
|| checkpoint.evidence.find((item) => item && item.path)?.path
|
|
269
|
+
|| null;
|
|
270
|
+
return {
|
|
271
|
+
checkpoint_id: checkpoint.id,
|
|
272
|
+
status,
|
|
273
|
+
report_path: reference,
|
|
274
|
+
digest: checkpoint.digest,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function buildStatusPanel(db, {
|
|
279
|
+
since = null,
|
|
280
|
+
limit = 3,
|
|
281
|
+
rootDir = process.cwd(),
|
|
282
|
+
} = {}) {
|
|
75
283
|
const tables = new Set(db.prepare(
|
|
76
284
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
|
|
77
285
|
).all().map((row) => row.name));
|
|
78
|
-
const
|
|
79
|
-
? ops.aggregateTelemetryByRuntime(db, { since })
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
286
|
+
const byRuntime = tables.has('telemetry') && tables.has('sessions')
|
|
287
|
+
? ops.aggregateTelemetryByRuntime(db, { since })
|
|
288
|
+
: [];
|
|
289
|
+
const topTasks = tables.has('telemetry') && tables.has('sessions')
|
|
290
|
+
? ops.aggregateTelemetryByTask(db, { since, limit })
|
|
291
|
+
: [];
|
|
292
|
+
const knownTotalCost = byRuntime.reduce((sum, row) => sum + (row.cost_usd || 0), 0);
|
|
293
|
+
const unpricedUsageEvents = byRuntime.reduce(
|
|
294
|
+
(sum, row) => sum + Number(row.unpriced_usage_events || 0),
|
|
295
|
+
0,
|
|
85
296
|
);
|
|
86
|
-
const pricedUsageEvents =
|
|
87
|
-
(
|
|
297
|
+
const pricedUsageEvents = byRuntime.reduce(
|
|
298
|
+
(sum, row) => sum + Number(row.priced_usage_events || 0),
|
|
299
|
+
0,
|
|
88
300
|
);
|
|
89
301
|
const costStatus = unpricedUsageEvents > 0
|
|
90
302
|
? (pricedUsageEvents > 0 ? 'partial' : 'unavailable')
|
|
91
303
|
: (pricedUsageEvents > 0 ? 'complete' : 'none');
|
|
92
|
-
const
|
|
304
|
+
const totalCost = unpricedUsageEvents > 0 ? null : knownTotalCost;
|
|
305
|
+
|
|
93
306
|
const baselineHealth = baselines.inspectBaseline(db, { rootDir });
|
|
94
|
-
const
|
|
95
|
-
const baselineStatus =
|
|
307
|
+
const currentBaseline = baselineHealth.baseline;
|
|
308
|
+
const baselineStatus = currentBaseline?.status
|
|
96
309
|
|| (baselineHealth.blockers.includes('BASELINE_SCHEMA_MIGRATION_REQUIRED')
|
|
97
|
-
? 'migration_required'
|
|
310
|
+
? 'migration_required'
|
|
311
|
+
: 'missing');
|
|
98
312
|
const baseline = {
|
|
99
|
-
id:
|
|
100
|
-
mode:
|
|
313
|
+
id: currentBaseline?.id || null,
|
|
314
|
+
mode: currentBaseline?.mode || null,
|
|
101
315
|
status: baselineStatus,
|
|
102
316
|
health: baselineHealth.status,
|
|
103
|
-
repository_revision:
|
|
317
|
+
repository_revision: currentBaseline?.repository_revision || null,
|
|
104
318
|
blockers: baselineHealth.blockers,
|
|
105
319
|
warnings: baselineHealth.warnings,
|
|
106
320
|
};
|
|
107
|
-
if (
|
|
108
|
-
baseline.repository_branch =
|
|
109
|
-
baseline.worktree_state =
|
|
110
|
-
baseline.gaps = baselines.summarizeGaps(
|
|
111
|
-
|
|
112
|
-
const research = workflowState.readWorkflow(db, current.research_run_id, { rootDir });
|
|
113
|
-
baseline.research = research ? {
|
|
114
|
-
id: research.id,
|
|
115
|
-
status: research.status,
|
|
116
|
-
mode: research.mode,
|
|
117
|
-
output_health: research.artifact_health.status,
|
|
118
|
-
blockers: research.artifact_health.blockers,
|
|
119
|
-
} : {
|
|
120
|
-
id: current.research_run_id, status: 'missing', mode: null,
|
|
121
|
-
output_health: 'fail', blockers: ['BASELINE_RESEARCH_RECORD_INVALID'],
|
|
122
|
-
};
|
|
123
|
-
}
|
|
321
|
+
if (currentBaseline) {
|
|
322
|
+
baseline.repository_branch = currentBaseline.repository_branch || null;
|
|
323
|
+
baseline.worktree_state = currentBaseline.worktree_state || 'unavailable';
|
|
324
|
+
baseline.gaps = baselines.summarizeGaps(currentBaseline.gaps);
|
|
325
|
+
baseline.research_checkpoint_id = currentBaseline.research_checkpoint_id || null;
|
|
124
326
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const rows = db.prepare(
|
|
133
|
-
`SELECT status, COUNT(*) AS count FROM ${table} ${where} GROUP BY status`,
|
|
134
|
-
).all();
|
|
135
|
-
const counts = Object.fromEntries(statuses.map((status) => [status, 0]));
|
|
136
|
-
for (const row of rows) if (row.status in counts) counts[row.status] = Number(row.count);
|
|
137
|
-
return counts;
|
|
138
|
-
};
|
|
327
|
+
|
|
328
|
+
const contextResult = readContext(db, tables, rootDir);
|
|
329
|
+
const context = contextResult.value;
|
|
330
|
+
const authority = context?.envelope || null;
|
|
331
|
+
const checkpoints = checkpointPanel(db, tables);
|
|
332
|
+
const decisions = decisionPanel(db, tables, context);
|
|
333
|
+
const artifacts = artifactPanel(db, tables, rootDir);
|
|
139
334
|
const taskCounts = countStatuses(
|
|
140
|
-
|
|
335
|
+
db,
|
|
336
|
+
tables,
|
|
337
|
+
'tasks',
|
|
338
|
+
['pending', 'in_progress', 'completed', 'blocked', 'expanded'],
|
|
141
339
|
);
|
|
142
340
|
taskCounts.stale = tables.has('tasks')
|
|
143
|
-
? Number(db.prepare('SELECT COUNT(*) AS count FROM tasks WHERE stale = 1').get().count)
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
if (!Array.isArray(workflows.current)) workflows.current = [];
|
|
173
|
-
const decisions = tables.has('decision_threads') && tables.has('decision_items')
|
|
174
|
-
? decisionDialogue.inspectDecisionHealth(db, { rootDir })
|
|
175
|
-
: {
|
|
176
|
-
status: 'unavailable', active: 0, completed: 0, awaiting_owner: 0,
|
|
177
|
-
awaiting_blocking: 0, checkpoint_ready: 0,
|
|
178
|
-
deferred_blocking: 0, stale_artifacts: [], current: null, current_thread_id: null,
|
|
179
|
-
};
|
|
180
|
-
const artifactTables = [
|
|
181
|
-
'artifacts', 'artifact_edges', 'baselines', 'workflow_steps',
|
|
182
|
-
'decision_threads', 'context_snapshots',
|
|
183
|
-
];
|
|
184
|
-
const artifacts = artifactTables.every((table) => tables.has(table))
|
|
185
|
-
? artifactRegistry.inspectArtifactHealth(db, { rootDir })
|
|
186
|
-
: {
|
|
187
|
-
status: 'unavailable', registered: 0, managed: 0, unmanaged: 0,
|
|
188
|
-
issues: [], counts: {},
|
|
189
|
-
};
|
|
190
|
-
let transitions;
|
|
191
|
-
let breadcrumb = null;
|
|
192
|
-
try {
|
|
193
|
-
if (!tables.has('baselines')) {
|
|
194
|
-
transitions = {
|
|
195
|
-
allowed: ['ultra-init', 'ultra-status'], required: 'ultra-init', readiness: 'blocked',
|
|
196
|
-
blockers: ['BASELINE_SCHEMA_MIGRATION_REQUIRED'], warnings: [],
|
|
197
|
-
};
|
|
198
|
-
} else {
|
|
199
|
-
breadcrumb = readBreadcrumb(db, {}, { rootDir });
|
|
200
|
-
transitions = {
|
|
201
|
-
allowed: breadcrumb.allowed_transitions,
|
|
202
|
-
required: breadcrumb.required_transition,
|
|
203
|
-
readiness: breadcrumb.readiness,
|
|
204
|
-
blockers: breadcrumb.blockers,
|
|
205
|
-
warnings: breadcrumb.warnings,
|
|
206
|
-
change_id: breadcrumb.change_id || null,
|
|
207
|
-
task_id: breadcrumb.task_id || null,
|
|
208
|
-
workflow: breadcrumb.workflow || null,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
} catch (error) {
|
|
212
|
-
transitions = {
|
|
213
|
-
allowed: ['ultra-doctor', 'ultra-status'], required: 'ultra-doctor', readiness: 'blocked',
|
|
214
|
-
blockers: [`STATUS_AUTHORITY_UNAVAILABLE:${error.code || 'unknown'}`], warnings: [],
|
|
215
|
-
};
|
|
216
|
-
}
|
|
341
|
+
? Number(db.prepare('SELECT COUNT(*) AS count FROM tasks WHERE stale = 1').get().count)
|
|
342
|
+
: 0;
|
|
343
|
+
const sessions = countStatuses(
|
|
344
|
+
db,
|
|
345
|
+
tables,
|
|
346
|
+
'sessions',
|
|
347
|
+
['running', 'completed', 'crashed', 'orphan'],
|
|
348
|
+
);
|
|
349
|
+
const changes = countStatuses(
|
|
350
|
+
db,
|
|
351
|
+
tables,
|
|
352
|
+
'changes',
|
|
353
|
+
['active', 'blocked', 'ready', 'archived', 'cancelled'],
|
|
354
|
+
);
|
|
355
|
+
const currentChange = authority?.change || null;
|
|
356
|
+
const currentTask = authority?.task || null;
|
|
357
|
+
const changeId = currentChange?.id || null;
|
|
358
|
+
const evidence = {
|
|
359
|
+
test: checkpointEvidence(checkpoints, 'test', changeId),
|
|
360
|
+
review: checkpointEvidence(checkpoints, 'review', changeId),
|
|
361
|
+
delivery: checkpointEvidence(checkpoints, 'deliver', changeId),
|
|
362
|
+
};
|
|
363
|
+
const diagnostics = authority?.diagnostics || {
|
|
364
|
+
warnings: [],
|
|
365
|
+
needs_attention: contextResult.error ? [contextResult.error] : [],
|
|
366
|
+
hard_conflicts: [],
|
|
367
|
+
};
|
|
217
368
|
|
|
218
|
-
const currentChange = tables.has('changes')
|
|
219
|
-
? db.prepare(
|
|
220
|
-
`SELECT id, title, kind, status, intent, base_commit, artifact_root, updated_at
|
|
221
|
-
FROM changes
|
|
222
|
-
WHERE status IN ('active', 'blocked', 'ready')
|
|
223
|
-
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
224
|
-
).get() || null
|
|
225
|
-
: null;
|
|
226
|
-
const evidenceChangeId = breadcrumb?.change_id || currentChange?.id
|
|
227
|
-
|| (tables.has('changes') ? db.prepare(
|
|
228
|
-
`SELECT id FROM changes WHERE status = 'archived'
|
|
229
|
-
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
230
|
-
).get()?.id : null);
|
|
231
|
-
let currentTask = null;
|
|
232
|
-
if (tables.has('tasks')) {
|
|
233
|
-
const taskId = breadcrumb?.task_id || (currentChange?.id ? db.prepare(
|
|
234
|
-
`SELECT id FROM tasks WHERE change_id = ?
|
|
235
|
-
ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1
|
|
236
|
-
WHEN 'pending' THEN 2 ELSE 3 END, priority ASC, created_at ASC LIMIT 1`,
|
|
237
|
-
).get(currentChange.id)?.id : null);
|
|
238
|
-
if (taskId) currentTask = ops.readTask(db, taskId);
|
|
239
|
-
}
|
|
240
|
-
const evidence = {};
|
|
241
|
-
for (const kind of ['test', 'review', 'deliver']) {
|
|
242
|
-
const run = evidenceChangeId && tables.has('workflow_runs') && tables.has('workflow_steps')
|
|
243
|
-
? workflowState.listWorkflows(
|
|
244
|
-
db, { kind, status: 'completed', change_id: evidenceChangeId, limit: 1 }, { rootDir },
|
|
245
|
-
)[0]
|
|
246
|
-
: null;
|
|
247
|
-
let status = 'missing';
|
|
248
|
-
if (run && kind === 'test') status = run.summary.passed === true ? 'pass' : 'fail';
|
|
249
|
-
if (run && kind === 'review') status = run.summary.verdict || 'unknown';
|
|
250
|
-
if (run && kind === 'deliver') {
|
|
251
|
-
status = run.summary.archive_status === 'archived' ? 'delivered' : 'incomplete';
|
|
252
|
-
}
|
|
253
|
-
const evidenceKey = kind === 'deliver' ? 'delivery' : kind;
|
|
254
|
-
evidence[evidenceKey] = run ? {
|
|
255
|
-
workflow_id: run.id, status, output_health: run.artifact_health.status,
|
|
256
|
-
report_path: run.summary.report_path || null,
|
|
257
|
-
} : { workflow_id: null, status: 'missing', output_health: 'missing', report_path: null };
|
|
258
|
-
}
|
|
259
369
|
return {
|
|
260
370
|
period: { since: since || 'all-time' },
|
|
371
|
+
context_envelope: context,
|
|
372
|
+
context_error: contextResult.error,
|
|
261
373
|
baseline,
|
|
262
|
-
|
|
374
|
+
checkpoints,
|
|
263
375
|
decisions,
|
|
264
376
|
artifacts,
|
|
265
377
|
current_change: currentChange,
|
|
@@ -268,10 +380,15 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
|
|
|
268
380
|
changes,
|
|
269
381
|
tasks: taskCounts,
|
|
270
382
|
sessions,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
383
|
+
guidance: {
|
|
384
|
+
recommendation_owner: 'host_model',
|
|
385
|
+
selection_owner: 'user',
|
|
386
|
+
automatic_invocation: false,
|
|
387
|
+
diagnostics,
|
|
388
|
+
},
|
|
389
|
+
by_runtime: byRuntime,
|
|
390
|
+
top_tasks: topTasks,
|
|
391
|
+
total_cost_usd: totalCost,
|
|
275
392
|
cost: {
|
|
276
393
|
status: costStatus,
|
|
277
394
|
known_total_usd: knownTotalCost,
|
|
@@ -281,38 +398,36 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
|
|
|
281
398
|
};
|
|
282
399
|
}
|
|
283
400
|
|
|
284
|
-
// Backward-compatible internal
|
|
401
|
+
// Backward-compatible internal name for cost-panel consumers.
|
|
285
402
|
const buildCostPanel = buildStatusPanel;
|
|
286
403
|
|
|
287
|
-
function formatCost(
|
|
288
|
-
if (!
|
|
289
|
-
if (
|
|
290
|
-
return `$${
|
|
404
|
+
function formatCost(value) {
|
|
405
|
+
if (!value) return '$0.0000';
|
|
406
|
+
if (value < 0.01) return `$${value.toFixed(6)}`;
|
|
407
|
+
return `$${value.toFixed(4)}`;
|
|
291
408
|
}
|
|
292
409
|
|
|
293
410
|
function renderHuman(panel) {
|
|
294
411
|
const lines = [];
|
|
295
412
|
const baseline = panel.baseline;
|
|
296
413
|
lines.push(
|
|
297
|
-
`Baseline: ${baseline.mode || 'none'}/${baseline.status} ·
|
|
298
|
-
+
|
|
414
|
+
`Baseline: ${baseline.mode || 'none'}/${baseline.status} · `
|
|
415
|
+
+ `${baseline.health === 'pass' ? 'ready' : 'needs attention'}`,
|
|
299
416
|
);
|
|
300
|
-
if (baseline.blockers.length > 0)
|
|
417
|
+
if (baseline.blockers.length > 0) {
|
|
418
|
+
lines.push(`Baseline blockers: ${baseline.blockers.join(', ')}`);
|
|
419
|
+
}
|
|
301
420
|
lines.push(
|
|
302
|
-
`
|
|
421
|
+
`Checkpoints: draft=${panel.checkpoints.draft} accepted=${panel.checkpoints.accepted} `
|
|
422
|
+
+ `superseded=${panel.checkpoints.superseded} health=${panel.checkpoints.status}`,
|
|
303
423
|
);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
lines.push(
|
|
312
|
-
`Decision: ${panel.decisions.current.id} (${panel.decisions.current.phase}) — ${panel.decisions.current.question}`,
|
|
313
|
-
);
|
|
314
|
-
} else if (panel.decisions.checkpoint_ready > 0) {
|
|
315
|
-
lines.push(`Decision checkpoint: ${panel.decisions.current_thread_id} awaits confirmation`);
|
|
424
|
+
lines.push(
|
|
425
|
+
`Decisions: accepted=${panel.decisions.accepted} `
|
|
426
|
+
+ `superseded=${panel.decisions.superseded}`,
|
|
427
|
+
);
|
|
428
|
+
if (panel.decisions.current.length > 0) {
|
|
429
|
+
const current = panel.decisions.current[0];
|
|
430
|
+
lines.push(`Decision: ${current.id} — ${current.selection}`);
|
|
316
431
|
}
|
|
317
432
|
lines.push(
|
|
318
433
|
`Artifacts: registered=${panel.artifacts.registered} managed=${panel.artifacts.managed}`
|
|
@@ -320,27 +435,35 @@ function renderHuman(panel) {
|
|
|
320
435
|
+ (panel.artifacts.issues.length > 0 ? ` issues=${panel.artifacts.issues.length}` : ''),
|
|
321
436
|
);
|
|
322
437
|
lines.push(
|
|
323
|
-
`Changes: active=${panel.changes.active}
|
|
438
|
+
`Changes: active=${panel.changes.active} archived=${panel.changes.archived} `
|
|
439
|
+
+ `cancelled=${panel.changes.cancelled}`,
|
|
324
440
|
);
|
|
325
441
|
lines.push(
|
|
326
|
-
`Tasks: pending=${panel.tasks.pending} in_progress=${panel.tasks.in_progress}
|
|
442
|
+
`Tasks: pending=${panel.tasks.pending} in_progress=${panel.tasks.in_progress} `
|
|
443
|
+
+ `completed=${panel.tasks.completed} blocked=${panel.tasks.blocked} stale=${panel.tasks.stale}`,
|
|
327
444
|
);
|
|
328
445
|
if (panel.current_change) {
|
|
329
446
|
lines.push(`Current change: ${panel.current_change.id} (${panel.current_change.status})`);
|
|
330
447
|
}
|
|
331
|
-
if (panel.current_task)
|
|
448
|
+
if (panel.current_task) {
|
|
449
|
+
lines.push(`Current task: ${panel.current_task.id} (${panel.current_task.status})`);
|
|
450
|
+
}
|
|
451
|
+
lines.push(
|
|
452
|
+
`Sessions: running=${panel.sessions.running} crashed=${panel.sessions.crashed} `
|
|
453
|
+
+ `orphan=${panel.sessions.orphan}`,
|
|
454
|
+
);
|
|
332
455
|
lines.push(
|
|
333
|
-
|
|
456
|
+
'Route selection: host model recommends; user explicitly selects; Ultra does not auto-chain.',
|
|
334
457
|
);
|
|
335
|
-
lines.push(`Allowed transitions: ${panel.transitions.allowed.join(', ') || 'none'}`);
|
|
336
|
-
if (panel.transitions.required) lines.push(`Required transition: ${panel.transitions.required}`);
|
|
337
458
|
lines.push(
|
|
338
|
-
`Evidence: test=${panel.evidence.test.status} review=${panel.evidence.review.status}
|
|
459
|
+
`Evidence: test=${panel.evidence.test.status} review=${panel.evidence.review.status} `
|
|
460
|
+
+ `delivery=${panel.evidence.delivery.status}`,
|
|
339
461
|
);
|
|
340
462
|
lines.push(`Period: ${panel.period.since}`);
|
|
341
463
|
if (panel.total_cost_usd === null) {
|
|
342
464
|
lines.push(
|
|
343
|
-
`Total cost: unavailable (${panel.cost.unpriced_usage_events} unpriced usage events;
|
|
465
|
+
`Total cost: unavailable (${panel.cost.unpriced_usage_events} unpriced usage events; `
|
|
466
|
+
+ `known cost ${formatCost(panel.cost.known_total_usd)})`,
|
|
344
467
|
);
|
|
345
468
|
} else {
|
|
346
469
|
lines.push(`Total cost: ${formatCost(panel.total_cost_usd)}`);
|
|
@@ -351,13 +474,13 @@ function renderHuman(panel) {
|
|
|
351
474
|
lines.push(' (no telemetry)');
|
|
352
475
|
} else {
|
|
353
476
|
lines.push(' runtime calls tokens_in tokens_out cost');
|
|
354
|
-
for (const
|
|
477
|
+
for (const runtime of panel.by_runtime) {
|
|
355
478
|
lines.push(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
479
|
+
` ${runtime.runtime.padEnd(10)}`
|
|
480
|
+
+ `${String(runtime.calls).padStart(6)}`
|
|
481
|
+
+ `${String(runtime.tokens_in).padStart(12)}`
|
|
482
|
+
+ `${String(runtime.tokens_out).padStart(12)}`
|
|
483
|
+
+ ` ${formatCost(runtime.cost_usd)}`,
|
|
361
484
|
);
|
|
362
485
|
}
|
|
363
486
|
}
|
|
@@ -366,11 +489,13 @@ function renderHuman(panel) {
|
|
|
366
489
|
if (panel.top_tasks.length === 0) {
|
|
367
490
|
lines.push(' (no task-scoped telemetry)');
|
|
368
491
|
} else {
|
|
369
|
-
for (const
|
|
370
|
-
lines.push(
|
|
492
|
+
for (const task of panel.top_tasks) {
|
|
493
|
+
lines.push(
|
|
494
|
+
` ${task.task_id.padEnd(24)} calls=${task.calls} cost=${formatCost(task.cost_usd)}`,
|
|
495
|
+
);
|
|
371
496
|
}
|
|
372
497
|
}
|
|
373
|
-
return lines.join('\n')
|
|
498
|
+
return `${lines.join('\n')}\n`;
|
|
374
499
|
}
|
|
375
500
|
|
|
376
501
|
function resolveDbPath(rootDir = process.cwd(), env = process.env) {
|
|
@@ -384,7 +509,9 @@ function resolveRootDir(dbPath) {
|
|
|
384
509
|
|
|
385
510
|
function dispatch(args) {
|
|
386
511
|
let flags;
|
|
387
|
-
try { flags = parseFlags(args); } catch (
|
|
512
|
+
try { flags = parseFlags(args); } catch (error) {
|
|
513
|
+
return fail('USAGE_ERROR', error.message);
|
|
514
|
+
}
|
|
388
515
|
if (flags.help) {
|
|
389
516
|
process.stdout.write(USAGE);
|
|
390
517
|
return 0;
|
|
@@ -402,18 +529,17 @@ function dispatch(args) {
|
|
|
402
529
|
return fail('STATE_DB_MISSING', `state.db not found at ${dbPath}`);
|
|
403
530
|
}
|
|
404
531
|
let since;
|
|
405
|
-
try { since = parseSince(flags.since); } catch (
|
|
532
|
+
try { since = parseSince(flags.since); } catch (error) {
|
|
533
|
+
return fail('USAGE_ERROR', error.message);
|
|
534
|
+
}
|
|
406
535
|
|
|
407
536
|
const Database = require('better-sqlite3');
|
|
408
537
|
const db = new Database(dbPath, { readonly: true });
|
|
409
538
|
try {
|
|
410
539
|
const rootDir = resolveRootDir(dbPath);
|
|
411
540
|
const panel = buildStatusPanel(db, { since, limit: flags.limit, rootDir });
|
|
412
|
-
if (flags.json) {
|
|
413
|
-
|
|
414
|
-
} else {
|
|
415
|
-
process.stdout.write(renderHuman(panel));
|
|
416
|
-
}
|
|
541
|
+
if (flags.json) emit({ ok: true, data: panel });
|
|
542
|
+
else process.stdout.write(renderHuman(panel));
|
|
417
543
|
return 0;
|
|
418
544
|
} finally {
|
|
419
545
|
db.close();
|