ultra-builder-pro-cli 0.17.1 → 0.19.1
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 +77 -6
- package/README.md +380 -261
- package/adapters/_shared/codex-assets.cjs +24 -16
- package/adapters/_shared/file-ops.cjs +40 -3
- package/adapters/_shared/provenance.cjs +90 -6
- package/adapters/_shared/runtime-assets.cjs +0 -11
- package/adapters/claude.js +3 -1
- package/adapters/kimi.js +4 -30
- package/adapters/opencode.js +50 -14
- package/docs/AGENT-CONTEXT.md +11 -14
- package/docs/ARCHITECTURE.md +23 -15
- package/docs/DECISIONS.md +75 -0
- package/docs/LEGACY-CLI-CROSSWALK.md +3 -3
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +54 -0
- package/docs/ROADMAP.md +13 -13
- package/docs/RUNTIME-COMPAT-MATRIX.md +24 -21
- package/docs/STATE-DB-ACCESS-POLICY.md +2 -2
- package/docs/WORKFLOW-LIFECYCLE.md +12 -10
- package/hooks/active_task_context.py +1 -1
- package/hooks/context_spine.py +1 -1
- package/hooks/health_check.py +19 -0
- package/hooks/pre_stop_check.py +1 -1
- package/hooks/workflow_checkpoint.py +1 -1
- package/hooks/workflow_context.py +1 -1
- package/hooks/workflow_resume.py +1 -1
- package/mcp-server/lib/workflow-state.cjs +67 -54
- package/mcp-server/server.cjs +19 -7
- package/orchestrator/dispatch-rules.cjs +1 -1
- package/package.json +4 -5
- package/skills/ultra-change/SKILL.md +4 -3
- package/skills/ultra-init/SKILL.md +2 -1
- package/skills/ultra-research/SKILL.md +7 -4
- package/spec/cli-protocol.md +4 -4
- package/spec/mcp-tools.yaml +6 -6
- package/spec/schemas/context-file.v4.5.schema.json +1 -1
- package/spec/schemas/mcp-tools.schema.json +3 -3
- package/spec/schemas/state-db.sql +3 -3
- package/spec/schemas/tasks.v4.5.schema.json +1 -1
- package/adapters/_shared/handbook.cjs +0 -156
- package/bin/handbook.js +0 -54
- package/docs/PLAN.zh-CN.md +0 -1696
- package/docs/USER-HANDBOOK-CONTRACT.md +0 -81
|
@@ -110,15 +110,9 @@ const RESEARCH_DISPOSITIONS = new Set([
|
|
|
110
110
|
'execute', 'verify_existing', 'reuse', 'not_applicable', 'deferred',
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
full
|
|
115
|
-
|
|
116
|
-
product: WORKFLOW_DEFINITIONS.research
|
|
117
|
-
.filter((item) => Number.parseInt(item.id, 10) < 30 || item.id === '99-synthesis')
|
|
118
|
-
.map((item) => item.id),
|
|
119
|
-
feature: ['10-user-personas', '11-user-scenarios', '20-user-stories', '21-features-scope', '22-success-metrics', '99-synthesis'],
|
|
120
|
-
architecture: ['30-architecture-context', '31-solution-strategy', '32-building-blocks', '40-deployment', '41-quality-risks', '99-synthesis'],
|
|
121
|
-
});
|
|
113
|
+
const RESEARCH_MODE_NAMES = new Set([
|
|
114
|
+
'full', 'adoption', 'product', 'feature', 'architecture', 'custom',
|
|
115
|
+
]);
|
|
122
116
|
|
|
123
117
|
const RESEARCH_SEMANTIC_CONTRACTS = Object.freeze({
|
|
124
118
|
'00-problem-validation': ['problem', ['actor', 'current_workaround', 'consequence', 'evidence_status']],
|
|
@@ -459,9 +453,20 @@ function decorateRun(db, row, rootDir) {
|
|
|
459
453
|
return value;
|
|
460
454
|
}
|
|
461
455
|
|
|
456
|
+
function definitionForRun(run) {
|
|
457
|
+
const definition = WORKFLOW_DEFINITIONS[run.kind] || [];
|
|
458
|
+
if (run.kind !== 'research') return definition;
|
|
459
|
+
const metadata = parseJson(run.metadata_json, 'workflow_runs.metadata_json', {});
|
|
460
|
+
if (!Array.isArray(metadata.coverage) || metadata.coverage.length === 0) {
|
|
461
|
+
return definition;
|
|
462
|
+
}
|
|
463
|
+
const covered = new Set(metadata.coverage.map((item) => item?.step_id).filter(Boolean));
|
|
464
|
+
return definition.filter((item) => covered.has(item.id));
|
|
465
|
+
}
|
|
466
|
+
|
|
462
467
|
function inspectRunHealth(run, steps, rootDir) {
|
|
463
468
|
const blockers = [...inspectOutputHealth(steps, rootDir).blockers];
|
|
464
|
-
const definition =
|
|
469
|
+
const definition = definitionForRun(run);
|
|
465
470
|
const expectedIds = new Set(definition.map((item) => item.id));
|
|
466
471
|
const actualIds = new Set(steps.map((item) => item.step_id));
|
|
467
472
|
if (run.definition_version !== DEFINITION_VERSION) {
|
|
@@ -576,6 +581,9 @@ function listWorkflows(db, filter = {}, { rootDir = process.cwd() } = {}) {
|
|
|
576
581
|
function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {}) {
|
|
577
582
|
const knownSteps = WORKFLOW_DEFINITIONS.research.map((item) => item.id);
|
|
578
583
|
const known = new Set(knownSteps);
|
|
584
|
+
if (!RESEARCH_MODE_NAMES.has(mode)) {
|
|
585
|
+
throw new WorkflowStateError('VALIDATION_ERROR', `unsupported research mode: ${mode}`);
|
|
586
|
+
}
|
|
579
587
|
if (coverage !== undefined) {
|
|
580
588
|
if (!Array.isArray(coverage) || coverage.length === 0) {
|
|
581
589
|
throw new WorkflowStateError(
|
|
@@ -635,55 +643,66 @@ function normalizeResearchCoverage(mode, coverage, selectedSteps, metadata = {})
|
|
|
635
643
|
...(consequence ? { consequence } : {}),
|
|
636
644
|
};
|
|
637
645
|
});
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
646
|
+
const selectionReason = String(metadata.selection_reason || '').trim();
|
|
647
|
+
if (selectionReason.length < 3) {
|
|
648
|
+
throw new WorkflowStateError(
|
|
649
|
+
'WORKFLOW_SELECTION_REASON_REQUIRED',
|
|
650
|
+
`${mode} research requires the model's evidence-based coverage rationale`,
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const synthesis = normalized.find((item) => item.step_id === '99-synthesis');
|
|
654
|
+
if (!synthesis || !['execute', 'verify_existing', 'reuse'].includes(synthesis.disposition)) {
|
|
655
|
+
throw new WorkflowStateError(
|
|
656
|
+
'WORKFLOW_RESEARCH_SYNTHESIS_REQUIRED',
|
|
657
|
+
`${mode} research requires an executable or reusable 99-synthesis disposition`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const selectedSemanticArea = normalized.some((item) => (
|
|
661
|
+
item.step_id !== '99-synthesis'
|
|
662
|
+
&& (
|
|
663
|
+
['execute', 'verify_existing', 'reuse'].includes(item.disposition)
|
|
664
|
+
|| (item.disposition === 'deferred' && !item.accepted_by)
|
|
665
|
+
)
|
|
666
|
+
));
|
|
667
|
+
if (!selectedSemanticArea) {
|
|
668
|
+
throw new WorkflowStateError(
|
|
669
|
+
'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
|
|
670
|
+
`${mode} research requires at least one applicable semantic area besides synthesis`,
|
|
671
|
+
);
|
|
653
672
|
}
|
|
654
673
|
return normalized.sort(
|
|
655
674
|
(left, right) => knownSteps.indexOf(left.step_id) - knownSteps.indexOf(right.step_id),
|
|
656
675
|
);
|
|
657
676
|
}
|
|
658
677
|
|
|
659
|
-
if (['full', 'adoption'].includes(mode)) {
|
|
660
|
-
throw new WorkflowStateError(
|
|
661
|
-
'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
|
|
662
|
-
`${mode} research requires model-selected coverage dispositions`,
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
678
|
if (mode === 'custom') {
|
|
666
679
|
if (!Array.isArray(selectedSteps) || selectedSteps.length === 0) {
|
|
667
|
-
throw new WorkflowStateError(
|
|
680
|
+
throw new WorkflowStateError(
|
|
681
|
+
'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
|
|
682
|
+
'custom research requires model-selected coverage or selected_steps',
|
|
683
|
+
);
|
|
668
684
|
}
|
|
669
|
-
const known = new Set(WORKFLOW_DEFINITIONS.research.map((item) => item.id));
|
|
670
685
|
const invalid = selectedSteps.find((item) => !known.has(item));
|
|
671
686
|
if (invalid) throw new WorkflowStateError('VALIDATION_ERROR', `unknown research step: ${invalid}`);
|
|
672
|
-
|
|
687
|
+
const selectionReason = String(metadata.selection_reason || '').trim();
|
|
688
|
+
if (selectionReason.length < 3) {
|
|
689
|
+
throw new WorkflowStateError(
|
|
690
|
+
'WORKFLOW_SELECTION_REASON_REQUIRED',
|
|
691
|
+
'custom research requires the model\'s evidence-based coverage rationale',
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
const explicitCoverage = [...new Set([...selectedSteps, '99-synthesis'])].map((stepId) => ({
|
|
673
695
|
step_id: stepId,
|
|
674
696
|
disposition: 'execute',
|
|
675
|
-
rationale:
|
|
697
|
+
rationale: selectionReason,
|
|
676
698
|
evidence_refs: [],
|
|
677
699
|
}));
|
|
700
|
+
return normalizeResearchCoverage(mode, explicitCoverage, undefined, metadata);
|
|
678
701
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
disposition: 'execute',
|
|
684
|
-
rationale: String(metadata.selection_reason || `${mode} research profile selected by the host model.`).trim(),
|
|
685
|
-
evidence_refs: [],
|
|
686
|
-
}));
|
|
702
|
+
throw new WorkflowStateError(
|
|
703
|
+
'WORKFLOW_RESEARCH_COVERAGE_REQUIRED',
|
|
704
|
+
`${mode} research requires model-selected coverage dispositions`,
|
|
705
|
+
);
|
|
687
706
|
}
|
|
688
707
|
|
|
689
708
|
function assertReference(db, table, id, field) {
|
|
@@ -722,15 +741,7 @@ function assertWorkflowAuthority(db, input, mode) {
|
|
|
722
741
|
'WORKFLOW_AUTHORITY_REQUIRED', 'research workflow requires baseline_id or change_id',
|
|
723
742
|
);
|
|
724
743
|
}
|
|
725
|
-
if (['product', 'feature', 'architecture', 'custom'].includes(mode))
|
|
726
|
-
requireField('change_id');
|
|
727
|
-
if (!String(input.metadata?.selection_reason || '').trim()) {
|
|
728
|
-
throw new WorkflowStateError(
|
|
729
|
-
'WORKFLOW_SELECTION_REASON_REQUIRED',
|
|
730
|
-
`${mode} research requires an explicit owner-selected scope reason`,
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
744
|
+
if (['product', 'feature', 'architecture', 'custom'].includes(mode)) requireField('change_id');
|
|
734
745
|
if (input.baseline_id && !input.change_id) {
|
|
735
746
|
const baseline = db.prepare('SELECT mode FROM baselines WHERE id = ?').get(input.baseline_id);
|
|
736
747
|
const expectedMode = baseline?.mode === 'brownfield' ? 'adoption' : 'full';
|
|
@@ -887,7 +898,7 @@ function insertWorkflowInTx(db, input = {}, { rootDir = process.cwd() } = {}) {
|
|
|
887
898
|
throw new WorkflowStateError('VALIDATION_ERROR', `unsupported workflow kind: ${input.kind}`);
|
|
888
899
|
}
|
|
889
900
|
input = bindChangeWorkflowAuthority(db, input);
|
|
890
|
-
const
|
|
901
|
+
const catalogDefinition = WORKFLOW_DEFINITIONS[input.kind];
|
|
891
902
|
const mode = input.kind === 'research' ? (input.mode || 'full') : null;
|
|
892
903
|
assertWorkflowAuthority(db, input, mode);
|
|
893
904
|
const id = input.id || `wf-${input.kind}-${crypto.randomUUID().slice(0, 12)}`;
|
|
@@ -957,6 +968,9 @@ function insertWorkflowInTx(db, input = {}, { rootDir = process.cwd() } = {}) {
|
|
|
957
968
|
? normalizeResearchCoverage(mode, input.coverage, input.selected_steps, input.metadata)
|
|
958
969
|
: null;
|
|
959
970
|
const coverageByStep = new Map((researchCoverage || []).map((item) => [item.step_id, item]));
|
|
971
|
+
const definition = input.kind === 'research'
|
|
972
|
+
? catalogDefinition.filter((item) => coverageByStep.has(item.id))
|
|
973
|
+
: catalogDefinition;
|
|
960
974
|
const selected = new Set(input.kind === 'research'
|
|
961
975
|
? researchCoverage
|
|
962
976
|
.filter((item) => ['execute', 'verify_existing', 'reuse'].includes(item.disposition)
|
|
@@ -2380,7 +2394,6 @@ function recoverUntrackedChangeWorkflows(db, { rootDir = process.cwd() } = {}) {
|
|
|
2380
2394
|
module.exports = {
|
|
2381
2395
|
DEFINITION_VERSION,
|
|
2382
2396
|
WORKFLOW_DEFINITIONS,
|
|
2383
|
-
RESEARCH_MODES,
|
|
2384
2397
|
RUN_KINDS,
|
|
2385
2398
|
RUN_STATUSES,
|
|
2386
2399
|
WorkflowStateError,
|
package/mcp-server/server.cjs
CHANGED
|
@@ -818,16 +818,28 @@ function appendHookLifecycleEvent({ rootDir, action, hookInput = {} } = {}) {
|
|
|
818
818
|
const db = openStateDb(dbPath);
|
|
819
819
|
try {
|
|
820
820
|
ensureSchemaVersion(db);
|
|
821
|
-
const
|
|
822
|
-
`SELECT
|
|
821
|
+
const workflow = db.prepare(
|
|
822
|
+
`SELECT change_id, task_id FROM workflow_runs
|
|
823
|
+
WHERE status IN ('active', 'blocked', 'ready') AND change_id IS NOT NULL
|
|
823
824
|
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
824
825
|
).get();
|
|
826
|
+
if (!workflow) return { recorded: false, reason: 'NO_ACTIVE_WORKFLOW' };
|
|
827
|
+
const change = db.prepare(
|
|
828
|
+
`SELECT id FROM changes
|
|
829
|
+
WHERE id = ? AND status IN ('active', 'blocked', 'ready')`,
|
|
830
|
+
).get(workflow.change_id);
|
|
825
831
|
if (!change) return { recorded: false, reason: 'NO_ACTIVE_CHANGE' };
|
|
826
|
-
const task =
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
832
|
+
const task = workflow.task_id
|
|
833
|
+
? db.prepare(
|
|
834
|
+
`SELECT id FROM tasks
|
|
835
|
+
WHERE id = ? AND change_id = ?
|
|
836
|
+
AND status IN ('in_progress', 'pending', 'blocked')`,
|
|
837
|
+
).get(workflow.task_id, change.id)
|
|
838
|
+
: db.prepare(
|
|
839
|
+
`SELECT id FROM tasks WHERE change_id = ? AND status IN ('in_progress', 'pending', 'blocked')
|
|
840
|
+
ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1 ELSE 2 END,
|
|
841
|
+
updated_at DESC, rowid DESC LIMIT 1`,
|
|
842
|
+
).get(change.id);
|
|
831
843
|
const payload = {
|
|
832
844
|
agent_id: hookMetadata(hookInput.agent_id),
|
|
833
845
|
agent_type: hookMetadata(hookInput.agent_type),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Declarative dispatch rules for deterministic priority matching.
|
|
4
4
|
//
|
|
5
5
|
// evaluate(ctx, rules) walks a priority-sorted rule array and returns the
|
|
6
6
|
// first matching { rule_id, action, runtime }. Actions:
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-builder-pro-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.19.1",
|
|
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",
|
|
7
7
|
"ubp": "bin/install.js",
|
|
8
|
-
"ubp-handbook": "bin/handbook.js",
|
|
9
8
|
"ubp-orchestrator": "bin/orchestrator.js",
|
|
10
9
|
"ultra-tools": "ultra-tools/cli.cjs"
|
|
11
10
|
},
|
|
@@ -54,9 +53,9 @@
|
|
|
54
53
|
"docs/AGENT-CONTEXT.md",
|
|
55
54
|
"docs/SKILL-AUTHORING.md",
|
|
56
55
|
"docs/COMMIT-HASH-BACKFILL.md",
|
|
57
|
-
"docs/
|
|
56
|
+
"docs/DECISIONS.md",
|
|
58
57
|
"docs/STATE-DB-ACCESS-POLICY.md",
|
|
59
|
-
"docs/
|
|
58
|
+
"docs/PLUGIN-ISOLATION-CONTRACT.md",
|
|
60
59
|
"docs/RUNTIME-COMPAT-MATRIX.md",
|
|
61
60
|
"!**/__pycache__/**",
|
|
62
61
|
"!**/*.pyc",
|
|
@@ -60,9 +60,10 @@ meaning is coupled. Downstream task, context, test, or review evidence affected
|
|
|
60
60
|
change must become stale and be regenerated; never keep old evidence green by prompt
|
|
61
61
|
assertion.
|
|
62
62
|
|
|
63
|
-
If research is required,
|
|
64
|
-
contract is sufficiently evidenced,
|
|
65
|
-
|
|
63
|
+
If research is required, recommend `ultra-research` with the recorded disposition. If
|
|
64
|
+
the contract is sufficiently evidenced, recommend `ultra-plan`. Return and wait for an
|
|
65
|
+
explicit user invocation; do not start either workflow. `change.context` is compiled
|
|
66
|
+
only for an actual plan, implementation, test, or review consumer.
|
|
66
67
|
|
|
67
68
|
Stable implementation discoveries use the approval-gated specification-learning
|
|
68
69
|
transitions. External memory and graph providers retain their own content; Ultra stores
|
|
@@ -75,7 +75,8 @@ Read the returned `allowed_transitions` and `required_transition`:
|
|
|
75
75
|
- a required transition represents a hard recovery invariant and must be followed;
|
|
76
76
|
- otherwise the host model recommends among the allowed capabilities using the user's
|
|
77
77
|
goal and current evidence;
|
|
78
|
-
-
|
|
78
|
+
- never invoke another public Ultra workflow automatically; recommend the best allowed
|
|
79
|
+
capability and wait for an explicit user invocation.
|
|
79
80
|
|
|
80
81
|
Report classification, scope, schema and backup result, Git state, init workflow id,
|
|
81
82
|
baseline state, and allowed transitions. Do not claim that a baseline is ready until a
|
|
@@ -7,8 +7,8 @@ description: Build or refresh an evidence-backed product and architecture baseli
|
|
|
7
7
|
|
|
8
8
|
The host model owns investigation, synthesis, and coverage judgment. Ultra MCP owns the
|
|
9
9
|
run, evidence references, semantic records, artifact digests, freshness, and
|
|
10
|
-
convergence. The
|
|
11
|
-
or a fixed sequence of user conversations.
|
|
10
|
+
convergence. The reference areas are an optional coverage catalog, not a mandatory
|
|
11
|
+
questionnaire or a fixed sequence of user conversations.
|
|
12
12
|
|
|
13
13
|
Read `../ultra-think/references/decision-dialogue.md` before asking a material question.
|
|
14
14
|
|
|
@@ -21,16 +21,19 @@ Read `../ultra-think/references/decision-dialogue.md` before asking a material q
|
|
|
21
21
|
- `adoption` for a brownfield baseline;
|
|
22
22
|
- a bounded mode only for a recorded active-change research disposition.
|
|
23
23
|
4. Before `workflow.start`, inspect current code, docs, tests, runtime, and prior
|
|
24
|
-
artifacts.
|
|
24
|
+
artifacts. Select the smallest sufficient set of applicable catalog areas. Include
|
|
25
|
+
only areas that need work or whose exclusion must remain auditable:
|
|
25
26
|
- `execute`: produce fresh evidence;
|
|
26
27
|
- `verify_existing`: verify a current artifact against its source;
|
|
27
28
|
- `reuse`: reuse evidence that is still current;
|
|
28
29
|
- `not_applicable`: exclude with an evidence reference and rationale;
|
|
29
30
|
- `deferred`: record the consequence and owner acceptance when deferral changes the
|
|
30
31
|
accepted scope or leaves material risk.
|
|
32
|
+
5. Pass the evidence-based coverage rationale as `metadata.selection_reason`. Omitted
|
|
33
|
+
catalog areas create no DB step and need no ceremonial disposition.
|
|
31
34
|
|
|
32
35
|
`99-synthesis` must execute, verify, or reuse. A missing disposition is a coverage
|
|
33
|
-
error; a
|
|
36
|
+
error only for an included area; a recorded exclusion with evidence is not incomplete.
|
|
34
37
|
|
|
35
38
|
## Coverage catalog
|
|
36
39
|
|
package/spec/cli-protocol.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# `ultra-tools` CLI protocol
|
|
2
2
|
|
|
3
|
-
`ultra-tools` is the maintenance CLI around the live MCP runtime
|
|
4
|
-
|
|
3
|
+
`ultra-tools` is the maintenance CLI around the live MCP runtime. The mapping
|
|
4
|
+
registry below reserves stable command names for MCP tools;
|
|
5
5
|
it does not mean every mapping is executable. Task lifecycle workflows must use
|
|
6
6
|
the live MCP server and fail closed when it is unavailable.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Authority boundary: [`docs/DECISIONS.md`](../docs/DECISIONS.md).
|
|
9
9
|
|
|
10
10
|
## 1. Invocation
|
|
11
11
|
|
|
@@ -159,7 +159,7 @@ The full access policy lives in
|
|
|
159
159
|
The CLI follows the `version` field of `spec/mcp-tools.yaml`. Adding a new
|
|
160
160
|
flag is a minor bump; renaming or removing a flag, or changing exit-code
|
|
161
161
|
semantics, is a major bump. Major bumps require an entry in
|
|
162
|
-
`docs/
|
|
162
|
+
[`docs/DECISIONS.md`](../docs/DECISIONS.md) and the changelog.
|
|
163
163
|
|
|
164
164
|
## 7. `ubp-orchestrator`
|
|
165
165
|
|
package/spec/mcp-tools.yaml
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Ultra Builder Pro — MCP tool contracts
|
|
2
2
|
# Single source for tool name, schema, errors, and CLI mapping.
|
|
3
3
|
# Validated by spec/schemas/mcp-tools.schema.json (run `npm run test:spec`).
|
|
4
|
-
#
|
|
4
|
+
# Current authority boundary: docs/DECISIONS.md.
|
|
5
5
|
|
|
6
6
|
version: '1.3'
|
|
7
7
|
|
|
8
8
|
info:
|
|
9
9
|
title: Ultra Builder Pro MCP tool contracts
|
|
10
10
|
phase: '1.2'
|
|
11
|
-
trace_to: '
|
|
11
|
+
trace_to: 'docs/DECISIONS.md and mcp-server/server.cjs'
|
|
12
12
|
notes: |
|
|
13
13
|
This published contract contains only tools registered by the MCP server.
|
|
14
14
|
Host-native review, impact, skill discovery, and human-interaction capabilities
|
|
@@ -397,7 +397,7 @@ tools:
|
|
|
397
397
|
- { code: IO_ERROR, description: 'filesystem write failed', retriable: true }
|
|
398
398
|
cli_subcommand: task init-project
|
|
399
399
|
emits_events: [project_initialized, project_resumed]
|
|
400
|
-
trace_to: '
|
|
400
|
+
trace_to: 'mcp-server/lib/init-project.cjs'
|
|
401
401
|
|
|
402
402
|
- name: task.expand
|
|
403
403
|
family: task
|
|
@@ -1833,7 +1833,7 @@ tools:
|
|
|
1833
1833
|
# ────────────────────────── workflow family ────────────────────────────
|
|
1834
1834
|
- name: workflow.start
|
|
1835
1835
|
family: workflow
|
|
1836
|
-
description: Start one durable Ultra workflow run. For
|
|
1836
|
+
description: Start one durable Ultra workflow run. For research, the host model supplies only evidence-relevant semantic coverage; MCP records and validates the selection without choosing it.
|
|
1837
1837
|
phase: '13'
|
|
1838
1838
|
writer_role: mcp
|
|
1839
1839
|
input_schema:
|
|
@@ -1883,11 +1883,11 @@ tools:
|
|
|
1883
1883
|
- { code: WORKFLOW_AUTHORITY_REQUIRED, description: 'the workflow is missing its required baseline, change, or task authority' }
|
|
1884
1884
|
- { code: WORKFLOW_CHANGE_DECISION_BLOCKING, description: 'the change contains unresolved material owner decisions' }
|
|
1885
1885
|
- { code: WORKFLOW_CHANGE_RESEARCH_INCOMPLETE, description: 'the change-bound research disposition is not current and complete' }
|
|
1886
|
-
- { code: WORKFLOW_RESEARCH_COVERAGE_REQUIRED, description: '
|
|
1886
|
+
- { code: WORKFLOW_RESEARCH_COVERAGE_REQUIRED, description: 'research requires model-selected coverage with at least one applicable semantic area beyond synthesis' }
|
|
1887
1887
|
- { code: WORKFLOW_RESEARCH_COVERAGE_EVIDENCE_REQUIRED, description: 'reuse, verification, and not-applicable dispositions require evidence references' }
|
|
1888
1888
|
- { code: WORKFLOW_RESEARCH_SYNTHESIS_REQUIRED, description: 'research synthesis must execute, verify, or reuse durable evidence' }
|
|
1889
1889
|
- { code: WORKFLOW_RESEARCH_MODE_MISMATCH, description: 'the selected research mode does not match its authority or baseline mode' }
|
|
1890
|
-
- { code: WORKFLOW_SELECTION_REASON_REQUIRED, description: '
|
|
1890
|
+
- { code: WORKFLOW_SELECTION_REASON_REQUIRED, description: 'research requires the model evidence rationale for its selected coverage' }
|
|
1891
1891
|
- { code: TASK_EXECUTION_CONTRACT_INCOMPLETE, description: 'dev cannot start until the linked task contract is executable' }
|
|
1892
1892
|
- { code: TASK_CHANGE_OWNERSHIP_MISMATCH, description: 'linked task and change authority disagree' }
|
|
1893
1893
|
- { code: TASK_DEPENDENCIES_INCOMPLETE, description: 'dev cannot start while a task dependency remains incomplete' }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://ultra-builder-pro.dev/spec/context-file.v4.5.schema.json",
|
|
4
4
|
"title": "context-file projection (v4.5)",
|
|
5
|
-
"description": "Schema for the parsed representation of .ultra/tasks/contexts/task-*.md. The header is regenerated by the projector from state.db
|
|
5
|
+
"description": "Schema for the parsed representation of .ultra/tasks/contexts/task-*.md. The header is regenerated by the projector from state.db. Body sections are author-owned; status MUST NOT appear in body. Authority boundary: docs/DECISIONS.md.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["header", "body"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://ultra-builder-pro.dev/spec/mcp-tools.schema.json",
|
|
4
4
|
"title": "MCP tools manifest",
|
|
5
|
-
"description": "Meta-schema describing the shape of spec/mcp-tools.yaml. The manifest
|
|
5
|
+
"description": "Meta-schema describing the shape of spec/mcp-tools.yaml. The manifest is the single source for published MCP tool contracts.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["version", "info", "tools"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"name": {
|
|
57
57
|
"type": "string",
|
|
58
58
|
"pattern": "^[a-z][a-z_]*\\.[a-z][a-z_]*$",
|
|
59
|
-
"description": "family.verb naming convention
|
|
59
|
+
"description": "family.verb naming convention"
|
|
60
60
|
},
|
|
61
61
|
"family": {
|
|
62
62
|
"type": "string",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
},
|
|
104
104
|
"trace_to": {
|
|
105
105
|
"type": "string",
|
|
106
|
-
"description": "
|
|
106
|
+
"description": "current source or decision reference"
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
-- Ultra Builder Pro — authoritative state schema
|
|
2
|
-
--
|
|
2
|
+
-- This file builds .ultra/state.db, the source of truth for all
|
|
3
3
|
-- baseline / change / task / session / event / telemetry data. tasks.json and context md status
|
|
4
|
-
--
|
|
4
|
+
-- headers are projections.
|
|
5
5
|
--
|
|
6
|
-
--
|
|
6
|
+
-- Authority boundary: docs/DECISIONS.md.
|
|
7
7
|
--
|
|
8
8
|
-- PRAGMAs are applied by mcp-server/lib/state-db.ts on connection open;
|
|
9
9
|
-- they cannot be persisted in CREATE statements but are documented here.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://ultra-builder-pro.dev/spec/tasks.v4.5.schema.json",
|
|
4
4
|
"title": "tasks.json projection (v4.5)",
|
|
5
|
-
"description": "Read-only projection of the tasks table. Generated by the projector
|
|
5
|
+
"description": "Read-only projection of the tasks table. Generated by the projector; manual edits are overwritten. Status and dependencies are derived from state.db. Authority boundary: docs/DECISIONS.md.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schema_version", "generated_at", "tasks"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const os = require('node:os');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
|
|
7
|
-
const { writeAtomic } = require('./file-ops.cjs');
|
|
8
|
-
|
|
9
|
-
const BEGIN_MARKER = '<!-- ultra-builder-pro:handbook:start -->';
|
|
10
|
-
const END_MARKER = '<!-- ultra-builder-pro:handbook:end -->';
|
|
11
|
-
const LEGACY_HEADING = '## Ultra Builder Pro Runtime Contract';
|
|
12
|
-
|
|
13
|
-
const HOSTS = Object.freeze({
|
|
14
|
-
claude: Object.freeze({
|
|
15
|
-
name: 'Claude Code',
|
|
16
|
-
handbook: ['.claude', 'CLAUDE.md'],
|
|
17
|
-
invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-change`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status`, `/ultra-think`, and `/ultra-doctor`',
|
|
18
|
-
collaboration: '`/codex-collab` is an explicitly requested read-only advisor',
|
|
19
|
-
coordination: 'Claude Code task UI is host coordination; it does not replace Ultra task state',
|
|
20
|
-
}),
|
|
21
|
-
codex: Object.freeze({
|
|
22
|
-
name: 'Codex',
|
|
23
|
-
handbook: ['.codex', 'AGENTS.md'],
|
|
24
|
-
invocation: '`$ultra-builder-pro:ultra-init`, `$ultra-builder-pro:ultra-research`, `$ultra-builder-pro:ultra-plan`, `$ultra-builder-pro:ultra-change`, `$ultra-builder-pro:ultra-dev`, `$ultra-builder-pro:ultra-test`, `$ultra-builder-pro:ultra-review`, and `$ultra-builder-pro:ultra-deliver`; diagnostics use `$ultra-builder-pro:ultra-status`, `$ultra-builder-pro:ultra-think`, and `$ultra-builder-pro:ultra-doctor`',
|
|
25
|
-
collaboration: '`$ultra-builder-pro:cc-collab` is an explicitly requested read-only advisor',
|
|
26
|
-
coordination: 'The Codex plan is turn-facing coordination; it does not replace Ultra task state',
|
|
27
|
-
}),
|
|
28
|
-
opencode: Object.freeze({
|
|
29
|
-
name: 'OpenCode',
|
|
30
|
-
handbook: ['.config', 'opencode', 'AGENTS.md'],
|
|
31
|
-
invocation: '`/ultra-init`, `/ultra-research`, `/ultra-plan`, `/ultra-change`, `/ultra-dev`, `/ultra-test`, `/ultra-review`, and `/ultra-deliver`; diagnostics use `/ultra-status`, `/ultra-think`, and `/ultra-doctor`',
|
|
32
|
-
collaboration: '`/cc-collab` and `/codex-collab` are explicitly requested read-only advisors',
|
|
33
|
-
coordination: 'OpenCode session coordination does not replace Ultra task state',
|
|
34
|
-
}),
|
|
35
|
-
kimi: Object.freeze({
|
|
36
|
-
name: 'Kimi Code',
|
|
37
|
-
handbook: ['.kimi-code', 'AGENTS.md'],
|
|
38
|
-
invocation: '`/ultra-builder-pro:ultra-init`, `/ultra-builder-pro:ultra-research`, `/ultra-builder-pro:ultra-plan`, `/ultra-builder-pro:ultra-change`, `/ultra-builder-pro:ultra-dev`, `/ultra-builder-pro:ultra-test`, `/ultra-builder-pro:ultra-review`, and `/ultra-builder-pro:ultra-deliver`; diagnostics use `/ultra-builder-pro:ultra-status`, `/ultra-builder-pro:ultra-think`, and `/ultra-builder-pro:ultra-doctor`',
|
|
39
|
-
collaboration: '`/skill:cc-collab` and `/skill:codex-collab` are explicitly requested read-only advisors',
|
|
40
|
-
coordination: 'Kimi `TodoList` and `Agent` / `AgentSwarm` are host coordination; they do not replace Ultra task state',
|
|
41
|
-
}),
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
function host(runtime) {
|
|
45
|
-
const value = HOSTS[runtime];
|
|
46
|
-
if (!value) throw new Error(`unsupported handbook runtime: ${runtime}`);
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function renderHandbook(runtime) {
|
|
51
|
-
const value = host(runtime);
|
|
52
|
-
return [
|
|
53
|
-
BEGIN_MARKER,
|
|
54
|
-
'## Ultra Builder Pro Runtime Contract',
|
|
55
|
-
'',
|
|
56
|
-
`Ultra Builder Pro is installed as a native ${value.name} plugin. Keep general engineering`,
|
|
57
|
-
'policy in this handbook and detailed Ultra procedures in the plugin skills.',
|
|
58
|
-
'',
|
|
59
|
-
`- Workflow entry points: ${value.invocation}.`,
|
|
60
|
-
`- Collaboration boundary: ${value.collaboration}. The current host remains primary and owns final verification.`,
|
|
61
|
-
`- Coordination boundary: ${value.coordination}.`,
|
|
62
|
-
'- Authority: `.ultra/state.db` is the only durable Ultra authority for baselines, changes, decision threads, tasks, sessions, events, incidents, projection state, telemetry, and review evidence. Generated JSON and Markdown are projections or workflow artifacts.',
|
|
63
|
-
'- Baseline boundary: `task.init_project` classifies greenfield or brownfield work and records repository scope. Projection-only prior state uses the exact supported backup-first import returned by the authority check; migrated compatibility rows require explicit brownfield re-adoption. `baseline.record` owns evidence, verification, repository snapshot, and the gap ledger; `baseline.converge` requires explicit owner approval. New ordinary changes require a healthy ready baseline. Existing active work may continue with warnings, but baseline readiness blocks change convergence; normal drift reconciliation is health-checked atomically at archive. Only an explicitly approved incident break-glass may start without baseline readiness, and its archive creates a blocking reconciliation gap.',
|
|
64
|
-
'- Decision boundary: project-bound owner choices use one resumable DB-backed decision thread. The host resolves observable facts autonomously, presents only the earliest unresolved decision with evidence and a recommendation, waits for the owner, and records the normalized decision rather than prompts or transcripts. A linked workflow cannot advance until blocking decisions are resolved and its owner-approved checkpoint is current.',
|
|
65
|
-
'- Context Spine boundary: `change.context` compiles role/gate readiness, required references, a fresh-context budget, public seam, verification command, Change/task authority digests, allowed transitions, and any unique hard-recovery transition. File, token, and context-share budgets are advisory attention signals, not refusal gates. The host owns semantic recommendations. Hooks inject only the DB-derived `change.breadcrumb`; missing required evidence or stale execution context must be recompiled.',
|
|
66
|
-
'- Specification learning boundary: stable implementation discoveries use approval-gated propose/approve-or-reject/apply transitions. Unresolved learning blocks convergence; review requires independent specification-fidelity and engineering-standards evidence.',
|
|
67
|
-
'- Memory and graph boundary: Ultra Builder Pro does not capture prompts, transcripts, observations, summaries, cross-session memory, or code-graph content. Separately installed providers own that data; Ultra may store only their metadata references in a change context manifest.',
|
|
68
|
-
'- Hook boundary: Ultra hooks observe workflow/change lifecycle only. Health/context may run when `.ultra/state.db` exists; advisory warnings never reject work, and an incomplete workflow never traps session stop. Direct projection protection remains authoritative; compact and subagent recovery stays active-workflow scoped. Generic command blocking and post-edit policy stay in user or repository governance.',
|
|
69
|
-
'- Installation boundary: `ubp --doctor` is the read-only authority for installed asset provenance, content hashes, and host entry-point wiring. Project `system.doctor` diagnoses state and performs only authorized backup-first schema, projection, session, and archive-journal recovery; it never approves a baseline.',
|
|
70
|
-
'- Agent boundary: the bundled review and debugging agents are bounded workers. They use the current checkout and parent-supplied context, do not own private persistent state, and never replace the primary agent.',
|
|
71
|
-
'- Package boundary: only the twelve Ultra workflows, four internal review-rule skills, host-specific collaboration companions, and the minimal host bootstrap belong to this plugin. General browser, deployment, discovery, and framework skills must be installed from their owners.',
|
|
72
|
-
END_MARKER,
|
|
73
|
-
].join('\n');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function markerCount(text, marker) {
|
|
77
|
-
return text.split(marker).length - 1;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function replaceManagedBlock(existing, block) {
|
|
81
|
-
const beginCount = markerCount(existing, BEGIN_MARKER);
|
|
82
|
-
const endCount = markerCount(existing, END_MARKER);
|
|
83
|
-
if (beginCount !== endCount || beginCount > 1) {
|
|
84
|
-
throw new Error('handbook contains malformed or duplicate Ultra managed markers');
|
|
85
|
-
}
|
|
86
|
-
if (beginCount === 0) return null;
|
|
87
|
-
|
|
88
|
-
const start = existing.indexOf(BEGIN_MARKER);
|
|
89
|
-
const end = existing.indexOf(END_MARKER, start) + END_MARKER.length;
|
|
90
|
-
return existing.slice(0, start) + block + existing.slice(end);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function replaceLegacyCodexSection(existing, block) {
|
|
94
|
-
const heading = new RegExp(`^${LEGACY_HEADING.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^\\n]*$`, 'm');
|
|
95
|
-
const match = heading.exec(existing);
|
|
96
|
-
if (!match) return null;
|
|
97
|
-
|
|
98
|
-
const start = match.index;
|
|
99
|
-
const remainder = existing.slice(start + match[0].length);
|
|
100
|
-
const nextHeading = /^##\s+/m.exec(remainder);
|
|
101
|
-
const end = nextHeading ? start + match[0].length + nextHeading.index : existing.length;
|
|
102
|
-
const before = existing.slice(0, start).trimEnd();
|
|
103
|
-
const after = existing.slice(end).trimStart();
|
|
104
|
-
return `${before}${before ? '\n\n' : ''}${block}${after ? `\n\n${after}` : '\n'}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function mergeHandbook(existing, runtime) {
|
|
108
|
-
host(runtime);
|
|
109
|
-
const current = typeof existing === 'string' ? existing : '';
|
|
110
|
-
const block = renderHandbook(runtime);
|
|
111
|
-
const managed = replaceManagedBlock(current, block);
|
|
112
|
-
if (managed !== null) return managed;
|
|
113
|
-
|
|
114
|
-
if (runtime === 'codex') {
|
|
115
|
-
const migrated = replaceLegacyCodexSection(current, block);
|
|
116
|
-
if (migrated !== null) return migrated;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const before = current.trimEnd();
|
|
120
|
-
return `${before}${before ? '\n\n' : ''}${block}\n`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function resolveHandbookFile(runtime, { homeDir = os.homedir() } = {}) {
|
|
124
|
-
return path.join(homeDir, ...host(runtime).handbook);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function timestamp() {
|
|
128
|
-
return new Date().toISOString().replace(/[-:.]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function applyHandbook({ runtime, file, homeDir, now = timestamp() }) {
|
|
132
|
-
const target = path.resolve(file || resolveHandbookFile(runtime, { homeDir }));
|
|
133
|
-
const exists = fs.existsSync(target);
|
|
134
|
-
const existing = exists ? fs.readFileSync(target, 'utf8') : '';
|
|
135
|
-
const merged = mergeHandbook(existing, runtime);
|
|
136
|
-
if (merged === existing) return { runtime, file: target, changed: false, backup: null };
|
|
137
|
-
|
|
138
|
-
let backup = null;
|
|
139
|
-
if (exists) {
|
|
140
|
-
backup = `${target}.ubp-backup-${now}`;
|
|
141
|
-
if (fs.existsSync(backup)) throw new Error(`handbook backup already exists: ${backup}`);
|
|
142
|
-
writeAtomic(backup, existing);
|
|
143
|
-
}
|
|
144
|
-
writeAtomic(target, merged);
|
|
145
|
-
return { runtime, file: target, changed: true, backup };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
module.exports = {
|
|
149
|
-
BEGIN_MARKER,
|
|
150
|
-
END_MARKER,
|
|
151
|
-
HOSTS,
|
|
152
|
-
applyHandbook,
|
|
153
|
-
mergeHandbook,
|
|
154
|
-
renderHandbook,
|
|
155
|
-
resolveHandbookFile,
|
|
156
|
-
};
|