ultra-builder-pro-cli 0.24.1 → 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.
Files changed (46) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +10 -0
  3. package/adapters/claude.js +104 -0
  4. package/adapters/codex.js +363 -40
  5. package/adapters/grok.js +1254 -58
  6. package/adapters/kimi.js +682 -13
  7. package/adapters/opencode.js +132 -11
  8. package/agents/code-reviewer.md +4 -5
  9. package/agents/debugger.md +8 -8
  10. package/docs/AGENT-CONTEXT.md +30 -4
  11. package/docs/ARCHITECTURE.md +24 -5
  12. package/docs/ARTIFACT-AUTHORITY.md +13 -0
  13. package/docs/COMMIT-HASH-BACKFILL.md +25 -18
  14. package/docs/RUNTIME-COMPAT-MATRIX.md +18 -2
  15. package/docs/STATE-DB-ACCESS-POLICY.md +8 -1
  16. package/mcp-server/lib/artifact-registry.cjs +188 -107
  17. package/mcp-server/lib/baseline-workflow.cjs +160 -23
  18. package/mcp-server/lib/change-packet.cjs +4 -32
  19. package/mcp-server/lib/change-workflow.cjs +36 -2755
  20. package/mcp-server/lib/context-envelope.cjs +124 -51
  21. package/mcp-server/lib/decision-records.cjs +164 -78
  22. package/mcp-server/lib/kernel-change-workflow.cjs +2029 -0
  23. package/mcp-server/lib/legacy-change-workflow.cjs +3032 -0
  24. package/mcp-server/lib/managed-file-write.cjs +56 -0
  25. package/mcp-server/lib/prd-parser.cjs +2 -2
  26. package/mcp-server/lib/stage-checkpoints.cjs +163 -9
  27. package/mcp-server/lib/state-db.cjs +186 -8
  28. package/mcp-server/lib/state-ops.cjs +214 -31
  29. package/mcp-server/lib/task-contract.cjs +404 -0
  30. package/mcp-server/lib/task-expander.cjs +8 -3
  31. package/mcp-server/lib/task-ledger.cjs +175 -62
  32. package/mcp-server/lib/ultra-facade.cjs +1895 -172
  33. package/mcp-server/lib/worker-packet.cjs +2 -2
  34. package/mcp-server/server.cjs +24 -5
  35. package/orchestrator/parallel-orchestrator.cjs +114 -27
  36. package/orchestrator/session-runner.cjs +25 -136
  37. package/package.json +1 -1
  38. package/skills/ultra-change/SKILL.md +6 -1
  39. package/skills/ultra-dev/SKILL.md +9 -4
  40. package/skills/ultra-plan/SKILL.md +13 -4
  41. package/skills/ultra-plan/references/semantic-preflight.md +6 -4
  42. package/spec/mcp-tools.yaml +114 -9
  43. package/spec/schemas/context-file.v4.5.schema.json +2 -2
  44. package/spec/schemas/state-db.sql +10 -6
  45. package/spec/schemas/task-ledger.v2.schema.json +23 -8
  46. package/spec/schemas/tasks.v4.5.schema.json +34 -12
@@ -17,6 +17,12 @@ const ENDPOINT_TYPES = new Set([
17
17
  ]);
18
18
  const OWNER_TYPES = new Set(['project', 'baseline', 'change', 'task', 'workflow']);
19
19
  const STATUSES = new Set(['current', 'stale', 'terminal', 'archived']);
20
+ const ARTIFACT_INPUT_FIELDS = new Set([
21
+ 'id', 'owner_type', 'owner_id', 'change_id', 'task_id', 'kind', 'path',
22
+ 'content_digest', 'expected_before_digest', 'source_refs', 'consumer_refs',
23
+ 'provenance', 'metadata', 'status',
24
+ ]);
25
+ const DIGEST = /^[0-9a-f]{64}$/;
20
26
  const OWNER_TABLES = Object.freeze({
21
27
  baseline: 'baselines',
22
28
  change: 'changes',
@@ -48,6 +54,70 @@ class ArtifactRegistryError extends Error {
48
54
  }
49
55
  }
50
56
 
57
+ function artifactText(value, field, { optional = false, nullable = false } = {}) {
58
+ if (value === undefined && optional) return undefined;
59
+ if (value === null && nullable) return null;
60
+ if (typeof value !== 'string' || value.trim() === '') {
61
+ throw new ArtifactRegistryError(
62
+ 'VALIDATION_ERROR',
63
+ `${field} must be a non-empty string${nullable ? ' or null' : ''}`,
64
+ );
65
+ }
66
+ return value.trim();
67
+ }
68
+
69
+ function artifactDigest(value, field, { nullable = false } = {}) {
70
+ if (value === undefined) return undefined;
71
+ if (value === null && nullable) return null;
72
+ if (typeof value !== 'string' || !DIGEST.test(value)) {
73
+ throw new ArtifactRegistryError(
74
+ 'VALIDATION_ERROR',
75
+ `${field} must be a lowercase SHA-256 digest${nullable ? ' or null' : ''}`,
76
+ );
77
+ }
78
+ return value;
79
+ }
80
+
81
+ function assertJsonValue(value, field, seen = new Set()) {
82
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
83
+ if (typeof value === 'number') {
84
+ if (Number.isFinite(value)) return;
85
+ throw new ArtifactRegistryError('VALIDATION_ERROR', `${field} must contain valid JSON values`);
86
+ }
87
+ if (typeof value !== 'object') {
88
+ throw new ArtifactRegistryError('VALIDATION_ERROR', `${field} must contain valid JSON values`);
89
+ }
90
+ if (seen.has(value)) {
91
+ throw new ArtifactRegistryError('VALIDATION_ERROR', `${field} must not contain circular references`);
92
+ }
93
+ if (!Array.isArray(value)) {
94
+ const prototype = Object.getPrototypeOf(value);
95
+ if (prototype !== Object.prototype && prototype !== null) {
96
+ throw new ArtifactRegistryError(
97
+ 'VALIDATION_ERROR', `${field} must contain only JSON objects`,
98
+ );
99
+ }
100
+ }
101
+ seen.add(value);
102
+ if (Array.isArray(value)) {
103
+ value.forEach((item, index) => assertJsonValue(item, `${field}[${index}]`, seen));
104
+ } else {
105
+ Object.entries(value).forEach(([key, item]) => (
106
+ assertJsonValue(item, `${field}.${key}`, seen)
107
+ ));
108
+ }
109
+ seen.delete(value);
110
+ }
111
+
112
+ function normalizeJsonObject(value, field) {
113
+ if (value === undefined) return {};
114
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
115
+ throw new ArtifactRegistryError('VALIDATION_ERROR', `${field} must be a JSON object`);
116
+ }
117
+ assertJsonValue(value, field);
118
+ return JSON.parse(JSON.stringify(value));
119
+ }
120
+
51
121
  function parseJson(value, fallback) {
52
122
  if (value == null || value === '') return fallback;
53
123
  try { return JSON.parse(value); } catch { return fallback; }
@@ -302,6 +372,13 @@ function normalizeEndpoint(endpoint, field) {
302
372
  if (!endpoint || typeof endpoint !== 'object' || Array.isArray(endpoint)) {
303
373
  throw new ArtifactRegistryError('VALIDATION_ERROR', `${field} must be an object`);
304
374
  }
375
+ const unknown = Object.keys(endpoint)
376
+ .filter((key) => !['type', 'id', 'relation'].includes(key));
377
+ if (unknown.length > 0) {
378
+ throw new ArtifactRegistryError(
379
+ 'VALIDATION_ERROR', `${field}.${unknown[0]} is not allowed`,
380
+ );
381
+ }
305
382
  const type = typeof endpoint.type === 'string' ? endpoint.type.trim() : '';
306
383
  const id = typeof endpoint.id === 'string' ? endpoint.id.trim() : '';
307
384
  const relation = typeof endpoint.relation === 'string' ? endpoint.relation.trim() : '';
@@ -313,6 +390,59 @@ function normalizeEndpoint(endpoint, field) {
313
390
  return { type, id, relation };
314
391
  }
315
392
 
393
+ function normalizeArtifactInput(input) {
394
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
395
+ throw new ArtifactRegistryError('VALIDATION_ERROR', 'artifact input is required');
396
+ }
397
+ const unknown = Object.keys(input).filter((field) => !ARTIFACT_INPUT_FIELDS.has(field));
398
+ if (unknown.length > 0) {
399
+ throw new ArtifactRegistryError(
400
+ 'VALIDATION_ERROR', `artifact.${unknown[0]} is not allowed`,
401
+ );
402
+ }
403
+ const status = input.status === undefined
404
+ ? 'current'
405
+ : artifactText(input.status, 'artifact.status');
406
+ if (!STATUSES.has(status)) {
407
+ throw new ArtifactRegistryError('VALIDATION_ERROR', `invalid artifact status: ${status}`);
408
+ }
409
+ const normalizeRefs = (value, field) => {
410
+ if (value === undefined) return [];
411
+ if (!Array.isArray(value)) {
412
+ throw new ArtifactRegistryError('VALIDATION_ERROR', `${field} must be an array`);
413
+ }
414
+ return value.map((item, index) => normalizeEndpoint(item, `${field}[${index}]`));
415
+ };
416
+ return {
417
+ id: artifactText(input.id, 'artifact.id', { optional: true }),
418
+ ownerType: artifactText(input.owner_type, 'artifact.owner_type'),
419
+ ownerId: artifactText(input.owner_id, 'artifact.owner_id'),
420
+ changeId: artifactText(
421
+ input.change_id,
422
+ 'artifact.change_id',
423
+ { optional: true, nullable: true },
424
+ ),
425
+ taskId: artifactText(
426
+ input.task_id,
427
+ 'artifact.task_id',
428
+ { optional: true, nullable: true },
429
+ ),
430
+ kind: artifactText(input.kind, 'artifact.kind'),
431
+ path: artifactText(input.path, 'artifact.path'),
432
+ status,
433
+ contentDigest: artifactDigest(input.content_digest, 'artifact.content_digest'),
434
+ expectedBeforeDigest: artifactDigest(
435
+ input.expected_before_digest,
436
+ 'artifact.expected_before_digest',
437
+ { nullable: true },
438
+ ),
439
+ sourceRefs: normalizeRefs(input.source_refs, 'artifact.source_refs'),
440
+ consumerRefs: normalizeRefs(input.consumer_refs, 'artifact.consumer_refs'),
441
+ provenance: normalizeJsonObject(input.provenance, 'artifact.provenance'),
442
+ metadata: normalizeJsonObject(input.metadata, 'artifact.metadata'),
443
+ };
444
+ }
445
+
316
446
  function assertEndpoint(db, endpoint, field) {
317
447
  const normalized = normalizeEndpoint(endpoint, field);
318
448
  const { type, id } = normalized;
@@ -339,6 +469,34 @@ function assertOwner(db, ownerType, ownerId) {
339
469
  }
340
470
  }
341
471
 
472
+ function assertArtifactOwner(db, ownerType, ownerId) {
473
+ assertOwner(db, ownerType, ownerId);
474
+ }
475
+
476
+ function assertDeclaredOwnerRefs(db, normalized) {
477
+ const expectedTaskId = normalized.ownerType === 'task' ? normalized.ownerId : null;
478
+ const expectedChangeId = normalized.ownerType === 'change'
479
+ ? normalized.ownerId
480
+ : normalized.ownerType === 'task'
481
+ ? db.prepare('SELECT change_id FROM tasks WHERE id = ?').get(normalized.ownerId)?.change_id || null
482
+ : normalized.ownerType === 'workflow'
483
+ ? db.prepare('SELECT change_id FROM workflow_runs WHERE id = ?').get(normalized.ownerId)?.change_id || null
484
+ : null;
485
+ for (const [field, supplied, expected] of [
486
+ ['change_id', normalized.changeId, expectedChangeId],
487
+ ['task_id', normalized.taskId, expectedTaskId],
488
+ ]) {
489
+ if (supplied !== undefined && supplied !== expected) {
490
+ throw new ArtifactRegistryError(
491
+ 'ARTIFACT_AUTHORITY_CONFLICT',
492
+ `artifact ${field} does not match its owner authority`,
493
+ { field, supplied, expected },
494
+ );
495
+ }
496
+ }
497
+ return { changeId: expectedChangeId, taskId: expectedTaskId };
498
+ }
499
+
342
500
  function edgesForArtifact(db, artifactId) {
343
501
  const sources = db.prepare(
344
502
  `SELECT source_type AS type, source_id AS id, relation
@@ -473,6 +631,13 @@ function invalidateConsumersFromEndpointInTx(db, start, {
473
631
  const key = endpointKey(endpoint);
474
632
  if (seen.has(key) || excluded.has(key) || key === endpointKey(start)) continue;
475
633
  seen.add(key);
634
+ if (endpoint.type === 'workflow') {
635
+ // Workflow rows are retained only as pre-v0.24 audit history. Preserve
636
+ // graph traversal for downstream current authorities, but never rewrite
637
+ // the historical workflow row or claim that it was invalidated.
638
+ queue.push(...outgoingEdges(db, endpoint));
639
+ continue;
640
+ }
476
641
  invalidated.push({ type: endpoint.type, id: endpoint.id });
477
642
  if (endpoint.type === 'artifact') {
478
643
  const row = db.prepare('SELECT status FROM artifacts WHERE id = ?').get(endpoint.id);
@@ -503,62 +668,6 @@ function invalidateConsumersFromEndpointInTx(db, start, {
503
668
  });
504
669
  }
505
670
  }
506
- if (endpoint.type === 'workflow') {
507
- const row = db.prepare(
508
- 'SELECT status, metadata_json, change_id, task_id FROM workflow_runs WHERE id = ?',
509
- ).get(endpoint.id);
510
- if (row) {
511
- const metadata = parseJson(row.metadata_json, {});
512
- if (['active', 'blocked', 'ready'].includes(row.status)) {
513
- delete metadata.authority_invalidation;
514
- metadata.draft_dirty = true;
515
- db.prepare(
516
- `UPDATE workflow_runs
517
- SET status = CASE WHEN status = 'ready' THEN 'active' ELSE status END,
518
- metadata_json = ?,
519
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
520
- WHERE id = ?`,
521
- ).run(JSON.stringify(metadata), endpoint.id);
522
- ops.appendEventInTx(db, {
523
- type: 'workflow_draft_changed',
524
- change_id: row.change_id,
525
- task_id: row.task_id,
526
- payload: {
527
- workflow_id: endpoint.id,
528
- source_type: start.type,
529
- source_id: start.id,
530
- reason,
531
- },
532
- });
533
- queue.push(...outgoingEdges(db, endpoint));
534
- continue;
535
- }
536
- if (metadata.authority_invalidation?.invalidated !== true) {
537
- metadata.authority_invalidation = {
538
- invalidated: true,
539
- source_type: start.type,
540
- source_id: start.id,
541
- reason,
542
- invalidated_at: new Date().toISOString(),
543
- };
544
- db.prepare(
545
- `UPDATE workflow_runs SET metadata_json = ?,
546
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?`,
547
- ).run(JSON.stringify(metadata), endpoint.id);
548
- ops.appendEventInTx(db, {
549
- type: 'workflow_invalidated',
550
- change_id: row.change_id,
551
- task_id: row.task_id,
552
- payload: {
553
- workflow_id: endpoint.id,
554
- source_type: start.type,
555
- source_id: start.id,
556
- reason,
557
- },
558
- });
559
- }
560
- }
561
- }
562
671
  queue.push(...outgoingEdges(db, endpoint));
563
672
  }
564
673
  return invalidated;
@@ -576,28 +685,20 @@ function invalidateDownstreamInTx(db, artifactId, priorConsumers = []) {
576
685
  }
577
686
 
578
687
  function preflightArtifactPublication(db, input, { rootDir = process.cwd() } = {}) {
579
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
580
- throw new ArtifactRegistryError('VALIDATION_ERROR', 'artifact input is required');
581
- }
582
- const ownerType = typeof input.owner_type === 'string' ? input.owner_type.trim() : '';
583
- const ownerId = typeof input.owner_id === 'string' ? input.owner_id.trim() : '';
584
- const kind = typeof input.kind === 'string' ? input.kind.trim() : '';
585
- const status = input.status || 'current';
586
- if (!kind || !STATUSES.has(status)) {
587
- throw new ArtifactRegistryError(
588
- 'VALIDATION_ERROR', 'artifact kind and valid status are required',
589
- );
590
- }
688
+ const normalized = normalizeArtifactInput(input);
689
+ const {
690
+ ownerType, ownerId, kind, status,
691
+ } = normalized;
591
692
  assertOwner(db, ownerType, ownerId);
592
- const resolved = resolveArtifactFile(rootDir, input.path);
693
+ assertDeclaredOwnerRefs(db, normalized);
694
+ const resolved = resolveArtifactFile(rootDir, normalized.path);
593
695
  if (isExemptArtifactPath(resolved.relative) || isGeneratedProjectionPath(resolved.relative)) {
594
696
  throw new ArtifactRegistryError(
595
697
  'ARTIFACT_PATH_EXEMPT',
596
698
  `runtime, scratch, and generated projection paths are not registry authority: ${resolved.relative}`,
597
699
  );
598
700
  }
599
- const explicitId = typeof input.id === 'string' && input.id.trim() !== ''
600
- ? input.id.trim() : null;
701
+ const explicitId = normalized.id || null;
601
702
  const existingById = explicitId
602
703
  ? db.prepare('SELECT * FROM artifacts WHERE id = ?').get(explicitId)
603
704
  : null;
@@ -674,18 +775,14 @@ function preflightArtifactPublication(db, input, { rootDir = process.cwd() } = {
674
775
  }
675
776
 
676
777
  function recordArtifactInTx(db, input, { rootDir = process.cwd() } = {}) {
677
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
678
- throw new ArtifactRegistryError('VALIDATION_ERROR', 'artifact input is required');
679
- }
680
- const ownerType = typeof input.owner_type === 'string' ? input.owner_type.trim() : '';
681
- const ownerId = typeof input.owner_id === 'string' ? input.owner_id.trim() : '';
682
- const kind = typeof input.kind === 'string' ? input.kind.trim() : '';
683
- const status = input.status || 'current';
684
- if (!kind || !STATUSES.has(status)) {
685
- throw new ArtifactRegistryError('VALIDATION_ERROR', 'artifact kind and valid status are required');
686
- }
778
+ const normalized = normalizeArtifactInput(input);
779
+ const {
780
+ ownerType, ownerId, kind, status,
781
+ sourceRefs, consumerRefs, provenance, metadata,
782
+ } = normalized;
687
783
  assertOwner(db, ownerType, ownerId);
688
- const resolved = resolveArtifactFile(rootDir, input.path);
784
+ const ownerRefs = assertDeclaredOwnerRefs(db, normalized);
785
+ const resolved = resolveArtifactFile(rootDir, normalized.path);
689
786
  if (isExemptArtifactPath(resolved.relative) || isGeneratedProjectionPath(resolved.relative)) {
690
787
  throw new ArtifactRegistryError(
691
788
  'ARTIFACT_PATH_EXEMPT',
@@ -694,18 +791,7 @@ function recordArtifactInTx(db, input, { rootDir = process.cwd() } = {}) {
694
791
  }
695
792
  const stableRead = openStableArtifactRead(resolved);
696
793
  try {
697
- const sourceRefs = Array.isArray(input.source_refs)
698
- ? input.source_refs.map((item, index) => normalizeEndpoint(item, `source_refs[${index}]`))
699
- : [];
700
- const consumerRefs = Array.isArray(input.consumer_refs)
701
- ? input.consumer_refs.map((item, index) => normalizeEndpoint(item, `consumer_refs[${index}]`))
702
- : [];
703
- const provenance = input.provenance && typeof input.provenance === 'object'
704
- && !Array.isArray(input.provenance) ? input.provenance : {};
705
- const metadata = input.metadata && typeof input.metadata === 'object'
706
- && !Array.isArray(input.metadata) ? input.metadata : {};
707
- const explicitId = typeof input.id === 'string' && input.id.trim() !== ''
708
- ? input.id.trim() : null;
794
+ const explicitId = normalized.id || null;
709
795
  const existingById = explicitId
710
796
  ? db.prepare('SELECT * FROM artifacts WHERE id = ?').get(explicitId)
711
797
  : null;
@@ -759,31 +845,25 @@ function recordArtifactInTx(db, input, { rootDir = process.cwd() } = {}) {
759
845
  });
760
846
  }
761
847
  const beforeDigest = existing?.digest || existing?.content_hash || null;
762
- if (input.expected_before_digest !== undefined
763
- && input.expected_before_digest !== beforeDigest) {
848
+ if (normalized.expectedBeforeDigest !== undefined
849
+ && normalized.expectedBeforeDigest !== beforeDigest) {
764
850
  throw new ArtifactRegistryError(
765
851
  'ARTIFACT_DIGEST_CONFLICT',
766
852
  `artifact ${artifactId} changed since it was read`,
767
- { expected: input.expected_before_digest, actual: beforeDigest },
853
+ { expected: normalized.expectedBeforeDigest, actual: beforeDigest },
768
854
  );
769
855
  }
770
856
  const afterDigest = stableRead.digest;
771
- if (input.content_digest !== undefined && input.content_digest !== afterDigest) {
857
+ if (normalized.contentDigest !== undefined && normalized.contentDigest !== afterDigest) {
772
858
  throw new ArtifactRegistryError(
773
859
  'ARTIFACT_DIGEST_CONFLICT',
774
860
  `artifact bytes do not match the supplied digest: ${resolved.relative}`,
775
- { expected: input.content_digest, actual: afterDigest },
861
+ { expected: normalized.contentDigest, actual: afterDigest },
776
862
  );
777
863
  }
778
864
  const changed = beforeDigest !== afterDigest;
779
865
  const priorConsumers = existing ? edgesForArtifact(db, artifactId).consumers : [];
780
- const ownerChangeId = ownerType === 'change'
781
- ? ownerId
782
- : ownerType === 'task'
783
- ? db.prepare('SELECT change_id FROM tasks WHERE id = ?').get(ownerId)?.change_id || null
784
- : ownerType === 'workflow'
785
- ? db.prepare('SELECT change_id FROM workflow_runs WHERE id = ?').get(ownerId)?.change_id || null
786
- : null;
866
+ const ownerChangeId = ownerRefs.changeId;
787
867
  const now = new Date().toISOString();
788
868
  let invalidated = [];
789
869
  db.prepare(
@@ -803,7 +883,7 @@ function recordArtifactInTx(db, input, { rootDir = process.cwd() } = {}) {
803
883
  ).run(
804
884
  artifactId, ownerType, ownerId,
805
885
  ownerChangeId,
806
- ownerType === 'task' ? ownerId : null,
886
+ ownerRefs.taskId,
807
887
  kind, resolved.relative, afterDigest, afterDigest, beforeDigest, afterDigest,
808
888
  JSON.stringify(provenance), JSON.stringify(metadata), status,
809
889
  existing?.created_at || now, now,
@@ -1134,6 +1214,7 @@ module.exports = {
1134
1214
  ENDPOINT_TYPES,
1135
1215
  OWNER_TYPES,
1136
1216
  STATUSES,
1217
+ assertArtifactOwner,
1137
1218
  digestFile,
1138
1219
  getArtifact,
1139
1220
  inspectArtifactHealth,