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
|
@@ -121,13 +121,13 @@ function abandonWorkerPacket(db, id, reason) {
|
|
|
121
121
|
return ops.tx(db, () => {
|
|
122
122
|
const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
|
|
123
123
|
if (!row || row.status === 'abandoned') return row || null;
|
|
124
|
-
if (row.status
|
|
124
|
+
if (!['pending', 'assigned'].includes(row.status)) return row;
|
|
125
125
|
db.prepare(
|
|
126
126
|
`UPDATE worker_packets
|
|
127
127
|
SET status = 'abandoned',
|
|
128
128
|
abandoned_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
129
129
|
abandon_reason = ?
|
|
130
|
-
WHERE id = ? AND status
|
|
130
|
+
WHERE id = ? AND status IN ('pending', 'assigned')`,
|
|
131
131
|
).run(String(reason || 'session acquisition failed'), id);
|
|
132
132
|
ops.appendEventInTx(db, {
|
|
133
133
|
type: 'worker_packet_abandoned',
|
package/mcp-server/server.cjs
CHANGED
|
@@ -68,6 +68,24 @@ function errorResponse(code, message, retriable = false, details = undefined) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function publicToolError(error) {
|
|
72
|
+
const code = String(error?.code || 'STATE_DB_ERROR');
|
|
73
|
+
if (code.startsWith('SQLITE_')) {
|
|
74
|
+
return {
|
|
75
|
+
code: 'STATE_DB_ERROR',
|
|
76
|
+
message: 'Ultra state persistence failed',
|
|
77
|
+
retriable: code === 'SQLITE_BUSY' || code === 'SQLITE_LOCKED',
|
|
78
|
+
details: undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
code,
|
|
83
|
+
message: error?.message || 'Ultra state operation failed',
|
|
84
|
+
retriable: Boolean(error?.retriable),
|
|
85
|
+
details: error?.details,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
function isInitializeCall(name, input = {}) {
|
|
72
90
|
return name === 'ultra.record'
|
|
73
91
|
&& Array.isArray(input.entries)
|
|
@@ -313,11 +331,12 @@ function startServer({
|
|
|
313
331
|
} catch (error) {
|
|
314
332
|
toolError = error.code || 'STATE_DB_ERROR';
|
|
315
333
|
emitTelemetry();
|
|
334
|
+
const visibleError = publicToolError(error);
|
|
316
335
|
return errorResponse(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
336
|
+
visibleError.code,
|
|
337
|
+
visibleError.message,
|
|
338
|
+
visibleError.retriable,
|
|
339
|
+
visibleError.details,
|
|
321
340
|
);
|
|
322
341
|
}
|
|
323
342
|
});
|
|
@@ -27,6 +27,8 @@ const { isDeepStrictEqual } = require('node:util');
|
|
|
27
27
|
const planStore = require('../mcp-server/lib/plan-store.cjs');
|
|
28
28
|
const ops = require('../mcp-server/lib/state-ops.cjs');
|
|
29
29
|
const checkpoints = require('../mcp-server/lib/stage-checkpoints.cjs');
|
|
30
|
+
const gitBootstrap = require('../mcp-server/lib/git-bootstrap.cjs');
|
|
31
|
+
const runtimePaths = require('../mcp-server/lib/runtime-paths.cjs');
|
|
30
32
|
const workerPackets = require('../mcp-server/lib/worker-packet.cjs');
|
|
31
33
|
const runner = require('./session-runner.cjs');
|
|
32
34
|
const dispatchRules = require('./dispatch-rules.cjs');
|
|
@@ -460,6 +462,10 @@ function reservePreparedTask(db, repoRoot, prepared, ctx) {
|
|
|
460
462
|
const { onError } = ctx;
|
|
461
463
|
let handle;
|
|
462
464
|
let packet = null;
|
|
465
|
+
let authority = null;
|
|
466
|
+
const priorContextIds = new Set(
|
|
467
|
+
db.prepare('SELECT id FROM context_envelopes').all().map((row) => row.id),
|
|
468
|
+
);
|
|
463
469
|
try {
|
|
464
470
|
if (!task.change_id) {
|
|
465
471
|
throw planError(
|
|
@@ -467,36 +473,44 @@ function reservePreparedTask(db, repoRoot, prepared, ctx) {
|
|
|
467
473
|
`orchestrator task ${task.id} must belong to the accepted Change`,
|
|
468
474
|
);
|
|
469
475
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
476
|
+
authority = runtimePaths.ensureRuntimeState(repoRoot, {
|
|
477
|
+
admitStorageBoundary: () => gitBootstrap.ensureExistingProjectStorageBoundary(repoRoot),
|
|
478
|
+
});
|
|
479
|
+
ops.tx(db, () => {
|
|
480
|
+
packet = workerPackets.createWorkerPacket(db, {
|
|
481
|
+
role: 'implement',
|
|
482
|
+
task_id: task.id,
|
|
483
|
+
runtime: decision.runtime,
|
|
484
|
+
output_path: `.ultra/changes/active/${task.change_id}/delivery/${task.id}-outcome.json`,
|
|
485
|
+
output_schema: {
|
|
486
|
+
type: 'object',
|
|
487
|
+
required: ['packet_digest'],
|
|
488
|
+
properties: {
|
|
489
|
+
packet_digest: { type: 'string', pattern: '^[0-9a-f]{64}$' },
|
|
490
|
+
},
|
|
480
491
|
},
|
|
481
|
-
},
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
+
}, { rootDir: repoRoot });
|
|
493
|
+
workerPackets.markWorkerPacketAssigned(db, packet.id);
|
|
494
|
+
handle = runner.spawnSession({
|
|
495
|
+
db, repoRoot,
|
|
496
|
+
task_id: task.id,
|
|
497
|
+
runtime: decision.runtime,
|
|
498
|
+
authority,
|
|
499
|
+
command: null,
|
|
500
|
+
args: [],
|
|
501
|
+
mark_task_started: true,
|
|
502
|
+
kernel_mode: true,
|
|
503
|
+
packet_digest: packet.packet_digest,
|
|
504
|
+
});
|
|
505
|
+
handle.task_id = task.id;
|
|
506
|
+
handle.packet = packet;
|
|
492
507
|
});
|
|
493
|
-
handle.task_id = task.id;
|
|
494
|
-
workerPackets.markWorkerPacketAssigned(db, packet.id);
|
|
495
|
-
handle.packet = packet;
|
|
496
508
|
} catch (err) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
509
|
+
rollbackFailedReservation(db, repoRoot, {
|
|
510
|
+
handle,
|
|
511
|
+
packet,
|
|
512
|
+
priorContextIds,
|
|
513
|
+
});
|
|
500
514
|
const authorityBlocked = authorityBlockedResult(err, task.id, {
|
|
501
515
|
reservationFailed: true,
|
|
502
516
|
});
|
|
@@ -523,6 +537,69 @@ function reservePreparedTask(db, repoRoot, prepared, ctx) {
|
|
|
523
537
|
};
|
|
524
538
|
}
|
|
525
539
|
|
|
540
|
+
function rollbackFailedReservation(db, repoRoot, {
|
|
541
|
+
handle,
|
|
542
|
+
packet,
|
|
543
|
+
priorContextIds,
|
|
544
|
+
}) {
|
|
545
|
+
if (handle?.worktree_path) {
|
|
546
|
+
try {
|
|
547
|
+
runner._internal.reconcileRemovedWorktree(
|
|
548
|
+
repoRoot,
|
|
549
|
+
handle.worktree_path,
|
|
550
|
+
{ sid: handle.sid },
|
|
551
|
+
);
|
|
552
|
+
} catch { /* the caller preserves the original reservation error */ }
|
|
553
|
+
}
|
|
554
|
+
if (handle?.artifact_dir) {
|
|
555
|
+
const expected = path.resolve(
|
|
556
|
+
repoRoot,
|
|
557
|
+
'.ultra',
|
|
558
|
+
'.runtime',
|
|
559
|
+
'sessions',
|
|
560
|
+
handle.sid,
|
|
561
|
+
);
|
|
562
|
+
const actual = path.resolve(handle.artifact_dir);
|
|
563
|
+
if (actual === expected) {
|
|
564
|
+
try { fs.rmSync(actual, { recursive: true, force: true }); }
|
|
565
|
+
catch { /* the caller preserves the original reservation error */ }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (packet?.created && !db.prepare(
|
|
569
|
+
'SELECT 1 FROM worker_packets WHERE id = ?',
|
|
570
|
+
).get(packet.id)) {
|
|
571
|
+
const expected = path.resolve(
|
|
572
|
+
repoRoot,
|
|
573
|
+
'.ultra',
|
|
574
|
+
'.runtime',
|
|
575
|
+
'worker-packets',
|
|
576
|
+
`${packet.id}.json`,
|
|
577
|
+
);
|
|
578
|
+
const actual = path.resolve(repoRoot, packet.packet_path);
|
|
579
|
+
if (actual === expected) {
|
|
580
|
+
try { fs.rmSync(actual, { force: true }); }
|
|
581
|
+
catch { /* the caller preserves the original reservation error */ }
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const context = packet?.context_envelope;
|
|
585
|
+
if (context?.id
|
|
586
|
+
&& !priorContextIds.has(context.id)
|
|
587
|
+
&& !db.prepare('SELECT 1 FROM context_envelopes WHERE id = ?').get(context.id)) {
|
|
588
|
+
const actual = path.resolve(repoRoot, context.path);
|
|
589
|
+
const runtimeContextRoot = path.resolve(
|
|
590
|
+
repoRoot,
|
|
591
|
+
'.ultra',
|
|
592
|
+
'.runtime',
|
|
593
|
+
'contexts',
|
|
594
|
+
);
|
|
595
|
+
if (actual.startsWith(`${runtimeContextRoot}${path.sep}`)
|
|
596
|
+
&& path.basename(actual) === `${context.id}.json`) {
|
|
597
|
+
try { fs.rmSync(actual, { force: true }); }
|
|
598
|
+
catch { /* the caller preserves the original reservation error */ }
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
526
603
|
async function runTask(db, repoRoot, task, wave, ctx) {
|
|
527
604
|
const prepared = ctx.prepared || prepareTaskDispatch(task, wave, ctx);
|
|
528
605
|
const reservation = reservePreparedTask(db, repoRoot, prepared, ctx);
|
|
@@ -560,6 +637,16 @@ function unwindReservations(db, repoRoot, reservations, ctx) {
|
|
|
560
637
|
if (ctx.onError) ctx.onError(error);
|
|
561
638
|
continue;
|
|
562
639
|
}
|
|
640
|
+
try {
|
|
641
|
+
workerPackets.abandonWorkerPacket(
|
|
642
|
+
db,
|
|
643
|
+
handle.packet?.id,
|
|
644
|
+
'parallel reservation cancelled before execution',
|
|
645
|
+
);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
errors.push(error);
|
|
648
|
+
if (ctx.onError) ctx.onError(error);
|
|
649
|
+
}
|
|
563
650
|
const task = ops.readTask(db, handle.task_id);
|
|
564
651
|
if (task?.status === 'in_progress') {
|
|
565
652
|
try {
|
|
@@ -21,16 +21,6 @@ const runtimePaths = require('../mcp-server/lib/runtime-paths.cjs');
|
|
|
21
21
|
const gitBootstrap = require('../mcp-server/lib/git-bootstrap.cjs');
|
|
22
22
|
const closeJournal = require('./session-close-journal.cjs');
|
|
23
23
|
|
|
24
|
-
function legacyContextSpine() {
|
|
25
|
-
return module.require(path.join(
|
|
26
|
-
__dirname,
|
|
27
|
-
'..',
|
|
28
|
-
'mcp-server',
|
|
29
|
-
'lib',
|
|
30
|
-
'context-spine.cjs',
|
|
31
|
-
));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
24
|
const EXPECTED_EXECUTION_GATE_CODES = new Set([
|
|
35
25
|
'TASK_EXECUTION_CONTRACT_INCOMPLETE',
|
|
36
26
|
'WORKFLOW_TASK_NOT_EXECUTABLE',
|
|
@@ -559,77 +549,12 @@ function mintSessionId() {
|
|
|
559
549
|
function assertSessionTaskReady(db, repoRoot, taskId) {
|
|
560
550
|
const task = ops.readTask(db, taskId);
|
|
561
551
|
if (!task) return null; // admissionCheck reports the canonical TASK_NOT_FOUND error.
|
|
562
|
-
// Unbound rows remain readable for legacy recovery, but every current
|
|
563
|
-
// change-owned task must pass the same plan gate as a dev workflow.
|
|
564
|
-
if (!task.change_id) return task;
|
|
565
|
-
ops.assertTaskExecutionContract(task);
|
|
566
552
|
if (!['pending', 'in_progress', 'blocked'].includes(task.status) || task.stale) {
|
|
567
553
|
throw new SessionRunnerError(
|
|
568
554
|
'TASK_NOT_EXECUTABLE',
|
|
569
555
|
`task ${task.id} is not executable from status ${task.status}${task.stale ? ' (stale)' : ''}`,
|
|
570
556
|
);
|
|
571
557
|
}
|
|
572
|
-
const incompleteDependencies = (task.deps || []).filter((id) => {
|
|
573
|
-
const dependency = ops.readTask(db, id);
|
|
574
|
-
return !dependency || !['completed', 'expanded'].includes(dependency.status);
|
|
575
|
-
});
|
|
576
|
-
if (incompleteDependencies.length > 0) {
|
|
577
|
-
throw new SessionRunnerError(
|
|
578
|
-
'TASK_DEPENDENCIES_INCOMPLETE',
|
|
579
|
-
`task ${task.id} has incomplete dependencies: ${incompleteDependencies.join(', ')}`,
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
const plan = checkpoints.currentCheckpoint(
|
|
583
|
-
db,
|
|
584
|
-
'plan',
|
|
585
|
-
{ change_id: task.change_id },
|
|
586
|
-
{ includeDraft: false },
|
|
587
|
-
);
|
|
588
|
-
if (!plan || checkpoints.checkpointDigest(plan) !== plan.digest) {
|
|
589
|
-
throw new SessionRunnerError(
|
|
590
|
-
'PLAN_CHECKPOINT_REQUIRED',
|
|
591
|
-
`task ${task.id} requires an accepted Plan checkpoint`,
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
const implementationContext = legacyContextSpine().validateContextSnapshot(db, {
|
|
595
|
-
change_id: task.change_id,
|
|
596
|
-
task_id: task.id,
|
|
597
|
-
role: 'implement',
|
|
598
|
-
gate: 'implementation',
|
|
599
|
-
}, {
|
|
600
|
-
rootDir: repoRoot,
|
|
601
|
-
require_current_checkout: true,
|
|
602
|
-
});
|
|
603
|
-
if (!implementationContext.snapshot) {
|
|
604
|
-
throw new SessionRunnerError(
|
|
605
|
-
'IMPLEMENTATION_CONTEXT_REQUIRED',
|
|
606
|
-
`task ${task.id} requires a ready implementation Context before session admission`,
|
|
607
|
-
{ blockers: implementationContext.blockers },
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
if (implementationContext.blockers.length > 0) {
|
|
611
|
-
throw new SessionRunnerError(
|
|
612
|
-
'IMPLEMENTATION_CONTEXT_STALE',
|
|
613
|
-
`task ${task.id} implementation Context is stale`,
|
|
614
|
-
{ blockers: implementationContext.blockers },
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
const planningContext = legacyContextSpine().validateContextSnapshot(db, {
|
|
618
|
-
change_id: task.change_id,
|
|
619
|
-
task_id: null,
|
|
620
|
-
role: 'plan',
|
|
621
|
-
gate: 'planning',
|
|
622
|
-
}, {
|
|
623
|
-
rootDir: repoRoot,
|
|
624
|
-
require_current_checkout: true,
|
|
625
|
-
});
|
|
626
|
-
if (!planningContext.snapshot || planningContext.blockers.length > 0) {
|
|
627
|
-
throw new SessionRunnerError(
|
|
628
|
-
'PLAN_CONTEXT_STALE',
|
|
629
|
-
`change ${task.change_id} planning Context is stale at session admission`,
|
|
630
|
-
{ blockers: planningContext.blockers },
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
558
|
return task;
|
|
634
559
|
}
|
|
635
560
|
|
|
@@ -638,65 +563,26 @@ function assertKernelSessionTaskReady(db, repoRoot, taskId, packetDigest) {
|
|
|
638
563
|
if (!task) {
|
|
639
564
|
throw new SessionRunnerError('TASK_NOT_FOUND', `task ${taskId} not found`);
|
|
640
565
|
}
|
|
641
|
-
ops.assertTaskExecutionContract(task);
|
|
642
566
|
if (!['pending', 'in_progress', 'blocked'].includes(task.status) || task.stale) {
|
|
643
567
|
throw new SessionRunnerError(
|
|
644
568
|
'TASK_NOT_EXECUTABLE',
|
|
645
569
|
`task ${task.id} is not executable from status ${task.status}${task.stale ? ' (stale)' : ''}`,
|
|
646
570
|
);
|
|
647
571
|
}
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
572
|
+
const packet = packetDigest
|
|
573
|
+
? db.prepare(
|
|
574
|
+
`SELECT id FROM worker_packets
|
|
575
|
+
WHERE packet_digest = ? AND scope_type = 'task' AND scope_id = ?
|
|
576
|
+
AND status IN ('pending', 'assigned')`,
|
|
577
|
+
).get(packetDigest, task.id)
|
|
578
|
+
: null;
|
|
579
|
+
if (!packet) {
|
|
653
580
|
throw new SessionRunnerError(
|
|
654
|
-
'
|
|
655
|
-
`task ${task.id}
|
|
581
|
+
'WORKER_PACKET_REQUIRED',
|
|
582
|
+
`task ${task.id} requires the exact Worker Packet prepared for this session`,
|
|
656
583
|
);
|
|
657
584
|
}
|
|
658
|
-
|
|
659
|
-
const plan = checkpoints.currentCheckpoint(
|
|
660
|
-
db,
|
|
661
|
-
'plan',
|
|
662
|
-
{ change_id: task.change_id },
|
|
663
|
-
{ includeDraft: false },
|
|
664
|
-
);
|
|
665
|
-
if (!plan) {
|
|
666
|
-
throw new SessionRunnerError(
|
|
667
|
-
'PLAN_CHECKPOINT_REQUIRED',
|
|
668
|
-
`task ${task.id} requires an accepted Plan checkpoint`,
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
const plannedDigest = plan.payload?.plan?.task_contract_digests?.[task.id];
|
|
672
|
-
if (!plannedDigest) {
|
|
673
|
-
throw new SessionRunnerError(
|
|
674
|
-
'PLAN_TASK_CONTRACT_MISSING',
|
|
675
|
-
`accepted Plan checkpoint does not bind task ${task.id}`,
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
const packet = packetDigest
|
|
679
|
-
? db.prepare(
|
|
680
|
-
`SELECT id, task_digest FROM worker_packets
|
|
681
|
-
WHERE packet_digest = ? AND scope_type = 'task' AND scope_id = ?
|
|
682
|
-
AND status IN ('pending', 'assigned')`,
|
|
683
|
-
).get(packetDigest, task.id)
|
|
684
|
-
: null;
|
|
685
|
-
if (!packet) {
|
|
686
|
-
throw new SessionRunnerError(
|
|
687
|
-
'WORKER_PACKET_REQUIRED',
|
|
688
|
-
`task ${task.id} requires the exact Worker Packet prepared for this session`,
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
workerPackets.readWorkerPacket(db, packet.id, { rootDir: repoRoot });
|
|
692
|
-
if (packet.task_digest !== plannedDigest) {
|
|
693
|
-
throw new SessionRunnerError(
|
|
694
|
-
'PLAN_TASK_CONTRACT_STALE',
|
|
695
|
-
`task ${task.id} no longer matches its accepted Plan checkpoint`,
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
return task;
|
|
699
|
-
}
|
|
585
|
+
workerPackets.readWorkerPacket(db, packet.id, { rootDir: repoRoot });
|
|
700
586
|
return task;
|
|
701
587
|
}
|
|
702
588
|
|
|
@@ -783,6 +669,7 @@ function spawnSession({
|
|
|
783
669
|
repoRoot,
|
|
784
670
|
task_id,
|
|
785
671
|
runtime,
|
|
672
|
+
authority: preadmittedAuthority = null,
|
|
786
673
|
takeover = false,
|
|
787
674
|
command = null,
|
|
788
675
|
args = [],
|
|
@@ -818,17 +705,19 @@ function spawnSession({
|
|
|
818
705
|
// or any filesystem mutation creates a new worktree.
|
|
819
706
|
if (kernel_mode) assertKernelSessionTaskReady(db, repoRoot, task_id, packet_digest);
|
|
820
707
|
else assertSessionTaskReady(db, repoRoot, task_id);
|
|
821
|
-
let authority;
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
708
|
+
let authority = preadmittedAuthority;
|
|
709
|
+
if (!authority) {
|
|
710
|
+
try {
|
|
711
|
+
authority = runtimePaths.ensureRuntimeState(repoRoot, {
|
|
712
|
+
admitStorageBoundary: () => gitBootstrap.ensureExistingProjectStorageBoundary(repoRoot),
|
|
713
|
+
});
|
|
714
|
+
} catch (error) {
|
|
715
|
+
throw new SessionRunnerError(
|
|
716
|
+
'WORKTREE_AUTHORITY_NOT_IGNORED',
|
|
717
|
+
`cannot establish the project Ultra storage boundary: ${error.message}`,
|
|
718
|
+
{ cause: error },
|
|
719
|
+
);
|
|
720
|
+
}
|
|
832
721
|
}
|
|
833
722
|
const pendingClose = closeJournal.findForTask(repoRoot, task_id);
|
|
834
723
|
if (pendingClose) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-builder-pro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Explicitly invoked Ultra Builder Pro workflows for Claude Code, OpenCode, Codex CLI, Kimi Code, and Grok Build, with a project-local MCP persistence and safety kernel.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ultra-builder-pro-cli": "bin/install.js",
|
|
@@ -18,7 +18,9 @@ checkpoint accepts the downstream authority.
|
|
|
18
18
|
4. Convert the request into observable outcome, executable acceptance, non-goals,
|
|
19
19
|
public seams, recovery, verification, documentation impact, risk, and research
|
|
20
20
|
disposition.
|
|
21
|
-
5. Recommend `quick`, `standard`, `major`, or `incident` from evidence
|
|
21
|
+
5. Recommend `quick`, `standard`, `major`, or `incident` from evidence, or record a
|
|
22
|
+
clearer bounded repository-specific kind when those profiles lose meaning. These
|
|
23
|
+
are model-owned labels, not SQLite enums. Ask only when
|
|
22
24
|
a choice changes accepted product intent, compatibility, security, material cost,
|
|
23
25
|
external effects, or recovery.
|
|
24
26
|
|
|
@@ -42,6 +44,9 @@ genuinely dependent evidence stale. Readiness is derived, not a stored absorbing
|
|
|
42
44
|
state. Semantic diagnostics remain advisory; structural, digest, path, or concurrency
|
|
43
45
|
conflicts require repair and retry of the same draft.
|
|
44
46
|
Use `change_contract / cancel` only when the owner intentionally abandons the Change.
|
|
47
|
+
An archived or cancelled Change remains immutable history. When new evidence requires
|
|
48
|
+
continued work, use `change_contract / supersede` to create one linked active successor;
|
|
49
|
+
never patch SQLite, reopen the historical row, or duplicate the relationship manually.
|
|
45
50
|
|
|
46
51
|
Publish the durable Change contract with `ultra.sync { action: publish }` when it is a
|
|
47
52
|
useful team handoff. Recommend bounded `ultra-research` for a real evidence gap or
|
|
@@ -30,7 +30,7 @@ before changing logic. Implement the smallest complete vertical slice. Ask the o
|
|
|
30
30
|
only when new evidence changes product intent, compatibility, security, material cost,
|
|
31
31
|
external effects, or recovery.
|
|
32
32
|
|
|
33
|
-
Use typed `ultra.record` entries for `task_outcome / start|block|complete`,
|
|
33
|
+
Use typed `ultra.record` entries for `task_outcome / start|block|complete|attest_commit`,
|
|
34
34
|
`event / append`, `artifact / bind`, `decision / accept`, and corrected
|
|
35
35
|
`task_contract / revise`. Every worker output must repeat the exact `packet_digest`;
|
|
36
36
|
the parent registers it only after packet, output path, schema, and digest validation.
|
|
@@ -49,9 +49,14 @@ reports outcome diagnostics. The model decides whether the implemented slice sat
|
|
|
49
49
|
the Task; semantic warnings remain visible. A rejection means the declared authority,
|
|
50
50
|
path, digest, or concurrency boundary was unsafe and leaves the draft mutable.
|
|
51
51
|
|
|
52
|
-
After acceptance
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
After acceptance:
|
|
53
|
+
|
|
54
|
+
1. Record `task_outcome / complete { id, packet_digest }`; never include a patch or SHA.
|
|
55
|
+
2. Publish the durable completed status with `ultra.sync`.
|
|
56
|
+
3. Create at most one authorized local task commit containing code, semantic
|
|
57
|
+
artifacts, and the team checkpoint.
|
|
58
|
+
4. Record `task_outcome / attest_commit { id, completion_commit }` with that integrated
|
|
59
|
+
commit's full SHA. This is local mechanical proof and must not create another commit.
|
|
55
60
|
|
|
56
61
|
Release the lease through `ultra.session`. Preserve a dirty or unintegrated worktree
|
|
57
62
|
on interruption. Commit, push, tag, publish, deploy, or destructive cleanup require
|
|
@@ -22,7 +22,12 @@ digests, context, and team checkpoint in one semantic operation.
|
|
|
22
22
|
for a material unresolved choice.
|
|
23
23
|
6. Design a walking skeleton and vertical slices. Each task owns an observable outcome,
|
|
24
24
|
public seam, verification command, acceptance mapping, target seams, bounded
|
|
25
|
-
context refs, documentation impact, recovery, owner, and dependencies.
|
|
25
|
+
context refs, documentation impact, recovery, owner, and dependencies. Set the
|
|
26
|
+
published `type`, `priority`, optional bounded `complexity`/`estimated_days`,
|
|
27
|
+
`slice_kind`, and string-array `deps`/`files_modified` deliberately. Prefer the
|
|
28
|
+
shared vocabulary when it communicates the work, but use a bounded
|
|
29
|
+
repository-specific label when it is clearer; SQLite validates structure and does
|
|
30
|
+
not choose business taxonomy.
|
|
26
31
|
|
|
27
32
|
Large sources stay lazy. Use digest freshness for accepted inputs that must remain
|
|
28
33
|
byte-current, existence for expected implementation targets, and advisory only when
|
|
@@ -34,8 +39,11 @@ question.
|
|
|
34
39
|
## Record and checkpoint
|
|
35
40
|
|
|
36
41
|
Use one `ultra.record` batch with `task_contract / define` for new tasks and
|
|
37
|
-
`task_contract / revise` for corrected contracts
|
|
38
|
-
`ultra.context`.
|
|
42
|
+
`task_contract / revise` for corrected contracts, including a wrong `title` or `type`,
|
|
43
|
+
before accepting the Plan checkpoint. Read back the draft with `ultra.context`.
|
|
44
|
+
Never write status, session, freshness, runtime tag, generated Context, or
|
|
45
|
+
completion-commit fields through a Task Contract; Task outcomes and the runtime own
|
|
46
|
+
them.
|
|
39
47
|
Never read or write `.ultra/tasks/tasks.json`; use `ultra.sync` for the Git team
|
|
40
48
|
checkpoint, and never edit the local runtime projection directly.
|
|
41
49
|
|
|
@@ -49,7 +57,8 @@ idempotency_key: stable semantic Plan checkpoint id
|
|
|
49
57
|
```
|
|
50
58
|
|
|
51
59
|
The model decides whether decomposition, acceptance coverage, and context are
|
|
52
|
-
sufficient and records any deliberate omission.
|
|
60
|
+
sufficient and records any deliberate omission. Missing semantic detail remains a
|
|
61
|
+
visible warning and does not deny an otherwise safe Session lease. The checkpoint compiles or reuses the
|
|
53
62
|
content-addressed Context Envelope, reports advisory coverage diagnostics, exports
|
|
54
63
|
`plan.json` and `plan.md` inside the same recoverable publication, accepts one Plan
|
|
55
64
|
revision, and publishes the team checkpoint. Do not export separately.
|
|
@@ -11,8 +11,10 @@ change-bound research records.
|
|
|
11
11
|
Markdown anchor through `trace_to`.
|
|
12
12
|
5. Check that every task reaches a public consumer and owns its error, documentation,
|
|
13
13
|
verification, and recovery obligations.
|
|
14
|
-
6. Validate dependencies
|
|
14
|
+
6. Validate dependencies. A `quick` profile normally has one vertical task, but the
|
|
15
|
+
model may use the smallest evidence-backed graph that satisfies the accepted Change.
|
|
15
16
|
|
|
16
|
-
Treat the MCP
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Treat the MCP result as a persistence receipt plus typed diagnostics. Structural,
|
|
18
|
+
digest, path, concurrency, and exact topology conflicts must be repaired. The model
|
|
19
|
+
owns semantic completeness, acceptance coverage, research sufficiency, and whether a
|
|
20
|
+
warning requires another planning revision before execution.
|