ultra-builder-pro-cli 0.15.0 → 0.17.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/.ultra-template/specs/research-distillate.md +4 -3
- package/CHANGELOG.md +120 -1
- package/README.md +67 -30
- package/adapters/_shared/codex-assets.cjs +10 -5
- package/adapters/_shared/handbook.cjs +3 -2
- package/adapters/_shared/interaction-contract.cjs +61 -0
- package/adapters/claude.js +1 -1
- package/adapters/opencode.js +1 -1
- package/bin/orchestrator.js +175 -19
- package/commands/ultra-change.md +1 -1
- package/commands/ultra-deliver.md +3 -3
- package/commands/ultra-init.md +1 -1
- package/commands/ultra-plan.md +1 -1
- package/commands/ultra-research.md +2 -2
- package/commands/ultra-review.md +10 -0
- package/commands/ultra-status.md +1 -1
- package/commands/ultra-test.md +1 -1
- package/commands/ultra-think.md +2 -2
- package/docs/AGENT-CONTEXT.md +38 -18
- package/docs/ARCHITECTURE.md +91 -13
- package/docs/LEGACY-CLI-CROSSWALK.md +163 -0
- package/docs/PLAN.zh-CN.md +19 -12
- package/docs/ROADMAP.md +5 -3
- package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +8 -6
- package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
- package/docs/WORKFLOW-LIFECYCLE.md +179 -39
- package/hooks/pre_stop_check.py +3 -2
- package/mcp-server/lib/baseline-workflow.cjs +84 -16
- package/mcp-server/lib/change-workflow.cjs +107 -39
- package/mcp-server/lib/context-spine.cjs +215 -87
- package/mcp-server/lib/decision-dialogue.cjs +713 -0
- package/mcp-server/lib/doctor.cjs +9 -0
- package/mcp-server/lib/git-bootstrap.cjs +240 -0
- package/mcp-server/lib/init-project.cjs +178 -41
- package/mcp-server/lib/project-breadcrumb.cjs +23 -14
- package/mcp-server/lib/state-db.cjs +279 -9
- package/mcp-server/lib/state-ops.cjs +64 -1
- package/mcp-server/lib/workflow-state.cjs +313 -205
- package/mcp-server/server.cjs +112 -34
- package/orchestrator/auto-merge.cjs +19 -0
- package/orchestrator/daemon.cjs +104 -0
- package/orchestrator/parallel-orchestrator.cjs +371 -49
- package/orchestrator/session-runner.cjs +354 -25
- package/package.json +2 -1
- package/skills/ultra-change/SKILL.md +69 -65
- package/skills/ultra-deliver/SKILL.md +37 -58
- package/skills/ultra-dev/SKILL.md +55 -49
- package/skills/ultra-doctor/SKILL.md +44 -49
- package/skills/ultra-init/SKILL.md +78 -100
- package/skills/ultra-plan/SKILL.md +78 -79
- package/skills/ultra-research/SKILL.md +99 -98
- package/skills/ultra-review/SKILL.md +38 -47
- package/skills/ultra-review/references/review-modes.md +5 -3
- package/skills/ultra-review/references/unified-schema.md +26 -6
- package/skills/ultra-review/scripts/review_wait.py +12 -0
- package/skills/ultra-status/SKILL.md +34 -40
- package/skills/ultra-test/SKILL.md +61 -48
- package/skills/ultra-think/SKILL.md +47 -47
- package/skills/ultra-think/references/decision-dialogue.md +106 -0
- package/spec/cli-protocol.md +46 -2
- package/spec/mcp-tools.yaml +444 -29
- package/spec/schemas/delivery-report.v1.schema.json +1 -46
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +66 -2
- package/spec/schemas/test-report.v1.schema.json +45 -14
- package/ultra-tools/cli.cjs +1 -1
- package/ultra-tools/commands/session.cjs +10 -3
- package/ultra-tools/commands/status.cjs +34 -14
- package/ultra-tools/commands/system.cjs +2 -2
- package/ultra-tools/commands/task.cjs +3 -0
|
@@ -13,6 +13,7 @@ const providerRefs = require('./provider-refs.cjs');
|
|
|
13
13
|
const baselines = require('./baseline-workflow.cjs');
|
|
14
14
|
const archiveJournal = require('./archive-journal.cjs');
|
|
15
15
|
const workflows = require('./workflow-state.cjs');
|
|
16
|
+
const decisions = require('./decision-dialogue.cjs');
|
|
16
17
|
const reconciliationSchema = require('../../spec/schemas/baseline-reconciliation.v1.schema.json');
|
|
17
18
|
|
|
18
19
|
const reconciliationAjv = new Ajv({ allErrors: true, strict: false });
|
|
@@ -23,6 +24,10 @@ const CHANGE_PATCH_FIELDS = new Set([
|
|
|
23
24
|
'title', 'intent', 'status', 'docs_impact', 'provider_refs',
|
|
24
25
|
'contract', 'classification', 'research_disposition',
|
|
25
26
|
]);
|
|
27
|
+
const SEMANTIC_AUTHORITY_FIELDS = new Set([
|
|
28
|
+
'intent', 'docs_impact', 'provider_refs', 'contract', 'classification',
|
|
29
|
+
'research_disposition',
|
|
30
|
+
]);
|
|
26
31
|
const CHANGE_TRANSITIONS = Object.freeze({
|
|
27
32
|
active: new Set(['active', 'blocked', 'cancelled']),
|
|
28
33
|
blocked: new Set(['active', 'blocked', 'cancelled']),
|
|
@@ -437,6 +442,7 @@ function writeIntent(file, change) {
|
|
|
437
442
|
`- Base commit: \`${change.base_commit || 'unavailable'}\``,
|
|
438
443
|
`- Documentation impact: \`${impact.status}\``,
|
|
439
444
|
`- Research disposition: \`${change.research_disposition.status}\``,
|
|
445
|
+
`- Alignment checkpoint: \`${change.alignment_thread_id || 'not-required'}\``,
|
|
440
446
|
'',
|
|
441
447
|
'## Intent',
|
|
442
448
|
'',
|
|
@@ -542,9 +548,12 @@ function compileContext(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
542
548
|
).all(change.id).map((task) => ops.readTask(db, task.id));
|
|
543
549
|
const specs = Array.isArray(input.spec_refs) ? input.spec_refs : [];
|
|
544
550
|
const spine = contextSpine.compileRoleContext(db, { input, change, tasks, rootDir });
|
|
551
|
+
const recommendation = spine.recommendation;
|
|
552
|
+
const durableSpine = { ...spine };
|
|
553
|
+
delete durableSpine.recommendation;
|
|
545
554
|
const snapshotId = `ctx-${crypto.randomUUID().slice(0, 12)}`;
|
|
546
555
|
const manifest = {
|
|
547
|
-
schema_version: '
|
|
556
|
+
schema_version: '3.0',
|
|
548
557
|
snapshot_id: snapshotId,
|
|
549
558
|
generated_at: nowIso(),
|
|
550
559
|
change: {
|
|
@@ -564,7 +573,7 @@ function compileContext(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
564
573
|
specs,
|
|
565
574
|
role: spine.role,
|
|
566
575
|
gate: spine.gate,
|
|
567
|
-
|
|
576
|
+
control: spine.control,
|
|
568
577
|
readiness: spine.readiness,
|
|
569
578
|
context: spine.context,
|
|
570
579
|
execution_contract: spine.execution_contract,
|
|
@@ -586,14 +595,16 @@ function compileContext(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
586
595
|
db.prepare(
|
|
587
596
|
`INSERT INTO context_snapshots
|
|
588
597
|
(id, change_id, task_id, git_head, provider_refs_json, manifest_path, manifest_hash,
|
|
589
|
-
role, gate, next_action,
|
|
590
|
-
token_estimate, token_budget)
|
|
591
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
598
|
+
role, gate, next_action, allowed_transitions_json, required_transition,
|
|
599
|
+
readiness, blockers_json, context_json, token_estimate, token_budget)
|
|
600
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
592
601
|
).run(
|
|
593
602
|
snapshotId, change.id, input.task_id || null,
|
|
594
603
|
spine.resume.git_head, JSON.stringify(providers), relative, hash,
|
|
595
|
-
spine.role, spine.gate,
|
|
596
|
-
JSON.stringify(spine.
|
|
604
|
+
spine.role, spine.gate, '',
|
|
605
|
+
JSON.stringify(spine.control.allowed_transitions), spine.control.required_transition,
|
|
606
|
+
spine.readiness.status,
|
|
607
|
+
JSON.stringify(spine.readiness.blockers), JSON.stringify(durableSpine),
|
|
597
608
|
spine.context.token_estimate, spine.context.budget.max_tokens,
|
|
598
609
|
);
|
|
599
610
|
upsertArtifact(db, {
|
|
@@ -621,34 +632,17 @@ function compileContext(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
621
632
|
manifest_path: relative, manifest_hash: hash, git_head: spine.resume.git_head,
|
|
622
633
|
worktree_digest: spine.resume.worktree_digest,
|
|
623
634
|
role: spine.role, gate: spine.gate, readiness: spine.readiness.status,
|
|
624
|
-
|
|
635
|
+
allowed_transitions: spine.control.allowed_transitions,
|
|
636
|
+
required_transition: spine.control.required_transition,
|
|
625
637
|
},
|
|
626
638
|
});
|
|
627
|
-
const linkedWorkflow = db.prepare(
|
|
628
|
-
`SELECT id FROM workflow_runs
|
|
629
|
-
WHERE kind = 'change' AND change_id = ? AND current_step = 'compile-context'
|
|
630
|
-
AND status IN ('active', 'blocked')
|
|
631
|
-
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
632
|
-
).get(change.id);
|
|
633
|
-
if (linkedWorkflow) {
|
|
634
|
-
if (spine.readiness.status === 'ready') {
|
|
635
|
-
workflows.recordWorkflowStep(db, {
|
|
636
|
-
id: linkedWorkflow.id, step_id: 'compile-context', status: 'completed',
|
|
637
|
-
evidence: [{
|
|
638
|
-
kind: 'context', ref: relative,
|
|
639
|
-
summary: `Role ${spine.role} context is ready for ${input.task_id || 'change planning'}.`,
|
|
640
|
-
}],
|
|
641
|
-
outputs: [{ path: relative, kind: 'context-manifest' }],
|
|
642
|
-
}, { rootDir });
|
|
643
|
-
} else {
|
|
644
|
-
workflows.recordWorkflowStep(db, {
|
|
645
|
-
id: linkedWorkflow.id, step_id: 'compile-context', status: 'blocked',
|
|
646
|
-
blockers: spine.readiness.blockers,
|
|
647
|
-
}, { rootDir });
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
639
|
});
|
|
651
|
-
return {
|
|
640
|
+
return {
|
|
641
|
+
manifest,
|
|
642
|
+
recommendation,
|
|
643
|
+
context_manifest_path: manifestPath,
|
|
644
|
+
manifest_hash: hash,
|
|
645
|
+
};
|
|
652
646
|
}
|
|
653
647
|
|
|
654
648
|
function createChange(db, input, { rootDir = process.cwd() } = {}) {
|
|
@@ -685,11 +679,46 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
685
679
|
'VALIDATION_ERROR', 'baseline_bypass is not valid while the project baseline is healthy',
|
|
686
680
|
);
|
|
687
681
|
}
|
|
682
|
+
const alignmentGate = decisions.decisionGate(
|
|
683
|
+
db, { baseline_id: baselineHealth.baseline?.id || null }, { rootDir },
|
|
684
|
+
);
|
|
685
|
+
if (!alignmentGate.ready) {
|
|
686
|
+
throw new ChangeWorkflowError(
|
|
687
|
+
'CHANGE_ALIGNMENT_REQUIRED',
|
|
688
|
+
`resolve and confirm the active baseline alignment before creating a change: ${alignmentGate.blockers.join(', ')}`,
|
|
689
|
+
{ blockers: alignmentGate.blockers, thread_id: alignmentGate.thread?.id || null },
|
|
690
|
+
);
|
|
691
|
+
}
|
|
688
692
|
const docsImpact = normalizeDocsImpact(input.docs_impact);
|
|
689
693
|
const providers = normalizeProviderRefs(input.provider_refs);
|
|
690
694
|
const contract = normalizeChangeContract(input.contract);
|
|
691
695
|
const classification = normalizeClassification(input.classification, input.kind);
|
|
692
696
|
const researchDisposition = normalizeResearchDisposition(input.research_disposition, input.kind);
|
|
697
|
+
let alignmentThread = null;
|
|
698
|
+
if (input.alignment_thread_id !== undefined) {
|
|
699
|
+
try {
|
|
700
|
+
alignmentThread = decisions.assertConfirmedDecisionCheckpoint(
|
|
701
|
+
db, input.alignment_thread_id, { rootDir, requireArtifact: true },
|
|
702
|
+
);
|
|
703
|
+
} catch (error) {
|
|
704
|
+
throw new ChangeWorkflowError(
|
|
705
|
+
'CHANGE_ALIGNMENT_REQUIRED',
|
|
706
|
+
`alignment_thread_id must name a current artifact-bound checkpoint: ${error.message}`,
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
if (alignmentThread.change_id) {
|
|
710
|
+
throw new ChangeWorkflowError(
|
|
711
|
+
'CHANGE_ALIGNMENT_REQUIRED', `alignment thread already belongs to ${alignmentThread.change_id}`,
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
if (!baselineHealth.baseline?.id || alignmentThread.baseline_id !== baselineHealth.baseline.id
|
|
715
|
+
|| alignmentThread.workflow_run_id) {
|
|
716
|
+
throw new ChangeWorkflowError(
|
|
717
|
+
'CHANGE_ALIGNMENT_REQUIRED',
|
|
718
|
+
'change alignment must be bound only to the current baseline before change creation',
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
693
722
|
const artifactRoot = path.join('.ultra', 'changes', 'active', input.id);
|
|
694
723
|
const artifactDir = path.resolve(rootDir, artifactRoot);
|
|
695
724
|
if (fs.existsSync(artifactDir)) {
|
|
@@ -700,6 +729,7 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
700
729
|
id: input.id, title, kind: input.kind, status: 'active',
|
|
701
730
|
intent, docs_impact: docsImpact, provider_refs: providers, baseline_bypass: baselineBypass,
|
|
702
731
|
contract, classification, research_disposition: researchDisposition,
|
|
732
|
+
alignment_thread_id: alignmentThread?.id || null,
|
|
703
733
|
base_commit: input.base_commit || gitHead(rootDir), artifact_root: artifactRoot,
|
|
704
734
|
created_at: ts, updated_at: ts,
|
|
705
735
|
};
|
|
@@ -709,15 +739,22 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
709
739
|
`INSERT INTO changes
|
|
710
740
|
(id, title, kind, status, intent, docs_impact_json, provider_refs_json,
|
|
711
741
|
baseline_bypass_json, contract_json, classification_json,
|
|
712
|
-
research_disposition_json, base_commit, artifact_root,
|
|
713
|
-
|
|
742
|
+
research_disposition_json, alignment_thread_id, base_commit, artifact_root,
|
|
743
|
+
created_at, updated_at)
|
|
744
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
714
745
|
).run(
|
|
715
746
|
row.id, row.title, row.kind, row.status, row.intent, JSON.stringify(row.docs_impact),
|
|
716
747
|
JSON.stringify(row.provider_refs), row.baseline_bypass
|
|
717
748
|
? JSON.stringify(row.baseline_bypass) : null,
|
|
718
749
|
JSON.stringify(row.contract), JSON.stringify(row.classification),
|
|
719
|
-
JSON.stringify(row.research_disposition), row.
|
|
750
|
+
JSON.stringify(row.research_disposition), row.alignment_thread_id,
|
|
751
|
+
row.base_commit, row.artifact_root, ts, ts,
|
|
720
752
|
);
|
|
753
|
+
if (row.alignment_thread_id) {
|
|
754
|
+
db.prepare(
|
|
755
|
+
'UPDATE decision_threads SET change_id = ?, updated_at = ? WHERE id = ?',
|
|
756
|
+
).run(row.id, ts, row.alignment_thread_id);
|
|
757
|
+
}
|
|
721
758
|
fs.mkdirSync(artifactDir, { recursive: true });
|
|
722
759
|
const intentPath = path.join(artifactDir, 'intent.md');
|
|
723
760
|
writeIntent(intentPath, row);
|
|
@@ -738,14 +775,16 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
738
775
|
}
|
|
739
776
|
ops.appendEventInTx(db, {
|
|
740
777
|
type: 'change_created', change_id: row.id,
|
|
741
|
-
payload: {
|
|
778
|
+
payload: {
|
|
779
|
+
kind: row.kind, artifact_root: row.artifact_root,
|
|
780
|
+
alignment_thread_id: row.alignment_thread_id,
|
|
781
|
+
},
|
|
742
782
|
});
|
|
743
783
|
let workflow = workflows.startWorkflow(db, {
|
|
744
784
|
kind: 'change', baseline_id: baselineHealth.baseline?.id || null,
|
|
745
785
|
change_id: row.id, subject: `Establish executable readiness for ${row.title}.`,
|
|
746
786
|
metadata: { change_kind: row.kind, base_commit: row.base_commit },
|
|
747
787
|
}, { rootDir });
|
|
748
|
-
const context = compileContext(db, { id: row.id }, { rootDir });
|
|
749
788
|
workflow = workflows.recordWorkflowStep(db, {
|
|
750
789
|
id: workflow.id, step_id: 'bind-baseline', status: 'completed',
|
|
751
790
|
evidence: [{
|
|
@@ -771,9 +810,10 @@ function createChange(db, input, { rootDir = process.cwd() } = {}) {
|
|
|
771
810
|
research_disposition: row.research_disposition,
|
|
772
811
|
}],
|
|
773
812
|
}, { rootDir });
|
|
813
|
+
workflow = workflows.completeWorkflow(db, { id: workflow.id }, { rootDir });
|
|
774
814
|
return {
|
|
775
815
|
change: readChange(db, row.id), intent_path: intentPath,
|
|
776
|
-
context_manifest_path:
|
|
816
|
+
context_manifest_path: null,
|
|
777
817
|
workflow,
|
|
778
818
|
};
|
|
779
819
|
});
|
|
@@ -866,6 +906,30 @@ function updateChange(db, id, patch = {}, { rootDir = process.cwd() } = {}) {
|
|
|
866
906
|
sets.push('updated_at = ?'); values.push(nowIso(), id);
|
|
867
907
|
db.prepare(`UPDATE changes SET ${sets.join(', ')} WHERE id = ?`).run(...values);
|
|
868
908
|
const updated = readChange(db, id);
|
|
909
|
+
const invalidatedTasks = [];
|
|
910
|
+
if (contextSpine.changeStateDigest(current) !== contextSpine.changeStateDigest(updated)) {
|
|
911
|
+
const tasks = db.prepare(
|
|
912
|
+
'SELECT id, stale FROM tasks WHERE change_id = ? ORDER BY created_at ASC, id ASC',
|
|
913
|
+
).all(id);
|
|
914
|
+
const markStale = db.prepare(
|
|
915
|
+
'UPDATE tasks SET stale = 1, updated_at = ? WHERE id = ?',
|
|
916
|
+
);
|
|
917
|
+
const invalidatedAt = nowIso();
|
|
918
|
+
for (const task of tasks) {
|
|
919
|
+
if (task.stale) continue;
|
|
920
|
+
markStale.run(invalidatedAt, task.id);
|
|
921
|
+
invalidatedTasks.push(task.id);
|
|
922
|
+
ops.appendEventInTx(db, {
|
|
923
|
+
type: 'task_stale_marked',
|
|
924
|
+
task_id: task.id,
|
|
925
|
+
change_id: id,
|
|
926
|
+
payload: {
|
|
927
|
+
reason: 'change_semantic_authority_updated',
|
|
928
|
+
fields: Object.keys(patch).filter((field) => SEMANTIC_AUTHORITY_FIELDS.has(field)),
|
|
929
|
+
},
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
869
933
|
if (syncIntent) {
|
|
870
934
|
fs.mkdirSync(path.dirname(intentPath), { recursive: true });
|
|
871
935
|
writeIntent(intentPath, updated);
|
|
@@ -876,7 +940,11 @@ function updateChange(db, id, patch = {}, { rootDir = process.cwd() } = {}) {
|
|
|
876
940
|
contentHash: crypto.createHash('sha256').update(fs.readFileSync(intentPath)).digest('hex'),
|
|
877
941
|
});
|
|
878
942
|
}
|
|
879
|
-
ops.appendEventInTx(db, {
|
|
943
|
+
ops.appendEventInTx(db, {
|
|
944
|
+
type: 'change_updated',
|
|
945
|
+
change_id: id,
|
|
946
|
+
payload: { fields: Object.keys(patch), invalidated_tasks: invalidatedTasks },
|
|
947
|
+
});
|
|
880
948
|
return updated;
|
|
881
949
|
});
|
|
882
950
|
} catch (error) {
|