ultra-builder-pro-cli 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.ultra-template/docs/research/README.md +4 -4
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +68 -35
  4. package/adapters/_shared/codex-assets.cjs +121 -60
  5. package/adapters/_shared/interaction-contract.cjs +20 -7
  6. package/adapters/_shared/provenance.cjs +7 -2
  7. package/adapters/_shared/runtime-assets.cjs +4 -3
  8. package/adapters/_shared/runtime-native.cjs +132 -0
  9. package/adapters/claude.js +61 -31
  10. package/adapters/codex.js +178 -81
  11. package/adapters/grok.js +372 -0
  12. package/adapters/kimi.js +8 -4
  13. package/adapters/opencode.js +211 -33
  14. package/agents/code-reviewer.md +4 -2
  15. package/agents/debugger.md +4 -1
  16. package/agents/review-code.md +7 -3
  17. package/agents/review-comments.md +7 -4
  18. package/agents/review-coordinator.md +11 -7
  19. package/agents/review-design.md +8 -4
  20. package/agents/review-errors.md +8 -4
  21. package/agents/review-spec.md +8 -5
  22. package/agents/review-tests.md +7 -4
  23. package/agents/tdd-runner.md +5 -2
  24. package/bin/install.js +9 -4
  25. package/bin/orchestrator.js +4 -13
  26. package/docs/AGENT-CONTEXT.md +141 -231
  27. package/docs/ARCHITECTURE.md +203 -498
  28. package/docs/ARTIFACT-AUTHORITY.md +38 -35
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
  30. package/docs/ROADMAP.md +85 -110
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +139 -174
  32. package/docs/SKILL-AUTHORING.md +3 -3
  33. package/docs/STATE-DB-ACCESS-POLICY.md +25 -20
  34. package/docs/WORKFLOW-LIFECYCLE.md +174 -442
  35. package/hooks/README.md +11 -8
  36. package/hooks/active_task_context.py +16 -16
  37. package/hooks/adapters/grok.py +134 -0
  38. package/hooks/{context_spine.py → context_envelope.py} +26 -22
  39. package/hooks/health_check.py +19 -29
  40. package/hooks/pre_stop_check.py +22 -12
  41. package/hooks/subagent_tracker.py +1 -1
  42. package/hooks/workflow_checkpoint.py +14 -13
  43. package/hooks/workflow_context.py +11 -12
  44. package/hooks/workflow_resume.py +14 -13
  45. package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
  46. package/mcp-server/lib/artifact-registry.cjs +25 -1
  47. package/mcp-server/lib/baseline-workflow.cjs +36 -38
  48. package/mcp-server/lib/canonical-json.cjs +19 -0
  49. package/mcp-server/lib/change-workflow.cjs +637 -49
  50. package/mcp-server/lib/context-envelope.cjs +508 -0
  51. package/mcp-server/lib/decision-records.cjs +337 -0
  52. package/mcp-server/lib/doctor.cjs +91 -33
  53. package/mcp-server/lib/init-project.cjs +78 -39
  54. package/mcp-server/lib/managed-file-write.cjs +116 -0
  55. package/mcp-server/lib/plan-checkpoint.cjs +177 -0
  56. package/mcp-server/lib/project-context.cjs +178 -0
  57. package/mcp-server/lib/stage-checkpoints.cjs +309 -0
  58. package/mcp-server/lib/state-db.cjs +155 -24
  59. package/mcp-server/lib/task-ledger.cjs +522 -13
  60. package/mcp-server/lib/ultra-facade.cjs +834 -0
  61. package/mcp-server/lib/worker-packet.cjs +358 -0
  62. package/mcp-server/server.cjs +208 -1059
  63. package/orchestrator/daemon.cjs +27 -9
  64. package/orchestrator/parallel-orchestrator.cjs +111 -76
  65. package/orchestrator/session-runner.cjs +135 -34
  66. package/package.json +10 -3
  67. package/skills/ultra-change/SKILL.md +39 -98
  68. package/skills/ultra-deliver/SKILL.md +42 -65
  69. package/skills/ultra-dev/SKILL.md +52 -99
  70. package/skills/ultra-doctor/SKILL.md +35 -57
  71. package/skills/ultra-init/SKILL.md +48 -88
  72. package/skills/ultra-init/references/brownfield-adoption.md +11 -8
  73. package/skills/ultra-plan/SKILL.md +54 -132
  74. package/skills/ultra-research/SKILL.md +58 -112
  75. package/skills/ultra-review/SKILL.md +28 -46
  76. package/skills/ultra-review/references/unified-schema.md +2 -0
  77. package/skills/ultra-review/scripts/review_wait.py +8 -0
  78. package/skills/ultra-status/SKILL.md +22 -45
  79. package/skills/ultra-test/SKILL.md +27 -61
  80. package/skills/ultra-think/SKILL.md +29 -48
  81. package/skills/ultra-think/references/interaction-boundary.md +56 -0
  82. package/spec/cli-protocol.md +80 -187
  83. package/spec/mcp-tools.yaml +130 -2738
  84. package/spec/schemas/mcp-tools.schema.json +1 -1
  85. package/spec/schemas/state-db.sql +107 -4
  86. package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
  87. package/ultra-tools/commands/status.cjs +369 -243
  88. package/mcp-server/lib/context-spine.cjs +0 -1208
  89. package/mcp-server/lib/decision-dialogue.cjs +0 -1128
  90. package/mcp-server/lib/project-breadcrumb.cjs +0 -173
  91. package/mcp-server/lib/spec-learning.cjs +0 -284
  92. package/mcp-server/lib/workflow-state.cjs +0 -2965
  93. package/skills/ultra-think/references/decision-dialogue.md +0 -153
@@ -2,25 +2,25 @@
2
2
 
3
3
  // Ultra project status panel.
4
4
  //
5
- // Reads project authority and telemetry from state.db and renders either a
6
- // human-readable report or a JSON envelope consumable by skills/ultra-status.
7
- // Does not modify state.
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 { readBreadcrumb } = require('../../mcp-server/lib/context-spine.cjs');
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 baseline, workflow, task, session, valid transitions, and
23
- cost-observability status.
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 s = String(value).trim();
45
- if (/^\d{4}-\d{2}-\d{2}T/.test(s)) return s;
46
- const m = s.match(/^(\d+)([dhm])$/i);
47
- if (!m) throw new Error(`invalid --since value: ${value} (expected 7d, 24h, 30m, or ISO-8601)`);
48
- const n = Number(m[1]);
49
- const unit = m[2].toLowerCase();
50
- const ms = unit === 'd' ? n * 86400000 : unit === 'h' ? n * 3600000 : n * 60000;
51
- return new Date(nowMs - ms).toISOString();
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 i = 0; i < args.length; i++) {
57
- const a = args[i];
58
- switch (a) {
59
- case '--cost': out.cost = true; break;
60
- case '--json': out.json = true; break;
61
- case '--since': out.since = args[++i]; break;
62
- case '--limit': out.limit = Number(args[++i]); break;
63
- case '-h': case '--help': return { help: true };
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 (a.startsWith('--since=')) out.since = a.split('=')[1];
66
- else if (a.startsWith('--limit=')) out.limit = Number(a.split('=')[1]);
67
- else throw new Error(`unknown flag: ${a}`);
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 buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd() } = {}) {
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 by_runtime = tables.has('telemetry') && tables.has('sessions')
79
- ? ops.aggregateTelemetryByRuntime(db, { since }) : [];
80
- const top_tasks = tables.has('telemetry') && tables.has('sessions')
81
- ? ops.aggregateTelemetryByTask(db, { since, limit }) : [];
82
- const knownTotalCost = by_runtime.reduce((acc, r) => acc + (r.cost_usd || 0), 0);
83
- const unpricedUsageEvents = by_runtime.reduce(
84
- (acc, row) => acc + Number(row.unpriced_usage_events || 0), 0,
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 = by_runtime.reduce(
87
- (acc, row) => acc + Number(row.priced_usage_events || 0), 0,
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 total_cost_usd = unpricedUsageEvents > 0 ? null : knownTotalCost;
304
+ const totalCost = unpricedUsageEvents > 0 ? null : knownTotalCost;
305
+
93
306
  const baselineHealth = baselines.inspectBaseline(db, { rootDir });
94
- const current = baselineHealth.baseline;
95
- const baselineStatus = current?.status
307
+ const currentBaseline = baselineHealth.baseline;
308
+ const baselineStatus = currentBaseline?.status
96
309
  || (baselineHealth.blockers.includes('BASELINE_SCHEMA_MIGRATION_REQUIRED')
97
- ? 'migration_required' : 'missing');
310
+ ? 'migration_required'
311
+ : 'missing');
98
312
  const baseline = {
99
- id: current?.id || null,
100
- mode: current?.mode || null,
313
+ id: currentBaseline?.id || null,
314
+ mode: currentBaseline?.mode || null,
101
315
  status: baselineStatus,
102
316
  health: baselineHealth.status,
103
- repository_revision: current?.repository_revision || null,
317
+ repository_revision: currentBaseline?.repository_revision || null,
104
318
  blockers: baselineHealth.blockers,
105
319
  warnings: baselineHealth.warnings,
106
320
  };
107
- if (current) {
108
- baseline.repository_branch = current.repository_branch || null;
109
- baseline.worktree_state = current.worktree_state || 'unavailable';
110
- baseline.gaps = baselines.summarizeGaps(current.gaps);
111
- if (current.research_run_id && tables.has('workflow_runs') && tables.has('workflow_steps')) {
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
- const countStatuses = (table, statuses, where = '') => {
126
- if (!tables.has(table)) {
127
- return {
128
- status: 'unavailable',
129
- ...Object.fromEntries(statuses.map((status) => [status, 0])),
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
- 'tasks', ['pending', 'in_progress', 'completed', 'blocked', 'expanded'],
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) : 0;
144
- const sessions = countStatuses('sessions', ['running', 'completed', 'crashed', 'orphan']);
145
- const changes = countStatuses('changes', ['active', 'blocked', 'ready', 'archived', 'cancelled']);
146
- let workflows;
147
- try {
148
- if (!tables.has('workflow_runs') || !tables.has('workflow_steps')) {
149
- workflows = {
150
- status: 'unavailable', active: 0, blocked: 0, ready: 0,
151
- stale_outputs: [], historical_stale_outputs: [],
152
- terminal_authority_runs: [], untracked_active_changes: [],
153
- };
154
- } else {
155
- workflows = workflowState.inspectWorkflowHealth(db, { rootDir });
156
- workflows.current = workflowState.listWorkflows(db, { limit: 500 }, { rootDir })
157
- .filter((run) => ['active', 'blocked', 'ready'].includes(run.status))
158
- .map((run) => ({
159
- id: run.id, kind: run.kind, status: run.status, current_step: run.current_step,
160
- baseline_id: run.baseline_id, change_id: run.change_id, task_id: run.task_id,
161
- blockers: run.blockers, output_health: run.artifact_health.status,
162
- }));
163
- }
164
- } catch (error) {
165
- workflows = {
166
- status: 'fail', active: 0, blocked: 0, ready: 0,
167
- stale_outputs: [], historical_stale_outputs: [],
168
- terminal_authority_runs: [], untracked_active_changes: [],
169
- error: error.code || error.message,
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
- workflows,
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
- transitions,
272
- by_runtime,
273
- top_tasks,
274
- total_cost_usd,
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 export for consumers that used the old name.
401
+ // Backward-compatible internal name for cost-panel consumers.
285
402
  const buildCostPanel = buildStatusPanel;
286
403
 
287
- function formatCost(n) {
288
- if (!n) return '$0.0000';
289
- if (n < 0.01) return `$${n.toFixed(6)}`;
290
- return `$${n.toFixed(4)}`;
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} · ${baseline.health === 'pass' ? 'ready' : 'blocked'}`
298
- + (baseline.research ? ` · research=${baseline.research.id}/${baseline.research.status}/${baseline.research.output_health}` : ''),
414
+ `Baseline: ${baseline.mode || 'none'}/${baseline.status} · `
415
+ + `${baseline.health === 'pass' ? 'ready' : 'needs attention'}`,
299
416
  );
300
- if (baseline.blockers.length > 0) lines.push(`Baseline blockers: ${baseline.blockers.join(', ')}`);
417
+ if (baseline.blockers.length > 0) {
418
+ lines.push(`Baseline blockers: ${baseline.blockers.join(', ')}`);
419
+ }
301
420
  lines.push(
302
- `Workflows: active=${panel.workflows.active} blocked=${panel.workflows.blocked} ready=${panel.workflows.ready} health=${panel.workflows.status}`,
421
+ `Checkpoints: draft=${panel.checkpoints.draft} accepted=${panel.checkpoints.accepted} `
422
+ + `superseded=${panel.checkpoints.superseded} health=${panel.checkpoints.status}`,
303
423
  );
304
- if (panel.workflows.current.length > 0) {
305
- const current = panel.workflows.current[0];
306
- lines.push(
307
- `Workflow: ${current.id} (${current.kind}/${current.status}) · step=${current.current_step || 'finalize'} · outputs=${current.output_health}`,
308
- );
309
- }
310
- if (panel.decisions.current) {
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} blocked=${panel.changes.blocked} ready=${panel.changes.ready} archived=${panel.changes.archived}`,
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} completed=${panel.tasks.completed} blocked=${panel.tasks.blocked} stale=${panel.tasks.stale}`,
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) lines.push(`Current task: ${panel.current_task.id} (${panel.current_task.status})`);
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
- `Sessions: running=${panel.sessions.running} crashed=${panel.sessions.crashed} orphan=${panel.sessions.orphan}`,
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} delivery=${panel.evidence.delivery.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; known cost ${formatCost(panel.cost.known_total_usd)})`,
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 r of panel.by_runtime) {
477
+ for (const runtime of panel.by_runtime) {
355
478
  lines.push(
356
- ' ' + r.runtime.padEnd(10) +
357
- String(r.calls).padStart(6) +
358
- String(r.tokens_in).padStart(12) +
359
- String(r.tokens_out).padStart(12) +
360
- ' ' + formatCost(r.cost_usd),
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 t of panel.top_tasks) {
370
- lines.push(` ${t.task_id.padEnd(24)} calls=${t.calls} cost=${formatCost(t.cost_usd)}`);
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') + '\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 (err) { return fail('USAGE_ERROR', err.message); }
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 (err) { return fail('USAGE_ERROR', err.message); }
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
- emit({ ok: true, data: panel });
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();