ultra-builder-pro-cli 0.19.1 → 0.20.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 +24 -1
- package/README.md +10 -7
- package/adapters/_shared/interaction-contract.cjs +26 -4
- package/commands/ultra-research.md +1 -1
- package/docs/AGENT-CONTEXT.md +12 -9
- package/docs/ARCHITECTURE.md +16 -11
- package/docs/DECISIONS.md +11 -7
- package/docs/LEGACY-CLI-CROSSWALK.md +9 -7
- package/docs/ROADMAP.md +4 -3
- package/docs/RUNTIME-COMPAT-MATRIX.md +10 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +1 -1
- package/docs/WORKFLOW-LIFECYCLE.md +45 -25
- package/mcp-server/lib/decision-dialogue.cjs +84 -11
- package/mcp-server/lib/doctor.cjs +2 -1
- package/mcp-server/lib/state-db.cjs +143 -13
- package/mcp-server/lib/workflow-state.cjs +4 -4
- package/mcp-server/server.cjs +5 -1
- package/package.json +1 -1
- package/skills/ultra-change/SKILL.md +12 -5
- package/skills/ultra-plan/SKILL.md +20 -3
- package/skills/ultra-research/SKILL.md +25 -15
- package/skills/ultra-test/SKILL.md +5 -2
- package/skills/ultra-think/SKILL.md +2 -0
- package/skills/ultra-think/references/decision-dialogue.md +47 -11
- package/spec/cli-protocol.md +1 -0
- package/spec/mcp-tools.yaml +30 -3
- package/spec/schemas/state-db.sql +7 -4
- package/ultra-tools/cli.cjs +1 -1
- package/ultra-tools/commands/status.cjs +2 -1
|
@@ -120,6 +120,7 @@ function readDecisionThread(db, id) {
|
|
|
120
120
|
items,
|
|
121
121
|
started_at: row.started_at,
|
|
122
122
|
updated_at: row.updated_at,
|
|
123
|
+
completed_at: row.completed_at,
|
|
123
124
|
confirmed_at: row.confirmed_at,
|
|
124
125
|
};
|
|
125
126
|
}
|
|
@@ -384,6 +385,56 @@ function deferDecision(db, input = {}) {
|
|
|
384
385
|
});
|
|
385
386
|
}
|
|
386
387
|
|
|
388
|
+
function completeDecisionThread(db, input = {}) {
|
|
389
|
+
const id = requiredText(input.id, 'id');
|
|
390
|
+
const summary = requiredText(input.summary, 'summary');
|
|
391
|
+
return ops.tx(db, () => {
|
|
392
|
+
const thread = readDecisionThread(db, id);
|
|
393
|
+
if (!thread) {
|
|
394
|
+
throw new DecisionDialogueError('DECISION_THREAD_NOT_FOUND', `decision thread ${id} not found`);
|
|
395
|
+
}
|
|
396
|
+
if (thread.status === 'checkpoint_ready') {
|
|
397
|
+
throw new DecisionDialogueError(
|
|
398
|
+
'DECISION_CHECKPOINT_PENDING', `confirm or revise checkpoint ${id} before completing the thread`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (thread.status !== 'active') {
|
|
402
|
+
throw new DecisionDialogueError(
|
|
403
|
+
'DECISION_THREAD_NOT_MUTABLE', `decision thread ${id} is ${thread.status}`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const open = thread.items.find((item) => item.status === 'open');
|
|
407
|
+
const deferredBlocking = thread.items.filter((item) => item.blocking && item.status === 'deferred');
|
|
408
|
+
if (open || deferredBlocking.length > 0) {
|
|
409
|
+
const blockers = [
|
|
410
|
+
...(open ? [`DECISION_AWAITING_OWNER:${open.id}`] : []),
|
|
411
|
+
...deferredBlocking.map((item) => `DECISION_DEFERRED_BLOCKING:${item.id}`),
|
|
412
|
+
];
|
|
413
|
+
throw new DecisionDialogueError(
|
|
414
|
+
'DECISION_ALIGNMENT_BLOCKING',
|
|
415
|
+
`decision thread ${id} still contains unresolved blocking state`,
|
|
416
|
+
{ blockers },
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const ts = nowIso();
|
|
420
|
+
db.prepare(
|
|
421
|
+
`UPDATE decision_threads
|
|
422
|
+
SET status = 'completed', summary_json = ?, completed_at = ?, updated_at = ?
|
|
423
|
+
WHERE id = ?`,
|
|
424
|
+
).run(JSON.stringify({
|
|
425
|
+
text: summary,
|
|
426
|
+
completion_kind: 'normalized_state',
|
|
427
|
+
completed_at: ts,
|
|
428
|
+
}), ts, ts, id);
|
|
429
|
+
ops.appendEventInTx(db, {
|
|
430
|
+
type: 'decision_thread_completed',
|
|
431
|
+
change_id: thread.change_id,
|
|
432
|
+
payload: { thread_id: id, item_count: thread.items.length },
|
|
433
|
+
});
|
|
434
|
+
return readDecisionThread(db, id);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
387
438
|
function supersedeDecision(db, input = {}) {
|
|
388
439
|
return ops.tx(db, () => {
|
|
389
440
|
const id = requiredText(input.id, 'id');
|
|
@@ -411,6 +462,15 @@ function supersedeDecision(db, input = {}) {
|
|
|
411
462
|
checkpoint_json = json_set(checkpoint_json, '$.invalidated', json('true'),
|
|
412
463
|
'$.invalidated_at', ?, '$.invalidation_reason', ?), updated_at = ? WHERE id = ?`,
|
|
413
464
|
).run(ts, reason, ts, thread.id);
|
|
465
|
+
} else if (thread.status === 'completed') {
|
|
466
|
+
db.prepare(
|
|
467
|
+
`UPDATE decision_threads
|
|
468
|
+
SET status = 'active', completed_at = NULL,
|
|
469
|
+
summary_json = json_set(summary_json, '$.invalidated', json('true'),
|
|
470
|
+
'$.invalidated_at', ?, '$.invalidation_reason', ?),
|
|
471
|
+
updated_at = ?
|
|
472
|
+
WHERE id = ?`,
|
|
473
|
+
).run(ts, reason, ts, thread.id);
|
|
414
474
|
}
|
|
415
475
|
insertQuestionInTx(db, readDecisionThread(db, thread.id), replacement, id);
|
|
416
476
|
return readDecisionThread(db, thread.id);
|
|
@@ -515,8 +575,10 @@ function checkpointDecisionThread(db, input = {}, { rootDir = process.cwd() } =
|
|
|
515
575
|
if (!thread) {
|
|
516
576
|
throw new DecisionDialogueError('DECISION_THREAD_NOT_FOUND', `decision thread ${id} not found`);
|
|
517
577
|
}
|
|
518
|
-
if (
|
|
519
|
-
throw new DecisionDialogueError(
|
|
578
|
+
if (['completed', 'cancelled'].includes(thread.status)) {
|
|
579
|
+
throw new DecisionDialogueError(
|
|
580
|
+
'DECISION_THREAD_NOT_MUTABLE', `decision thread ${id} is ${thread.status}`,
|
|
581
|
+
);
|
|
520
582
|
}
|
|
521
583
|
const open = thread.items.find((item) => item.status === 'open');
|
|
522
584
|
const deferredBlocking = thread.items.filter((item) => item.blocking && item.status === 'deferred');
|
|
@@ -612,6 +674,7 @@ function decisionGate(db, bindings = {}, { rootDir = process.cwd() } = {}) {
|
|
|
612
674
|
).all(params);
|
|
613
675
|
const blockers = [];
|
|
614
676
|
let selected = null;
|
|
677
|
+
let fallback = null;
|
|
615
678
|
for (const row of rows) {
|
|
616
679
|
const thread = readDecisionThread(db, row.id);
|
|
617
680
|
if (thread.status === 'confirmed') {
|
|
@@ -622,23 +685,25 @@ function decisionGate(db, bindings = {}, { rootDir = process.cwd() } = {}) {
|
|
|
622
685
|
}
|
|
623
686
|
continue;
|
|
624
687
|
}
|
|
625
|
-
|
|
626
|
-
|
|
688
|
+
fallback ||= thread;
|
|
689
|
+
const priorBlockerCount = blockers.length;
|
|
690
|
+
if (thread.current_decision?.blocking) {
|
|
691
|
+
blockers.push(`DECISION_AWAITING_OWNER:${thread.current_decision.id}`);
|
|
692
|
+
}
|
|
627
693
|
for (const item of thread.items) {
|
|
628
694
|
if (item.blocking && item.status === 'deferred') {
|
|
629
695
|
blockers.push(`DECISION_DEFERRED_BLOCKING:${item.id}`);
|
|
630
696
|
}
|
|
631
697
|
}
|
|
632
698
|
if (thread.status === 'checkpoint_ready') blockers.push(`DECISION_CHECKPOINT_CONFIRMATION_REQUIRED:${thread.id}`);
|
|
633
|
-
|
|
634
|
-
blockers.push(`DECISION_CHECKPOINT_REQUIRED:${thread.id}`);
|
|
635
|
-
}
|
|
699
|
+
if (blockers.length > priorBlockerCount) selected ||= thread;
|
|
636
700
|
}
|
|
701
|
+
const reported = selected || fallback;
|
|
637
702
|
return {
|
|
638
703
|
ready: blockers.length === 0,
|
|
639
704
|
blockers: [...new Set(blockers)],
|
|
640
|
-
thread:
|
|
641
|
-
current_decision:
|
|
705
|
+
thread: reported,
|
|
706
|
+
current_decision: reported?.current_decision || null,
|
|
642
707
|
};
|
|
643
708
|
}
|
|
644
709
|
|
|
@@ -656,8 +721,10 @@ function assertDecisionGate(db, bindings = {}, options = {}) {
|
|
|
656
721
|
function inspectDecisionHealth(db, { rootDir = process.cwd() } = {}) {
|
|
657
722
|
const threads = listDecisionThreads(db, { limit: 500 });
|
|
658
723
|
const active = threads.filter((thread) => thread.status === 'active');
|
|
724
|
+
const completed = threads.filter((thread) => thread.status === 'completed');
|
|
659
725
|
const checkpointReady = threads.filter((thread) => thread.status === 'checkpoint_ready');
|
|
660
726
|
const awaiting = active.filter((thread) => thread.current_decision);
|
|
727
|
+
const awaitingBlocking = awaiting.filter((thread) => thread.current_decision.blocking);
|
|
661
728
|
const deferredBlocking = threads.flatMap((thread) => thread.items.filter(
|
|
662
729
|
(item) => item.blocking && item.status === 'deferred',
|
|
663
730
|
));
|
|
@@ -678,15 +745,20 @@ function inspectDecisionHealth(db, { rootDir = process.cwd() } = {}) {
|
|
|
678
745
|
}
|
|
679
746
|
}
|
|
680
747
|
}
|
|
681
|
-
|
|
682
|
-
|
|
748
|
+
let currentThread = awaitingBlocking[0] || checkpointReady[0] || awaiting[0] || active[0] || null;
|
|
749
|
+
if (staleArtifacts.length > 0) {
|
|
750
|
+
currentThread = threads.find((thread) => thread.id === staleArtifacts[0].thread_id) || currentThread;
|
|
751
|
+
}
|
|
752
|
+
const currentThreadId = staleArtifacts[0]?.thread_id || currentThread?.id || null;
|
|
683
753
|
const status = staleArtifacts.length > 0
|
|
684
754
|
? 'fail'
|
|
685
755
|
: (active.length > 0 || checkpointReady.length > 0 || deferredBlocking.length > 0 ? 'warning' : 'pass');
|
|
686
756
|
return {
|
|
687
757
|
status,
|
|
688
758
|
active: active.length,
|
|
759
|
+
completed: completed.length,
|
|
689
760
|
awaiting_owner: awaiting.length,
|
|
761
|
+
awaiting_blocking: awaitingBlocking.length,
|
|
690
762
|
checkpoint_ready: checkpointReady.length,
|
|
691
763
|
deferred_blocking: deferredBlocking.length,
|
|
692
764
|
stale_artifacts: staleArtifacts,
|
|
@@ -704,6 +776,7 @@ module.exports = {
|
|
|
704
776
|
resolveDecision,
|
|
705
777
|
delegateDecision,
|
|
706
778
|
deferDecision,
|
|
779
|
+
completeDecisionThread,
|
|
707
780
|
supersedeDecision,
|
|
708
781
|
checkpointDecisionThread,
|
|
709
782
|
decisionGate,
|
|
@@ -67,7 +67,8 @@ function inspectSystem(db, { rootDir = process.cwd() } = {}) {
|
|
|
67
67
|
const decisionHealth = missing.length === 0
|
|
68
68
|
? decisions.inspectDecisionHealth(db, { rootDir })
|
|
69
69
|
: {
|
|
70
|
-
status: 'fail', active: 0, awaiting_owner: 0,
|
|
70
|
+
status: 'fail', active: 0, completed: 0, awaiting_owner: 0, awaiting_blocking: 0,
|
|
71
|
+
checkpoint_ready: 0,
|
|
71
72
|
deferred_blocking: 0, stale_artifacts: [], current: null, current_thread_id: null,
|
|
72
73
|
};
|
|
73
74
|
const baselineCheckStatus = baseline.status === 'pass'
|
|
@@ -9,7 +9,7 @@ const REPO_ROOT = process.env.UBP_RUNTIME_ROOT
|
|
|
9
9
|
? path.resolve(process.env.UBP_RUNTIME_ROOT)
|
|
10
10
|
: path.resolve(__dirname, '..', '..');
|
|
11
11
|
const SCHEMA_FILE = path.join(REPO_ROOT, 'spec', 'schemas', 'state-db.sql');
|
|
12
|
-
const EXPECTED_VERSION = '
|
|
12
|
+
const EXPECTED_VERSION = '19.0';
|
|
13
13
|
const KIMI_SCHEMA_VERSION = '9.1';
|
|
14
14
|
const CONTEXT_SCHEMA_VERSION = '10.0';
|
|
15
15
|
const BASELINE_SCHEMA_VERSION = '11.0';
|
|
@@ -19,6 +19,8 @@ const AUTHORITY_SCHEMA_VERSION = '14.0';
|
|
|
19
19
|
const SEMANTIC_SCHEMA_VERSION = '15.0';
|
|
20
20
|
const DIALOGUE_SCHEMA_VERSION = '16.0';
|
|
21
21
|
const GIT_AUTHORITY_SCHEMA_VERSION = '17.0';
|
|
22
|
+
const ADAPTIVE_SCHEMA_VERSION = '18.0';
|
|
23
|
+
const DECISION_COMPLETION_SCHEMA_VERSION = '19.0';
|
|
22
24
|
|
|
23
25
|
const MIGRATED_GAPS = Object.freeze([{
|
|
24
26
|
id: 'legacy-rebaseline-required',
|
|
@@ -328,7 +330,9 @@ function migrateAdaptiveWorkflowRuns(db, fromVersion = latestSchemaVersion(db))
|
|
|
328
330
|
const existing = db.prepare(
|
|
329
331
|
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
330
332
|
).get();
|
|
331
|
-
if (fromVersion
|
|
333
|
+
if ([ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION].includes(fromVersion) || existing) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
332
336
|
|
|
333
337
|
const obsoleteSteps = Object.freeze({
|
|
334
338
|
init: ['establish-baseline'],
|
|
@@ -455,6 +459,95 @@ function baselineSupportsUnbornGit(db) {
|
|
|
455
459
|
return typeof row?.sql === 'string' && row.sql.includes("'unborn'");
|
|
456
460
|
}
|
|
457
461
|
|
|
462
|
+
function decisionThreadsSupportCompletion(db) {
|
|
463
|
+
const row = db.prepare(
|
|
464
|
+
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'decision_threads'",
|
|
465
|
+
).get();
|
|
466
|
+
if (typeof row?.sql !== 'string' || !row.sql.includes("'completed'")) return false;
|
|
467
|
+
return columnNames(db, 'decision_threads').has('completed_at');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function upgradeDecisionThreadLifecycle(db) {
|
|
471
|
+
if (!tableNames(db).includes('decision_threads') || decisionThreadsSupportCompletion(db)) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
const foreignKeys = db.pragma('foreign_keys', { simple: true });
|
|
475
|
+
db.pragma('foreign_keys = OFF');
|
|
476
|
+
try {
|
|
477
|
+
db.transaction(() => {
|
|
478
|
+
db.exec(`
|
|
479
|
+
DROP TABLE IF EXISTS decision_threads_completion_upgrade;
|
|
480
|
+
CREATE TABLE decision_threads_completion_upgrade (
|
|
481
|
+
id TEXT PRIMARY KEY,
|
|
482
|
+
purpose TEXT NOT NULL,
|
|
483
|
+
mode TEXT NOT NULL DEFAULT 'guided'
|
|
484
|
+
CHECK (mode IN ('guided', 'fast', 'autonomous', 'diagnostic')),
|
|
485
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
486
|
+
CHECK (status IN ('active', 'checkpoint_ready', 'completed', 'confirmed', 'cancelled')),
|
|
487
|
+
baseline_id TEXT REFERENCES baselines(id) ON DELETE SET NULL,
|
|
488
|
+
change_id TEXT REFERENCES changes(id) ON DELETE CASCADE,
|
|
489
|
+
workflow_run_id TEXT REFERENCES workflow_runs(id) ON DELETE CASCADE,
|
|
490
|
+
summary_json TEXT NOT NULL DEFAULT '{}',
|
|
491
|
+
checkpoint_json TEXT NOT NULL DEFAULT '{}',
|
|
492
|
+
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
493
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
494
|
+
completed_at TEXT,
|
|
495
|
+
confirmed_at TEXT,
|
|
496
|
+
CHECK (baseline_id IS NOT NULL OR change_id IS NOT NULL OR workflow_run_id IS NOT NULL)
|
|
497
|
+
);
|
|
498
|
+
INSERT INTO decision_threads_completion_upgrade (
|
|
499
|
+
id, purpose, mode, status, baseline_id, change_id, workflow_run_id,
|
|
500
|
+
summary_json, checkpoint_json, started_at, updated_at, completed_at, confirmed_at
|
|
501
|
+
)
|
|
502
|
+
SELECT
|
|
503
|
+
id, purpose, mode, status, baseline_id, change_id, workflow_run_id,
|
|
504
|
+
summary_json, checkpoint_json, started_at, updated_at, NULL, confirmed_at
|
|
505
|
+
FROM decision_threads;
|
|
506
|
+
DROP TABLE decision_threads;
|
|
507
|
+
ALTER TABLE decision_threads_completion_upgrade RENAME TO decision_threads;
|
|
508
|
+
CREATE INDEX decision_threads_status
|
|
509
|
+
ON decision_threads(status, updated_at);
|
|
510
|
+
CREATE INDEX decision_threads_authority
|
|
511
|
+
ON decision_threads(baseline_id, change_id, workflow_run_id, status);
|
|
512
|
+
`);
|
|
513
|
+
db.prepare(
|
|
514
|
+
`UPDATE decision_threads
|
|
515
|
+
SET status = 'completed',
|
|
516
|
+
summary_json = CASE
|
|
517
|
+
WHEN summary_json = '{}' THEN ?
|
|
518
|
+
ELSE json_set(summary_json, '$.completion_kind', 'migrated_settled_thread')
|
|
519
|
+
END,
|
|
520
|
+
completed_at = updated_at
|
|
521
|
+
WHERE status = 'active'
|
|
522
|
+
AND EXISTS (
|
|
523
|
+
SELECT 1 FROM decision_items WHERE decision_items.thread_id = decision_threads.id
|
|
524
|
+
)
|
|
525
|
+
AND NOT EXISTS (
|
|
526
|
+
SELECT 1 FROM decision_items
|
|
527
|
+
WHERE decision_items.thread_id = decision_threads.id AND status = 'open'
|
|
528
|
+
)
|
|
529
|
+
AND NOT EXISTS (
|
|
530
|
+
SELECT 1 FROM decision_items
|
|
531
|
+
WHERE decision_items.thread_id = decision_threads.id
|
|
532
|
+
AND blocking = 1 AND status = 'deferred'
|
|
533
|
+
)`,
|
|
534
|
+
).run(JSON.stringify({
|
|
535
|
+
text: 'Existing normalized decision state was completed during schema migration.',
|
|
536
|
+
completion_kind: 'migrated_settled_thread',
|
|
537
|
+
}));
|
|
538
|
+
const violations = db.pragma('foreign_key_check');
|
|
539
|
+
if (violations.length > 0) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`decision completion migration produced ${violations.length} foreign key violation(s)`,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
})();
|
|
545
|
+
} finally {
|
|
546
|
+
db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
|
|
547
|
+
}
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
458
551
|
function upgradeBaselineWorktreeConstraint(db, fromVersion = latestSchemaVersion(db)) {
|
|
459
552
|
if (!tableNames(db).includes('baselines') || baselineSupportsUnbornGit(db)) return false;
|
|
460
553
|
const previousVersion = fromVersion || 'unknown';
|
|
@@ -611,6 +704,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
611
704
|
})();
|
|
612
705
|
}
|
|
613
706
|
const gitStateChangedFrom = upgradeBaselineWorktreeConstraint(db, fromVersion);
|
|
707
|
+
const decisionCompletionChanged = upgradeDecisionThreadLifecycle(db);
|
|
614
708
|
db.transaction(() => {
|
|
615
709
|
applyCompatibleColumns(db);
|
|
616
710
|
const contextChanged = applyContextSpineUpgrade(db);
|
|
@@ -621,7 +715,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
621
715
|
fromVersion && ![
|
|
622
716
|
CONTEXT_SCHEMA_VERSION, BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION,
|
|
623
717
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
624
|
-
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
718
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
719
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
625
720
|
].includes(fromVersion)
|
|
626
721
|
&& !contextMigration,
|
|
627
722
|
);
|
|
@@ -646,7 +741,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
646
741
|
if (fromVersion && ![
|
|
647
742
|
BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION, WORKFLOW_SCHEMA_VERSION,
|
|
648
743
|
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
649
|
-
GIT_AUTHORITY_SCHEMA_VERSION,
|
|
744
|
+
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
745
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
650
746
|
].includes(fromVersion)
|
|
651
747
|
&& !baselineMigration) {
|
|
652
748
|
db.prepare(
|
|
@@ -715,7 +811,11 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
715
811
|
const workflowMigration = db.prepare(
|
|
716
812
|
"SELECT 1 FROM migration_history WHERE to_version = '13.0' AND status = 'success' LIMIT 1",
|
|
717
813
|
).get();
|
|
718
|
-
if (fromVersion &&
|
|
814
|
+
if (fromVersion && ![
|
|
815
|
+
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
816
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
817
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
818
|
+
].includes(fromVersion) && !workflowMigration) {
|
|
719
819
|
db.prepare(
|
|
720
820
|
`INSERT INTO migration_history
|
|
721
821
|
(from_version, to_version, direction, status, notes)
|
|
@@ -729,7 +829,10 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
729
829
|
const authorityMigration = db.prepare(
|
|
730
830
|
"SELECT 1 FROM migration_history WHERE to_version = '14.0' AND status = 'success' LIMIT 1",
|
|
731
831
|
).get();
|
|
732
|
-
if (fromVersion &&
|
|
832
|
+
if (fromVersion && ![
|
|
833
|
+
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
834
|
+
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
835
|
+
].includes(fromVersion) && !authorityMigration) {
|
|
733
836
|
db.prepare(
|
|
734
837
|
`INSERT INTO migration_history
|
|
735
838
|
(from_version, to_version, direction, status, notes)
|
|
@@ -743,7 +846,10 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
743
846
|
const semanticMigration = db.prepare(
|
|
744
847
|
"SELECT 1 FROM migration_history WHERE to_version = '15.0' AND status = 'success' LIMIT 1",
|
|
745
848
|
).get();
|
|
746
|
-
if (fromVersion &&
|
|
849
|
+
if (fromVersion && ![
|
|
850
|
+
SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
851
|
+
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
852
|
+
].includes(fromVersion) && !semanticMigration) {
|
|
747
853
|
db.prepare(
|
|
748
854
|
`INSERT INTO migration_history
|
|
749
855
|
(from_version, to_version, direction, status, notes)
|
|
@@ -757,7 +863,10 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
757
863
|
const dialogueMigration = db.prepare(
|
|
758
864
|
"SELECT 1 FROM migration_history WHERE to_version = '16.0' AND status = 'success' LIMIT 1",
|
|
759
865
|
).get();
|
|
760
|
-
if (fromVersion && ![
|
|
866
|
+
if (fromVersion && ![
|
|
867
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
868
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
869
|
+
].includes(fromVersion)
|
|
761
870
|
&& !dialogueMigration) {
|
|
762
871
|
db.prepare(
|
|
763
872
|
`INSERT INTO migration_history
|
|
@@ -772,7 +881,9 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
772
881
|
const gitStateMigration = db.prepare(
|
|
773
882
|
"SELECT 1 FROM migration_history WHERE to_version = '17.0' AND status = 'success' LIMIT 1",
|
|
774
883
|
).get();
|
|
775
|
-
if (fromVersion && ![
|
|
884
|
+
if (fromVersion && ![
|
|
885
|
+
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
886
|
+
].includes(fromVersion)
|
|
776
887
|
&& !gitStateMigration) {
|
|
777
888
|
db.prepare(
|
|
778
889
|
`INSERT INTO migration_history
|
|
@@ -788,26 +899,44 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
788
899
|
const adaptiveMigration = db.prepare(
|
|
789
900
|
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
790
901
|
).get();
|
|
791
|
-
if (fromVersion &&
|
|
902
|
+
if (fromVersion && ![
|
|
903
|
+
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
904
|
+
].includes(fromVersion) && !adaptiveMigration) {
|
|
792
905
|
db.prepare(
|
|
793
906
|
`INSERT INTO migration_history
|
|
794
907
|
(from_version, to_version, direction, status, notes)
|
|
795
908
|
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
796
909
|
).run(
|
|
797
910
|
fromVersion === GIT_AUTHORITY_SCHEMA_VERSION ? fromVersion : GIT_AUTHORITY_SCHEMA_VERSION,
|
|
798
|
-
|
|
911
|
+
ADAPTIVE_SCHEMA_VERSION,
|
|
799
912
|
adaptiveChanged
|
|
800
913
|
? 'Migrate active rigid workflows to adaptive capability transitions and independent initialization'
|
|
801
914
|
: 'Add adaptive capability transitions and independent initialization authority',
|
|
802
915
|
);
|
|
803
916
|
}
|
|
917
|
+
const completionMigration = db.prepare(
|
|
918
|
+
"SELECT 1 FROM migration_history WHERE to_version = '19.0' AND status = 'success' LIMIT 1",
|
|
919
|
+
).get();
|
|
920
|
+
if (fromVersion && fromVersion !== DECISION_COMPLETION_SCHEMA_VERSION && !completionMigration) {
|
|
921
|
+
db.prepare(
|
|
922
|
+
`INSERT INTO migration_history
|
|
923
|
+
(from_version, to_version, direction, status, notes)
|
|
924
|
+
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
925
|
+
).run(
|
|
926
|
+
ADAPTIVE_SCHEMA_VERSION,
|
|
927
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
928
|
+
decisionCompletionChanged
|
|
929
|
+
? 'Add non-ceremonial decision completion and migrate settled active threads'
|
|
930
|
+
: 'Add non-ceremonial decision completion authority',
|
|
931
|
+
);
|
|
932
|
+
}
|
|
804
933
|
db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
|
|
805
934
|
db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
|
|
806
935
|
db.prepare(
|
|
807
936
|
'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
|
|
808
937
|
).run(
|
|
809
938
|
EXPECTED_VERSION,
|
|
810
|
-
'
|
|
939
|
+
'Non-ceremonial decision completion and host-neutral intent persistence',
|
|
811
940
|
);
|
|
812
941
|
})();
|
|
813
942
|
}
|
|
@@ -837,7 +966,8 @@ function initStateDb(dbPath) {
|
|
|
837
966
|
const legacyState = existing.size > 0
|
|
838
967
|
&& ![
|
|
839
968
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
840
|
-
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
969
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
970
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
841
971
|
].includes(fromVersion);
|
|
842
972
|
backupPath = migrationBackup(db, dbPath, fromVersion, existing);
|
|
843
973
|
applyCompatibleColumns(db);
|
|
@@ -647,7 +647,7 @@ function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {})
|
|
|
647
647
|
if (selectionReason.length < 3) {
|
|
648
648
|
throw new WorkflowStateError(
|
|
649
649
|
'WORKFLOW_SELECTION_REASON_REQUIRED',
|
|
650
|
-
`${mode} research requires
|
|
650
|
+
`${mode} research requires an evidence-based rationale for the accepted coverage`,
|
|
651
651
|
);
|
|
652
652
|
}
|
|
653
653
|
const synthesis = normalized.find((item) => item.step_id === '99-synthesis');
|
|
@@ -679,7 +679,7 @@ function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {})
|
|
|
679
679
|
if (!Array.isArray(selectedSteps) || selectedSteps.length === 0) {
|
|
680
680
|
throw new WorkflowStateError(
|
|
681
681
|
'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
|
|
682
|
-
'custom research requires
|
|
682
|
+
'custom research requires accepted coverage or selected_steps',
|
|
683
683
|
);
|
|
684
684
|
}
|
|
685
685
|
const invalid = selectedSteps.find((item) => !known.has(item));
|
|
@@ -688,7 +688,7 @@ function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {})
|
|
|
688
688
|
if (selectionReason.length < 3) {
|
|
689
689
|
throw new WorkflowStateError(
|
|
690
690
|
'WORKFLOW_SELECTION_REASON_REQUIRED',
|
|
691
|
-
'custom research requires
|
|
691
|
+
'custom research requires an evidence-based rationale for the accepted coverage',
|
|
692
692
|
);
|
|
693
693
|
}
|
|
694
694
|
const explicitCoverage = [...new Set([...selectedSteps, '99-synthesis'])].map((stepId) => ({
|
|
@@ -701,7 +701,7 @@ function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {})
|
|
|
701
701
|
}
|
|
702
702
|
throw new WorkflowStateError(
|
|
703
703
|
'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
|
|
704
|
-
`${mode} research requires
|
|
704
|
+
`${mode} research requires accepted coverage dispositions`,
|
|
705
705
|
);
|
|
706
706
|
}
|
|
707
707
|
|
package/mcp-server/server.cjs
CHANGED
|
@@ -112,6 +112,7 @@ const DECISION_TOOLS = Object.freeze([
|
|
|
112
112
|
'decision.delegate',
|
|
113
113
|
'decision.defer',
|
|
114
114
|
'decision.supersede',
|
|
115
|
+
'decision.complete',
|
|
115
116
|
'decision.checkpoint',
|
|
116
117
|
]);
|
|
117
118
|
|
|
@@ -139,7 +140,7 @@ const MUTATING_TOOLS = new Set([
|
|
|
139
140
|
'change.create', 'change.update', 'change.context', 'change.converge', 'change.archive',
|
|
140
141
|
'change.learning_propose', 'change.learning_resolve',
|
|
141
142
|
'decision.thread_start', 'decision.open', 'decision.resolve', 'decision.delegate',
|
|
142
|
-
'decision.defer', 'decision.supersede', 'decision.checkpoint',
|
|
143
|
+
'decision.defer', 'decision.supersede', 'decision.complete', 'decision.checkpoint',
|
|
143
144
|
'workflow.start', 'workflow.step', 'workflow.complete',
|
|
144
145
|
]);
|
|
145
146
|
|
|
@@ -576,6 +577,9 @@ async function dispatchTool(name, input, db, ctx = {}) {
|
|
|
576
577
|
case 'decision.supersede': {
|
|
577
578
|
return { thread: decisions.supersedeDecision(db, input) };
|
|
578
579
|
}
|
|
580
|
+
case 'decision.complete': {
|
|
581
|
+
return { thread: decisions.completeDecisionThread(db, input) };
|
|
582
|
+
}
|
|
579
583
|
case 'decision.checkpoint': {
|
|
580
584
|
return {
|
|
581
585
|
thread: decisions.checkpointDecisionThread(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-builder-pro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Explicitly invoked Ultra Builder Pro workflows for Claude Code, OpenCode, Codex CLI, and Kimi Code, with project-local MCP authority and lifecycle recovery.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ultra-builder-pro-cli": "bin/install.js",
|
|
@@ -29,13 +29,18 @@ Read `../ultra-think/references/decision-dialogue.md` before asking a material q
|
|
|
29
29
|
- change kind and evidence-backed risk flags;
|
|
30
30
|
- research disposition.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
change may have no material risk,
|
|
32
|
+
Recommend `quick`, `standard`, `major`, or `incident` from actual scope and risk,
|
|
33
|
+
together with the research disposition. A quick change may have no material risk,
|
|
34
|
+
research obligation, or more than one task. MCP may reject a profile that contradicts
|
|
35
|
+
hard risk invariants; it does not choose a replacement.
|
|
34
36
|
|
|
35
37
|
Ask only when a choice changes accepted product intent, scope, public behavior,
|
|
36
38
|
compatibility, security, material cost, external effects, or recovery. Use the host's
|
|
37
39
|
native question UI when available. Normalize clear choices already in the user's
|
|
38
|
-
request.
|
|
40
|
+
request. When profile or research posture remains unresolved, present the recommended
|
|
41
|
+
route and credible alternatives through that same UI, then stop. The user selects,
|
|
42
|
+
modifies, delegates, or defers the semantic route. The model owns reversible
|
|
43
|
+
implementation detail inside the accepted contract.
|
|
39
44
|
|
|
40
45
|
## Persist the contract
|
|
41
46
|
|
|
@@ -70,5 +75,7 @@ transitions. External memory and graph providers retain their own content; Ultra
|
|
|
70
75
|
metadata references only.
|
|
71
76
|
|
|
72
77
|
Return the normalized contract, change id and kind, research disposition, blockers,
|
|
73
|
-
and `allowed_transitions`.
|
|
74
|
-
|
|
78
|
+
and `allowed_transitions`. Recommend a semantic next action from those transitions.
|
|
79
|
+
If current intent does not already select it, present the recommendation and credible
|
|
80
|
+
alternatives through the interaction protocol, then wait. MCP does not encode a
|
|
81
|
+
semantic next action.
|
|
@@ -15,17 +15,34 @@ accepted Change Contract without adding ceremonial approval.
|
|
|
15
15
|
checkout.
|
|
16
16
|
2. Require one mutable change with a healthy baseline or recorded incident bypass.
|
|
17
17
|
3. Complete the exact change-bound research disposition before planning.
|
|
18
|
-
4. Resume the matching plan run
|
|
18
|
+
4. Resume the matching plan run. Do not start a new run until planning posture is
|
|
19
|
+
accepted.
|
|
19
20
|
|
|
20
21
|
Read `references/semantic-preflight.md` and inspect requirements, real consumers,
|
|
21
22
|
state, tests, deployment, and recovery paths.
|
|
22
23
|
|
|
24
|
+
## Align planning posture
|
|
25
|
+
|
|
26
|
+
Use the interaction protocol in `../ultra-think/references/decision-dialogue.md`.
|
|
27
|
+
Normalize a posture already explicit in current intent. Otherwise recommend one based
|
|
28
|
+
on the Change Contract, explain its scope effect, and use the host's native question
|
|
29
|
+
surface:
|
|
30
|
+
|
|
31
|
+
- `EXPAND`: surface evidence-backed opportunities beyond accepted scope;
|
|
32
|
+
- `SELECTIVE`: hold accepted scope and offer optional expansions individually;
|
|
33
|
+
- `HOLD`: preserve scope and strengthen completeness, failure handling, and recovery;
|
|
34
|
+
- `REDUCE`: propose the smallest outcome that still satisfies revised acceptance.
|
|
35
|
+
|
|
36
|
+
The user may select, modify, delegate, or defer the posture. Stop on an unanswered
|
|
37
|
+
choice. Start `workflow.start` only after alignment and store `planning_posture` plus
|
|
38
|
+
its rationale in workflow metadata. This is a scope boundary, not approval of the
|
|
39
|
+
eventual technical design.
|
|
40
|
+
|
|
23
41
|
## Design with model autonomy
|
|
24
42
|
|
|
25
43
|
Build a candidate plan privately. Ask the user only if the plan would change accepted
|
|
26
44
|
scope, public behavior, compatibility, security, material cost, external effects, or
|
|
27
|
-
recovery. Use the host
|
|
28
|
-
in `../ultra-think/references/decision-dialogue.md`.
|
|
45
|
+
recovery. Use the same host-native interaction protocol.
|
|
29
46
|
|
|
30
47
|
When the candidate remains inside the accepted Change Contract, proceed without a
|
|
31
48
|
second approval. A user may still request a plan review before persistence; that is an
|