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.
Files changed (95) hide show
  1. package/.ultra-template/docs/research/README.md +4 -4
  2. package/.ultra-template/tasks/tasks.json +4 -2
  3. package/CHANGELOG.md +72 -0
  4. package/README.md +55 -39
  5. package/adapters/_shared/codex-assets.cjs +121 -65
  6. package/adapters/_shared/interaction-contract.cjs +7 -1
  7. package/adapters/_shared/provenance.cjs +7 -2
  8. package/adapters/_shared/runtime-assets.cjs +3 -2
  9. package/adapters/_shared/runtime-native.cjs +132 -0
  10. package/adapters/claude.js +60 -30
  11. package/adapters/codex.js +178 -81
  12. package/adapters/grok.js +372 -0
  13. package/adapters/kimi.js +7 -3
  14. package/adapters/opencode.js +210 -32
  15. package/agents/code-reviewer.md +4 -2
  16. package/agents/debugger.md +4 -1
  17. package/agents/review-code.md +7 -3
  18. package/agents/review-comments.md +7 -4
  19. package/agents/review-coordinator.md +11 -7
  20. package/agents/review-design.md +8 -4
  21. package/agents/review-errors.md +8 -4
  22. package/agents/review-spec.md +8 -5
  23. package/agents/review-tests.md +7 -4
  24. package/agents/tdd-runner.md +5 -2
  25. package/bin/install.js +9 -4
  26. package/bin/orchestrator.js +4 -13
  27. package/docs/AGENT-CONTEXT.md +143 -229
  28. package/docs/ARCHITECTURE.md +205 -494
  29. package/docs/ARTIFACT-AUTHORITY.md +35 -28
  30. package/docs/LEGACY-CLI-CROSSWALK.md +1 -1
  31. package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
  32. package/docs/ROADMAP.md +85 -110
  33. package/docs/RUNTIME-COMPAT-MATRIX.md +139 -175
  34. package/docs/SKILL-AUTHORING.md +3 -3
  35. package/docs/STATE-DB-ACCESS-POLICY.md +13 -8
  36. package/docs/WORKFLOW-LIFECYCLE.md +189 -448
  37. package/hooks/README.md +11 -8
  38. package/hooks/active_task_context.py +16 -16
  39. package/hooks/adapters/grok.py +134 -0
  40. package/hooks/{context_spine.py → context_envelope.py} +26 -22
  41. package/hooks/health_check.py +29 -34
  42. package/hooks/pre_stop_check.py +22 -12
  43. package/hooks/subagent_tracker.py +1 -1
  44. package/hooks/workflow_checkpoint.py +14 -13
  45. package/hooks/workflow_context.py +11 -12
  46. package/hooks/workflow_resume.py +14 -13
  47. package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
  48. package/mcp-server/lib/baseline-workflow.cjs +108 -82
  49. package/mcp-server/lib/canonical-json.cjs +19 -0
  50. package/mcp-server/lib/change-authority.cjs +26 -0
  51. package/mcp-server/lib/change-workflow.cjs +630 -45
  52. package/mcp-server/lib/context-envelope.cjs +508 -0
  53. package/mcp-server/lib/decision-records.cjs +337 -0
  54. package/mcp-server/lib/doctor.cjs +91 -33
  55. package/mcp-server/lib/init-project.cjs +78 -39
  56. package/mcp-server/lib/managed-file-write.cjs +116 -0
  57. package/mcp-server/lib/plan-checkpoint.cjs +177 -0
  58. package/mcp-server/lib/project-context.cjs +178 -0
  59. package/mcp-server/lib/stage-checkpoints.cjs +309 -0
  60. package/mcp-server/lib/state-db.cjs +155 -24
  61. package/mcp-server/lib/state-ops.cjs +21 -3
  62. package/mcp-server/lib/task-ledger.cjs +522 -13
  63. package/mcp-server/lib/ultra-facade.cjs +757 -442
  64. package/mcp-server/lib/worker-packet.cjs +358 -0
  65. package/mcp-server/server.cjs +200 -1103
  66. package/orchestrator/daemon.cjs +27 -9
  67. package/orchestrator/parallel-orchestrator.cjs +111 -76
  68. package/orchestrator/session-runner.cjs +135 -34
  69. package/package.json +10 -3
  70. package/skills/ultra-change/SKILL.md +10 -8
  71. package/skills/ultra-deliver/SKILL.md +17 -19
  72. package/skills/ultra-dev/SKILL.md +20 -11
  73. package/skills/ultra-doctor/SKILL.md +4 -4
  74. package/skills/ultra-init/SKILL.md +6 -5
  75. package/skills/ultra-init/references/brownfield-adoption.md +20 -14
  76. package/skills/ultra-plan/SKILL.md +13 -9
  77. package/skills/ultra-research/SKILL.md +18 -9
  78. package/skills/ultra-review/SKILL.md +14 -9
  79. package/skills/ultra-review/references/unified-schema.md +2 -0
  80. package/skills/ultra-review/scripts/review_wait.py +8 -0
  81. package/skills/ultra-status/SKILL.md +2 -2
  82. package/skills/ultra-test/SKILL.md +10 -6
  83. package/skills/ultra-think/SKILL.md +8 -7
  84. package/skills/ultra-think/references/interaction-boundary.md +56 -0
  85. package/spec/cli-protocol.md +80 -199
  86. package/spec/mcp-tools.yaml +43 -2806
  87. package/spec/schemas/state-db.sql +107 -4
  88. package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
  89. package/ultra-tools/commands/status.cjs +369 -243
  90. package/mcp-server/lib/context-spine.cjs +0 -1208
  91. package/mcp-server/lib/decision-dialogue.cjs +0 -1128
  92. package/mcp-server/lib/project-breadcrumb.cjs +0 -173
  93. package/mcp-server/lib/spec-learning.cjs +0 -284
  94. package/mcp-server/lib/workflow-state.cjs +0 -3129
  95. package/skills/ultra-think/references/decision-dialogue.md +0 -155
@@ -0,0 +1,358 @@
1
+ 'use strict';
2
+
3
+ const Ajv = require('ajv');
4
+
5
+ const canonical = require('./canonical-json.cjs');
6
+ const contextEnvelope = require('./context-envelope.cjs');
7
+ const decisionRecords = require('./decision-records.cjs');
8
+ const { writeManagedJson } = require('./managed-file-write.cjs');
9
+ const ops = require('./state-ops.cjs');
10
+ const {
11
+ normalizeProjectRelative,
12
+ readStableProjectFile,
13
+ } = require('./safe-project-file.cjs');
14
+ const taskLedger = require('./task-ledger.cjs');
15
+
16
+ class WorkerPacketError extends Error {
17
+ constructor(code, message, details) {
18
+ super(message);
19
+ this.name = 'WorkerPacketError';
20
+ this.code = code;
21
+ if (details) this.details = details;
22
+ }
23
+ }
24
+
25
+ function requiredText(value, field) {
26
+ const normalized = typeof value === 'string' ? value.trim() : '';
27
+ if (!normalized) throw new WorkerPacketError('VALIDATION_ERROR', `${field} is required`);
28
+ return normalized;
29
+ }
30
+
31
+ function packetValueFromDocument(document) {
32
+ if (!document || typeof document !== 'object' || Array.isArray(document)) return null;
33
+ const { packet_digest: _packetDigest, ...value } = document;
34
+ return value;
35
+ }
36
+
37
+ function readWorkerPacket(db, id, { rootDir = process.cwd() } = {}) {
38
+ const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
39
+ if (!row) {
40
+ throw new WorkerPacketError('WORKER_PACKET_NOT_FOUND', `Worker Packet not found: ${id}`);
41
+ }
42
+ const read = readStableProjectFile(rootDir, row.packet_path, { encoding: 'utf8' });
43
+ if (!row.file_digest || read.digest !== row.file_digest) {
44
+ throw new WorkerPacketError(
45
+ 'WORKER_PACKET_FILE_DRIFT',
46
+ `Worker Packet bytes no longer match ${id}`,
47
+ {
48
+ path: row.packet_path,
49
+ expected: row.file_digest || null,
50
+ actual: read.digest,
51
+ },
52
+ );
53
+ }
54
+ let document;
55
+ try {
56
+ document = JSON.parse(read.text);
57
+ } catch (cause) {
58
+ throw new WorkerPacketError(
59
+ 'WORKER_PACKET_INVALID',
60
+ `Worker Packet is not valid JSON: ${row.packet_path}`,
61
+ { cause: cause.message },
62
+ );
63
+ }
64
+ const computed = canonical.digest(packetValueFromDocument(document));
65
+ if (document.packet_digest !== row.packet_digest || computed !== row.packet_digest) {
66
+ throw new WorkerPacketError(
67
+ 'WORKER_PACKET_DIGEST_MISMATCH',
68
+ `Worker Packet authority does not match its database record: ${id}`,
69
+ {
70
+ expected: row.packet_digest,
71
+ document: document.packet_digest || null,
72
+ computed,
73
+ },
74
+ );
75
+ }
76
+ contextEnvelope.readEnvelope(db, row.context_envelope_id, { rootDir });
77
+ return {
78
+ ...document,
79
+ id: row.id,
80
+ status: row.status,
81
+ packet_path: row.packet_path,
82
+ file_digest: row.file_digest,
83
+ context_digest: row.context_digest,
84
+ task_digest: row.task_digest,
85
+ decision_digest: row.decision_digest,
86
+ output_path: row.output_path,
87
+ };
88
+ }
89
+
90
+ function markWorkerPacketAssigned(db, id) {
91
+ return ops.tx(db, () => {
92
+ const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
93
+ if (!row) {
94
+ throw new WorkerPacketError('WORKER_PACKET_NOT_FOUND', `Worker Packet not found: ${id}`);
95
+ }
96
+ if (row.status === 'assigned') return row;
97
+ if (row.status !== 'pending') {
98
+ throw new WorkerPacketError(
99
+ 'WORKER_PACKET_STATUS_CONFLICT',
100
+ `Worker Packet ${id} is ${row.status}`,
101
+ );
102
+ }
103
+ db.prepare(
104
+ `UPDATE worker_packets
105
+ SET status = 'assigned',
106
+ assigned_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
107
+ abandoned_at = NULL,
108
+ abandon_reason = NULL
109
+ WHERE id = ? AND status = 'pending'`,
110
+ ).run(id);
111
+ ops.appendEventInTx(db, {
112
+ type: 'worker_packet_assigned',
113
+ task_id: row.scope_type === 'task' ? row.scope_id : null,
114
+ payload: { packet_id: id, packet_digest: row.packet_digest },
115
+ });
116
+ return db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
117
+ });
118
+ }
119
+
120
+ function abandonWorkerPacket(db, id, reason) {
121
+ return ops.tx(db, () => {
122
+ const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
123
+ if (!row || row.status === 'abandoned') return row || null;
124
+ if (row.status !== 'pending') return row;
125
+ db.prepare(
126
+ `UPDATE worker_packets
127
+ SET status = 'abandoned',
128
+ abandoned_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
129
+ abandon_reason = ?
130
+ WHERE id = ? AND status = 'pending'`,
131
+ ).run(String(reason || 'session acquisition failed'), id);
132
+ ops.appendEventInTx(db, {
133
+ type: 'worker_packet_abandoned',
134
+ task_id: row.scope_type === 'task' ? row.scope_id : null,
135
+ payload: {
136
+ packet_id: id,
137
+ packet_digest: row.packet_digest,
138
+ reason: String(reason || 'session acquisition failed'),
139
+ },
140
+ });
141
+ return db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
142
+ });
143
+ }
144
+
145
+ function createWorkerPacket(db, input = {}, { rootDir = process.cwd() } = {}) {
146
+ const role = requiredText(input.role, 'role');
147
+ const taskId = requiredText(input.task_id, 'task_id');
148
+ const task = ops.readTask(db, taskId);
149
+ if (!task) throw new WorkerPacketError('TASK_NOT_FOUND', `task not found: ${taskId}`);
150
+ if (!task.change_id) {
151
+ throw new WorkerPacketError(
152
+ 'TASK_CHANGE_REQUIRED',
153
+ `task ${taskId} must belong to an active Change`,
154
+ );
155
+ }
156
+ const change = db.prepare(
157
+ 'SELECT id, artifact_root FROM changes WHERE id = ?',
158
+ ).get(task.change_id);
159
+ if (!change) {
160
+ throw new WorkerPacketError('CHANGE_NOT_FOUND', `change not found: ${task.change_id}`);
161
+ }
162
+ for (const reference of task.context_refs || []) {
163
+ if (!reference?.required || !reference.expected_digest) continue;
164
+ const current = readStableProjectFile(rootDir, reference.ref, { encoding: null });
165
+ if (current.digest !== reference.expected_digest) {
166
+ throw new WorkerPacketError(
167
+ 'CONTEXT_REQUIRED_REF_STALE',
168
+ `required Context reference changed after Plan acceptance: ${reference.ref}`,
169
+ {
170
+ ref: reference.ref,
171
+ expected_digest: reference.expected_digest,
172
+ actual_digest: current.digest,
173
+ },
174
+ );
175
+ }
176
+ }
177
+ const context = contextEnvelope.persistEnvelope(db, {
178
+ stage: role === 'review' ? 'review' : 'dev',
179
+ scope: { change_id: task.change_id, task_id: taskId },
180
+ }, { rootDir, runtime: input.runtime || 'unknown' });
181
+ const durableTask = taskLedger.durableTask(task);
182
+ const acceptedDecisions = [
183
+ ...decisionRecords.listAcceptedDecisions(
184
+ db,
185
+ { change_id: task.change_id },
186
+ { limit: 200, rootDir, validateFiles: true },
187
+ ),
188
+ ];
189
+ const decisionDigest = canonical.digest(
190
+ acceptedDecisions.map((decision) => ({
191
+ id: decision.id,
192
+ digest: decision.digest,
193
+ status: decision.status,
194
+ })),
195
+ );
196
+ const outputPath = normalizeProjectRelative(requiredText(input.output_path, 'output_path'));
197
+ const packetValue = {
198
+ packet_version: '1.0',
199
+ role,
200
+ runtime: input.runtime || 'unknown',
201
+ scope: { change_id: task.change_id, task_id: taskId },
202
+ context_envelope: {
203
+ id: context.id,
204
+ path: context.artifact_path,
205
+ digest: context.digest,
206
+ },
207
+ accepted_decisions: acceptedDecisions.map((decision) => ({
208
+ id: decision.id,
209
+ selection: decision.selection,
210
+ effects: decision.effects,
211
+ artifact_path: decision.artifact_path,
212
+ digest: decision.digest,
213
+ })),
214
+ git: {
215
+ head: db.prepare(
216
+ `SELECT repository_revision FROM baselines
217
+ WHERE status <> 'superseded' ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
218
+ ).get()?.repository_revision || null,
219
+ diff_range: input.diff_range || null,
220
+ changed_files: Array.isArray(input.changed_files) ? input.changed_files : [],
221
+ },
222
+ task: {
223
+ id: task.id,
224
+ title: task.title,
225
+ outcome: task.outcome,
226
+ acceptance: task.acceptance,
227
+ verification_command: task.verification_command,
228
+ digest: durableTask.digest,
229
+ },
230
+ evidence_refs: Array.isArray(input.evidence_refs) ? input.evidence_refs : [],
231
+ output: {
232
+ path: outputPath,
233
+ schema: input.output_schema && typeof input.output_schema === 'object'
234
+ ? input.output_schema
235
+ : { type: 'object', required: ['packet_digest'] },
236
+ },
237
+ };
238
+ const packetDigest = canonical.digest(packetValue);
239
+ const packet = { ...packetValue, packet_digest: packetDigest };
240
+ const id = `worker-${packetDigest.slice(0, 24)}`;
241
+ const packetPath = `.ultra/.runtime/worker-packets/${id}.json`;
242
+ const existing = db.prepare(
243
+ 'SELECT * FROM worker_packets WHERE packet_digest = ?',
244
+ ).get(packetDigest);
245
+ if (existing) {
246
+ readWorkerPacket(db, existing.id, { rootDir });
247
+ if (existing.status === 'abandoned') {
248
+ db.prepare(
249
+ `UPDATE worker_packets
250
+ SET status = 'pending', assigned_at = NULL, abandoned_at = NULL,
251
+ abandon_reason = NULL
252
+ WHERE id = ? AND status = 'abandoned'`,
253
+ ).run(existing.id);
254
+ }
255
+ return {
256
+ ...packet,
257
+ id: existing.id,
258
+ packet_path: existing.packet_path,
259
+ context_digest: existing.context_digest,
260
+ task_digest: existing.task_digest,
261
+ decision_digest: existing.decision_digest,
262
+ status: existing.status === 'abandoned' ? 'pending' : existing.status,
263
+ file_digest: existing.file_digest,
264
+ created: false,
265
+ };
266
+ }
267
+ const published = writeManagedJson(rootDir, packetPath, packet);
268
+ ops.tx(db, () => {
269
+ db.prepare(
270
+ `INSERT INTO worker_packets
271
+ (id, role, scope_type, scope_id, context_envelope_id, context_digest,
272
+ task_digest, decision_digest, packet_digest, file_digest, status,
273
+ packet_path, output_path)
274
+ VALUES (?, ?, 'task', ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,
275
+ ).run(
276
+ id,
277
+ role,
278
+ taskId,
279
+ context.id,
280
+ context.digest,
281
+ durableTask.digest,
282
+ decisionDigest,
283
+ packetDigest,
284
+ published.digest,
285
+ packetPath,
286
+ outputPath,
287
+ );
288
+ ops.appendEventInTx(db, {
289
+ type: 'worker_packet_created',
290
+ change_id: task.change_id,
291
+ task_id: taskId,
292
+ payload: {
293
+ packet_id: id,
294
+ packet_digest: packetDigest,
295
+ context_digest: context.digest,
296
+ task_digest: durableTask.digest,
297
+ output_path: outputPath,
298
+ },
299
+ });
300
+ });
301
+ return {
302
+ ...packet,
303
+ id,
304
+ packet_path: packetPath,
305
+ context_digest: context.digest,
306
+ task_digest: durableTask.digest,
307
+ decision_digest: decisionDigest,
308
+ file_digest: published.digest,
309
+ status: 'pending',
310
+ created: true,
311
+ };
312
+ }
313
+
314
+ function verifyWorkerResult(packet, result = {}) {
315
+ if (!packet || typeof packet !== 'object') {
316
+ throw new WorkerPacketError('VALIDATION_ERROR', 'packet is required');
317
+ }
318
+ if (result.packet_digest !== packet.packet_digest) {
319
+ throw new WorkerPacketError(
320
+ 'WORKER_PACKET_DIGEST_MISMATCH',
321
+ 'worker result does not reference the exact assigned packet',
322
+ {
323
+ expected: packet.packet_digest,
324
+ actual: result.packet_digest || null,
325
+ },
326
+ );
327
+ }
328
+ const schema = packet.output?.schema;
329
+ if (schema && typeof schema === 'object' && !Array.isArray(schema)) {
330
+ let validate;
331
+ try {
332
+ validate = new Ajv({ allErrors: true, strict: false }).compile(schema);
333
+ } catch (cause) {
334
+ throw new WorkerPacketError(
335
+ 'WORKER_OUTPUT_SCHEMA_INVALID',
336
+ 'worker packet contains an invalid output schema',
337
+ { cause: cause.message },
338
+ );
339
+ }
340
+ if (!validate(result)) {
341
+ throw new WorkerPacketError(
342
+ 'WORKER_OUTPUT_INVALID',
343
+ 'worker result does not satisfy the assigned output schema',
344
+ { errors: validate.errors || [] },
345
+ );
346
+ }
347
+ }
348
+ return true;
349
+ }
350
+
351
+ module.exports = {
352
+ WorkerPacketError,
353
+ createWorkerPacket,
354
+ readWorkerPacket,
355
+ markWorkerPacketAssigned,
356
+ abandonWorkerPacket,
357
+ verifyWorkerResult,
358
+ };