ultra-builder-pro-cli 0.24.2 → 0.25.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 +42 -0
- package/adapters/claude.js +104 -0
- package/adapters/codex.js +363 -40
- package/adapters/grok.js +1254 -58
- package/adapters/kimi.js +682 -13
- package/adapters/opencode.js +132 -11
- package/docs/AGENT-CONTEXT.md +14 -0
- package/docs/ARTIFACT-AUTHORITY.md +13 -0
- package/docs/COMMIT-HASH-BACKFILL.md +25 -18
- package/docs/RUNTIME-COMPAT-MATRIX.md +18 -2
- package/mcp-server/lib/artifact-registry.cjs +181 -51
- package/mcp-server/lib/baseline-workflow.cjs +160 -23
- package/mcp-server/lib/change-workflow.cjs +21 -16
- package/mcp-server/lib/context-envelope.cjs +124 -51
- package/mcp-server/lib/decision-records.cjs +164 -78
- package/mcp-server/lib/kernel-change-workflow.cjs +2029 -0
- package/mcp-server/lib/legacy-change-workflow.cjs +294 -71
- package/mcp-server/lib/managed-file-write.cjs +56 -0
- package/mcp-server/lib/prd-parser.cjs +2 -2
- package/mcp-server/lib/stage-checkpoints.cjs +163 -9
- package/mcp-server/lib/state-db.cjs +186 -8
- package/mcp-server/lib/state-ops.cjs +214 -31
- package/mcp-server/lib/task-contract.cjs +404 -0
- package/mcp-server/lib/task-expander.cjs +2 -2
- package/mcp-server/lib/task-ledger.cjs +175 -62
- package/mcp-server/lib/ultra-facade.cjs +1786 -169
- package/mcp-server/lib/worker-packet.cjs +2 -2
- package/mcp-server/server.cjs +23 -4
- package/orchestrator/parallel-orchestrator.cjs +114 -27
- package/orchestrator/session-runner.cjs +25 -136
- package/package.json +1 -1
- package/skills/ultra-change/SKILL.md +6 -1
- package/skills/ultra-dev/SKILL.md +9 -4
- package/skills/ultra-plan/SKILL.md +13 -4
- package/skills/ultra-plan/references/semantic-preflight.md +6 -4
- package/spec/mcp-tools.yaml +114 -9
- package/spec/schemas/context-file.v4.5.schema.json +2 -2
- package/spec/schemas/state-db.sql +10 -6
- package/spec/schemas/task-ledger.v2.schema.json +23 -8
- package/spec/schemas/tasks.v4.5.schema.json +34 -12
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
const canonical = require('./canonical-json.cjs');
|
|
4
4
|
const artifactRegistry = require('./artifact-registry.cjs');
|
|
5
5
|
const ops = require('./state-ops.cjs');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
restoreManagedFile,
|
|
8
|
+
snapshotManagedFile,
|
|
9
|
+
writeManagedJson,
|
|
10
|
+
} = require('./managed-file-write.cjs');
|
|
7
11
|
const { readStableProjectFile } = require('./safe-project-file.cjs');
|
|
8
12
|
|
|
13
|
+
const DECISION_FIELDS = new Set([
|
|
14
|
+
'id', 'scope', 'question', 'recommendation', 'selection', 'effects',
|
|
15
|
+
'non_goals', 'owner', 'source', 'provenance', 'applied_refs', 'supersedes_id',
|
|
16
|
+
]);
|
|
17
|
+
|
|
9
18
|
class DecisionRecordError extends Error {
|
|
10
19
|
constructor(code, message, details) {
|
|
11
20
|
super(message);
|
|
@@ -21,11 +30,55 @@ function requiredText(value, field) {
|
|
|
21
30
|
return normalized;
|
|
22
31
|
}
|
|
23
32
|
|
|
33
|
+
function assertExactFields(value, allowed, field) {
|
|
34
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
35
|
+
throw new DecisionRecordError('VALIDATION_ERROR', `${field} must be an object`);
|
|
36
|
+
}
|
|
37
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
38
|
+
if (unknown.length > 0) {
|
|
39
|
+
throw new DecisionRecordError(
|
|
40
|
+
'VALIDATION_ERROR', `${field}.${unknown[0]} is not allowed`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function assertJsonValue(value, field, seen = new Set()) {
|
|
46
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
|
|
47
|
+
if (typeof value === 'number') {
|
|
48
|
+
if (Number.isFinite(value)) return;
|
|
49
|
+
throw new DecisionRecordError('VALIDATION_ERROR', `${field} must contain valid JSON values`);
|
|
50
|
+
}
|
|
51
|
+
if (typeof value !== 'object') {
|
|
52
|
+
throw new DecisionRecordError('VALIDATION_ERROR', `${field} must contain valid JSON values`);
|
|
53
|
+
}
|
|
54
|
+
if (seen.has(value)) {
|
|
55
|
+
throw new DecisionRecordError('VALIDATION_ERROR', `${field} must not contain circular references`);
|
|
56
|
+
}
|
|
57
|
+
if (!Array.isArray(value)) {
|
|
58
|
+
const prototype = Object.getPrototypeOf(value);
|
|
59
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
60
|
+
throw new DecisionRecordError(
|
|
61
|
+
'VALIDATION_ERROR', `${field} must contain only JSON objects`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
seen.add(value);
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
value.forEach((item, index) => assertJsonValue(item, `${field}[${index}]`, seen));
|
|
68
|
+
} else {
|
|
69
|
+
Object.entries(value).forEach(([key, item]) => (
|
|
70
|
+
assertJsonValue(item, `${field}.${key}`, seen)
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
seen.delete(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
24
76
|
function plainObject(value, field) {
|
|
25
77
|
if (value === undefined) return {};
|
|
26
78
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
27
79
|
throw new DecisionRecordError('VALIDATION_ERROR', `${field} must be an object`);
|
|
28
80
|
}
|
|
81
|
+
assertJsonValue(value, field);
|
|
29
82
|
return JSON.parse(JSON.stringify(value));
|
|
30
83
|
}
|
|
31
84
|
|
|
@@ -38,6 +91,7 @@ function stringArray(value, field) {
|
|
|
38
91
|
}
|
|
39
92
|
|
|
40
93
|
function normalizeScope(scope = {}) {
|
|
94
|
+
assertExactFields(scope, new Set(['baseline_id', 'change_id']), 'scope');
|
|
41
95
|
const baseline = typeof scope.baseline_id === 'string' ? scope.baseline_id.trim() : '';
|
|
42
96
|
const change = typeof scope.change_id === 'string' ? scope.change_id.trim() : '';
|
|
43
97
|
if (Boolean(baseline) === Boolean(change)) {
|
|
@@ -186,10 +240,19 @@ function normalizeAppliedRefs(value) {
|
|
|
186
240
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
187
241
|
throw new DecisionRecordError('VALIDATION_ERROR', `applied_refs[${index}] must be an object`);
|
|
188
242
|
}
|
|
243
|
+
assertExactFields(
|
|
244
|
+
item,
|
|
245
|
+
new Set(['ref', 'field', 'digest']),
|
|
246
|
+
`applied_refs[${index}]`,
|
|
247
|
+
);
|
|
189
248
|
return {
|
|
190
249
|
ref: requiredText(item.ref, `applied_refs[${index}].ref`),
|
|
191
|
-
...(item.field
|
|
192
|
-
|
|
250
|
+
...(item.field !== undefined
|
|
251
|
+
? { field: requiredText(item.field, `applied_refs[${index}].field`) }
|
|
252
|
+
: {}),
|
|
253
|
+
...(item.digest !== undefined
|
|
254
|
+
? { digest: requiredText(item.digest, `applied_refs[${index}].digest`) }
|
|
255
|
+
: {}),
|
|
193
256
|
};
|
|
194
257
|
});
|
|
195
258
|
}
|
|
@@ -201,7 +264,11 @@ function artifactPath(scope, id) {
|
|
|
201
264
|
}
|
|
202
265
|
|
|
203
266
|
function acceptDecision(db, input = {}, { rootDir = process.cwd() } = {}) {
|
|
267
|
+
assertExactFields(input, DECISION_FIELDS, 'decision');
|
|
204
268
|
const scope = normalizeScope(input.scope);
|
|
269
|
+
const supersedesId = input.supersedes_id === undefined || input.supersedes_id === null
|
|
270
|
+
? null
|
|
271
|
+
: requiredText(input.supersedes_id, 'supersedes_id');
|
|
205
272
|
const value = {
|
|
206
273
|
schema_version: '1.0',
|
|
207
274
|
id: requiredText(input.id, 'id'),
|
|
@@ -216,9 +283,7 @@ function acceptDecision(db, input = {}, { rootDir = process.cwd() } = {}) {
|
|
|
216
283
|
provenance: plainObject(input.provenance, 'provenance'),
|
|
217
284
|
applied_refs: normalizeAppliedRefs(input.applied_refs),
|
|
218
285
|
status: 'accepted',
|
|
219
|
-
supersedes_id:
|
|
220
|
-
? requiredText(input.supersedes_id, 'supersedes_id')
|
|
221
|
-
: null,
|
|
286
|
+
supersedes_id: supersedesId,
|
|
222
287
|
};
|
|
223
288
|
const digest = canonical.digest(value);
|
|
224
289
|
const document = { ...value, digest };
|
|
@@ -247,83 +312,104 @@ function acceptDecision(db, input = {}, { rootDir = process.cwd() } = {}) {
|
|
|
247
312
|
'a decision can only supersede another decision in the same scope',
|
|
248
313
|
);
|
|
249
314
|
}
|
|
315
|
+
if (prior.status !== 'accepted') {
|
|
316
|
+
throw new DecisionRecordError(
|
|
317
|
+
'DECISION_NOT_SUPERSEDEABLE',
|
|
318
|
+
`decision ${value.supersedes_id} is ${prior.status}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
250
321
|
}
|
|
322
|
+
artifactRegistry.assertArtifactOwner(db, scope.type, scope.id);
|
|
323
|
+
const snapshot = snapshotManagedFile(rootDir, relative);
|
|
251
324
|
const published = writeManagedJson(rootDir, relative, document);
|
|
252
|
-
|
|
253
|
-
|
|
325
|
+
try {
|
|
326
|
+
return ops.tx(db, () => {
|
|
327
|
+
if (value.supersedes_id) {
|
|
328
|
+
db.prepare(
|
|
329
|
+
`UPDATE decision_records SET status = 'superseded',
|
|
330
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
331
|
+
WHERE id = ? AND status = 'accepted'`,
|
|
332
|
+
).run(value.supersedes_id);
|
|
333
|
+
}
|
|
254
334
|
db.prepare(
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
JSON.stringify(value.effects),
|
|
274
|
-
JSON.stringify(value.non_goals),
|
|
275
|
-
value.owner,
|
|
276
|
-
value.source,
|
|
277
|
-
JSON.stringify(value.provenance),
|
|
278
|
-
JSON.stringify(value.applied_refs),
|
|
279
|
-
digest,
|
|
280
|
-
relative,
|
|
281
|
-
value.supersedes_id,
|
|
282
|
-
);
|
|
283
|
-
artifactRegistry.recordArtifactInTx(db, {
|
|
284
|
-
id: `artifact-decision-${value.id}`,
|
|
285
|
-
owner_type: scope.type,
|
|
286
|
-
owner_id: scope.id,
|
|
287
|
-
change_id: scope.type === 'change' ? scope.id : null,
|
|
288
|
-
kind: 'decision_record',
|
|
289
|
-
path: relative,
|
|
290
|
-
content_digest: published.digest,
|
|
291
|
-
source_refs: [{
|
|
292
|
-
type: scope.type,
|
|
293
|
-
id: scope.id,
|
|
294
|
-
relation: 'decided_for',
|
|
295
|
-
}],
|
|
296
|
-
consumer_refs: [{
|
|
297
|
-
type: 'external',
|
|
298
|
-
id: 'ultra-context-consumer',
|
|
299
|
-
relation: 'consumed_by',
|
|
300
|
-
}],
|
|
301
|
-
provenance: {
|
|
302
|
-
writer: 'decision-records',
|
|
303
|
-
decision_digest: digest,
|
|
304
|
-
owner: value.owner,
|
|
305
|
-
source: value.source,
|
|
306
|
-
},
|
|
307
|
-
metadata: {
|
|
308
|
-
decision_id: value.id,
|
|
309
|
-
status: value.status,
|
|
310
|
-
supersedes_id: value.supersedes_id,
|
|
311
|
-
},
|
|
312
|
-
}, { rootDir });
|
|
313
|
-
ops.appendEventInTx(db, {
|
|
314
|
-
type: 'decision_recorded',
|
|
315
|
-
change_id: scope.type === 'change' ? scope.id : null,
|
|
316
|
-
payload: {
|
|
317
|
-
decision_id: value.id,
|
|
318
|
-
scope_type: scope.type,
|
|
319
|
-
scope_id: scope.id,
|
|
335
|
+
`INSERT INTO decision_records
|
|
336
|
+
(id, scope_type, scope_id, question, recommendation, selection,
|
|
337
|
+
effects_json, non_goals_json, owner, source, provenance_json,
|
|
338
|
+
applied_refs_json, status, digest, artifact_path, supersedes_id)
|
|
339
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'accepted', ?, ?, ?)`,
|
|
340
|
+
).run(
|
|
341
|
+
value.id,
|
|
342
|
+
scope.type,
|
|
343
|
+
scope.id,
|
|
344
|
+
value.question,
|
|
345
|
+
value.recommendation,
|
|
346
|
+
value.selection,
|
|
347
|
+
JSON.stringify(value.effects),
|
|
348
|
+
JSON.stringify(value.non_goals),
|
|
349
|
+
value.owner,
|
|
350
|
+
value.source,
|
|
351
|
+
JSON.stringify(value.provenance),
|
|
352
|
+
JSON.stringify(value.applied_refs),
|
|
320
353
|
digest,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
354
|
+
relative,
|
|
355
|
+
value.supersedes_id,
|
|
356
|
+
);
|
|
357
|
+
artifactRegistry.recordArtifactInTx(db, {
|
|
358
|
+
id: `artifact-decision-${value.id}`,
|
|
359
|
+
owner_type: scope.type,
|
|
360
|
+
owner_id: scope.id,
|
|
361
|
+
change_id: scope.type === 'change' ? scope.id : null,
|
|
362
|
+
kind: 'decision_record',
|
|
363
|
+
path: relative,
|
|
364
|
+
content_digest: published.digest,
|
|
365
|
+
source_refs: [{
|
|
366
|
+
type: scope.type,
|
|
367
|
+
id: scope.id,
|
|
368
|
+
relation: 'decided_for',
|
|
369
|
+
}],
|
|
370
|
+
consumer_refs: [{
|
|
371
|
+
type: 'external',
|
|
372
|
+
id: 'ultra-context-consumer',
|
|
373
|
+
relation: 'consumed_by',
|
|
374
|
+
}],
|
|
375
|
+
provenance: {
|
|
376
|
+
writer: 'decision-records',
|
|
377
|
+
decision_digest: digest,
|
|
378
|
+
owner: value.owner,
|
|
379
|
+
source: value.source,
|
|
380
|
+
},
|
|
381
|
+
metadata: {
|
|
382
|
+
decision_id: value.id,
|
|
383
|
+
status: value.status,
|
|
384
|
+
supersedes_id: value.supersedes_id,
|
|
385
|
+
},
|
|
386
|
+
}, { rootDir });
|
|
387
|
+
ops.appendEventInTx(db, {
|
|
388
|
+
type: 'decision_recorded',
|
|
389
|
+
change_id: scope.type === 'change' ? scope.id : null,
|
|
390
|
+
payload: {
|
|
391
|
+
decision_id: value.id,
|
|
392
|
+
scope_type: scope.type,
|
|
393
|
+
scope_id: scope.id,
|
|
394
|
+
digest,
|
|
395
|
+
artifact_path: relative,
|
|
396
|
+
supersedes_id: value.supersedes_id,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
return readDecision(db, value.id);
|
|
324
400
|
});
|
|
325
|
-
|
|
326
|
-
|
|
401
|
+
} catch (error) {
|
|
402
|
+
try {
|
|
403
|
+
restoreManagedFile(rootDir, snapshot);
|
|
404
|
+
} catch (rollbackError) {
|
|
405
|
+
throw new DecisionRecordError(
|
|
406
|
+
'RECORD_FILE_ROLLBACK_REQUIRED',
|
|
407
|
+
`decision database mutation failed and managed file recovery also failed: ${relative}`,
|
|
408
|
+
{ cause: error.message, rollback_error: rollbackError.message },
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
327
413
|
}
|
|
328
414
|
|
|
329
415
|
module.exports = {
|