ultra-builder-pro-cli 0.5.0 → 0.5.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 +27 -1
- package/README.md +1 -1
- package/adapters/_shared/codex-assets.cjs +1 -1
- package/adapters/opencode.js +32 -1
- package/adapters/tests/claude.test.cjs +6 -0
- package/adapters/tests/codex-hook.test.cjs +25 -0
- package/adapters/tests/codex.test.cjs +7 -0
- package/adapters/tests/opencode.test.cjs +37 -0
- package/commands/ultra-dev.md +2 -1
- package/commands/ultra-plan.md +5 -1
- package/commands/ultra-status.md +2 -1
- package/commands/ultra-test.md +2 -1
- package/docs/COMMIT-HASH-BACKFILL.md +3 -3
- package/docs/RUNTIME-COMPAT-MATRIX.md +1 -1
- package/hooks/active_task_context.py +28 -0
- package/mcp-server/lib/projector.cjs +7 -2
- package/mcp-server/lib/state-authority.cjs +73 -0
- package/mcp-server/lib/state-db.cjs +19 -1
- package/mcp-server/lib/state-ops.cjs +3 -2
- package/mcp-server/server.cjs +2 -0
- package/mcp-server/tests/migrate.test.cjs +56 -1
- package/mcp-server/tests/projector.test.cjs +33 -1
- package/mcp-server/tests/server-contract.test.cjs +34 -1
- package/mcp-server/tests/state-db.test.cjs +31 -0
- package/mcp-server/tests/state-ops.test.cjs +15 -2
- package/package.json +1 -1
- package/skills/ultra-dev/SKILL.md +22 -17
- package/skills/ultra-plan/SKILL.md +28 -13
- package/skills/ultra-status/SKILL.md +11 -6
- package/skills/ultra-test/SKILL.md +9 -3
- package/spec/cli-protocol.md +12 -13
- package/spec/mcp-tools.yaml +7 -0
- package/spec/schemas/state-db.sql +3 -0
- package/spec/schemas/tasks.v4.5.schema.json +1 -0
- package/ultra-tools/cli.cjs +5 -5
- package/ultra-tools/commands/migrate.cjs +136 -48
- package/ultra-tools/commands/task.cjs +4 -6
- package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -57
- package/.ultra-template/tasks/tasks.json +0 -33
|
@@ -73,6 +73,17 @@ test('migrate forward inserts tasks + events, records migration_history, creates
|
|
|
73
73
|
const taskIds = db.prepare('SELECT id FROM tasks ORDER BY id').all().map((r) => r.id);
|
|
74
74
|
assert.deepEqual(taskIds, ['task-1', 'task-2', 'task-3']);
|
|
75
75
|
|
|
76
|
+
const migrated = db.prepare(
|
|
77
|
+
'SELECT id, deps, estimated_days, context_file, created_at, updated_at FROM tasks ORDER BY id',
|
|
78
|
+
).all();
|
|
79
|
+
assert.deepEqual(JSON.parse(migrated[1].deps), ['task-1']);
|
|
80
|
+
assert.equal(migrated[1].estimated_days, 1.5);
|
|
81
|
+
assert.equal(migrated[1].context_file, '.ultra/tasks/contexts/task-2.md');
|
|
82
|
+
for (const task of migrated) {
|
|
83
|
+
assert.match(task.created_at, /^2026-04-15T00:00:00\.000Z$/);
|
|
84
|
+
assert.match(task.updated_at, /^2026-04-16T00:00:00\.000Z$/);
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
// Status comes from tasks.json (task-3 = pending), not the context md (blocked)
|
|
77
88
|
const t3 = db.prepare("SELECT status FROM tasks WHERE id = 'task-3'").get();
|
|
78
89
|
assert.equal(t3.status, 'pending');
|
|
@@ -85,6 +96,27 @@ test('migrate forward inserts tasks + events, records migration_history, creates
|
|
|
85
96
|
assert.equal(mig[0].direction, 'forward');
|
|
86
97
|
assert.equal(mig[0].status, 'success');
|
|
87
98
|
closeStateDb(db);
|
|
99
|
+
|
|
100
|
+
const projected = JSON.parse(fs.readFileSync(
|
|
101
|
+
path.join(dir, '.ultra', 'tasks', 'tasks.json'),
|
|
102
|
+
'utf8',
|
|
103
|
+
));
|
|
104
|
+
assert.equal(projected.schema_version, '4.5');
|
|
105
|
+
assert.equal(projected.source, '.ultra/state.db');
|
|
106
|
+
assert.equal(projected.tasks.length, 3);
|
|
107
|
+
assert.equal(projected.tasks[1].estimated_days, 1.5);
|
|
108
|
+
assert.deepEqual(projected.tasks[1].deps, ['task-1']);
|
|
109
|
+
assert.match(
|
|
110
|
+
fs.readFileSync(path.join(dir, '.ultra', 'tasks', 'contexts', 'task-2.md'), 'utf8'),
|
|
111
|
+
/Wire JWT validation into the request pipeline/,
|
|
112
|
+
);
|
|
113
|
+
const contextTemplate = fs.readFileSync(
|
|
114
|
+
path.join(dir, '.ultra', 'tasks', 'contexts', 'TEMPLATE.md'),
|
|
115
|
+
'utf8',
|
|
116
|
+
);
|
|
117
|
+
assert.doesNotMatch(contextTemplate, /> \*\*Status\*\*:/);
|
|
118
|
+
assert.doesNotMatch(contextTemplate, /mid_workflow_recall|session_context/);
|
|
119
|
+
assert.match(contextTemplate, /Preserve this acceptance item/);
|
|
88
120
|
} finally {
|
|
89
121
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
90
122
|
}
|
|
@@ -112,7 +144,7 @@ test('migrate --rollback restores .ultra contents and writes a rollback row', ()
|
|
|
112
144
|
|
|
113
145
|
// .ultra/tasks/tasks.json restored to original v4.4 content
|
|
114
146
|
const tasksJson = JSON.parse(fs.readFileSync(path.join(dir, '.ultra', 'tasks', 'tasks.json'), 'utf8'));
|
|
115
|
-
assert.equal(tasksJson.
|
|
147
|
+
assert.equal(tasksJson.version, '4.4');
|
|
116
148
|
assert.equal(tasksJson.tasks.length, 3);
|
|
117
149
|
closeStateDb(undefined);
|
|
118
150
|
} finally {
|
|
@@ -120,6 +152,29 @@ test('migrate --rollback restores .ultra contents and writes a rollback row', ()
|
|
|
120
152
|
}
|
|
121
153
|
});
|
|
122
154
|
|
|
155
|
+
test('migrate refuses to merge a v4.4 projection into a non-empty state.db', () => {
|
|
156
|
+
const dir = tmpProject();
|
|
157
|
+
try {
|
|
158
|
+
const dbPath = path.join(dir, '.ultra', 'state.db');
|
|
159
|
+
const init = require('../lib/state-db.cjs').initStateDb(dbPath);
|
|
160
|
+
require('../lib/state-ops.cjs').createTask(init.db, {
|
|
161
|
+
id: 'existing', title: 'existing task', type: 'feature', priority: 'P1',
|
|
162
|
+
});
|
|
163
|
+
closeStateDb(init.db);
|
|
164
|
+
|
|
165
|
+
const r = runCli(['migrate', '--from=4.4', '--to=4.5', '--source-dir', dir]);
|
|
166
|
+
assert.equal(r.code, 2);
|
|
167
|
+
assert.equal(r.envelope.error.code, 'MIGRATE_FAILED');
|
|
168
|
+
assert.match(r.envelope.error.message, /non-empty state\.db/);
|
|
169
|
+
|
|
170
|
+
const db = openStateDb(dbPath);
|
|
171
|
+
assert.deepEqual(db.prepare('SELECT id FROM tasks ORDER BY id').all(), [{ id: 'existing' }]);
|
|
172
|
+
closeStateDb(db);
|
|
173
|
+
} finally {
|
|
174
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
123
178
|
test('migrate rejects unsupported --from / --to versions', () => {
|
|
124
179
|
const r1 = runCli(['migrate', '--from=3.0', '--to=4.5', '--dry', '--source-dir', FIXTURE]);
|
|
125
180
|
assert.equal(r1.code, 1);
|
|
@@ -34,7 +34,7 @@ function readJson(p) {
|
|
|
34
34
|
test('projectTasks emits a v4.5-conformant tasks.json', () => {
|
|
35
35
|
const { dir, db } = tmpProject();
|
|
36
36
|
try {
|
|
37
|
-
ops.createTask(db, { id: 'p-1', title: 'first', type: 'feature', priority: 'P1', tag: 'main' });
|
|
37
|
+
ops.createTask(db, { id: 'p-1', title: 'first', type: 'feature', priority: 'P1', tag: 'main', estimated_days: 2.5 });
|
|
38
38
|
ops.createTask(db, { id: 'p-2', title: 'second', type: 'bugfix', priority: 'P2' });
|
|
39
39
|
ops.updateTaskStatus(db, 'p-1', 'in_progress');
|
|
40
40
|
|
|
@@ -49,6 +49,7 @@ test('projectTasks emits a v4.5-conformant tasks.json', () => {
|
|
|
49
49
|
|
|
50
50
|
const p1 = projection.tasks.find((t) => t.id === 'p-1');
|
|
51
51
|
assert.equal(p1.status, 'in_progress');
|
|
52
|
+
assert.equal(p1.estimated_days, 2.5);
|
|
52
53
|
closeStateDb(db);
|
|
53
54
|
} finally {
|
|
54
55
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
@@ -78,6 +79,37 @@ test('projectContext rebuilds header but preserves the body', () => {
|
|
|
78
79
|
}
|
|
79
80
|
});
|
|
80
81
|
|
|
82
|
+
test('projectContext removes the legacy body status banner while preserving task content', () => {
|
|
83
|
+
const { dir, db } = tmpProject();
|
|
84
|
+
try {
|
|
85
|
+
ops.createTask(db, {
|
|
86
|
+
id: 'legacy-body', title: 'legacy context body', type: 'feature', priority: 'P1',
|
|
87
|
+
context_file: '.ultra/tasks/contexts/task-legacy-body.md',
|
|
88
|
+
});
|
|
89
|
+
const ctxFile = path.join(dir, '.ultra', 'tasks', 'contexts', 'task-legacy-body.md');
|
|
90
|
+
fs.mkdirSync(path.dirname(ctxFile), { recursive: true });
|
|
91
|
+
fs.writeFileSync(ctxFile, [
|
|
92
|
+
'# Task legacy-body',
|
|
93
|
+
'',
|
|
94
|
+
'> **Status**: pending | **Priority**: P1 | **Complexity**: 4',
|
|
95
|
+
'',
|
|
96
|
+
'## Context',
|
|
97
|
+
'Keep this body.',
|
|
98
|
+
'',
|
|
99
|
+
].join('\n'));
|
|
100
|
+
|
|
101
|
+
projector.projectContext(db, 'legacy-body', {}, { rootDir: dir });
|
|
102
|
+
const text = fs.readFileSync(ctxFile, 'utf8');
|
|
103
|
+
assert.match(text, /status: pending/);
|
|
104
|
+
assert.doesNotMatch(text, /> \*\*Status\*\*:/);
|
|
105
|
+
assert.match(text, /# Task legacy-body/);
|
|
106
|
+
assert.match(text, /Keep this body\./);
|
|
107
|
+
closeStateDb(db);
|
|
108
|
+
} finally {
|
|
109
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
81
113
|
test('manual edits to tasks.json are overwritten on the next projectAll', () => {
|
|
82
114
|
const { dir, db } = tmpProject();
|
|
83
115
|
try {
|
|
@@ -131,7 +131,7 @@ test('task.create + task.get round trip via MCP', async () => {
|
|
|
131
131
|
await withClient(proj, async (client) => {
|
|
132
132
|
const created = await client.callTool({
|
|
133
133
|
name: 'task.create',
|
|
134
|
-
arguments: { id: 'mcp-1', title: 'first', type: 'feature', priority: 'P1' },
|
|
134
|
+
arguments: { id: 'mcp-1', title: 'first', type: 'feature', priority: 'P1', estimated_days: 2.5 },
|
|
135
135
|
});
|
|
136
136
|
const createdData = readToolPayload(created);
|
|
137
137
|
assert.equal(createdData.id, 'mcp-1');
|
|
@@ -141,6 +141,39 @@ test('task.create + task.get round trip via MCP', async () => {
|
|
|
141
141
|
const gotData = readToolPayload(got);
|
|
142
142
|
assert.equal(gotData.task.id, 'mcp-1');
|
|
143
143
|
assert.equal(gotData.task.title, 'first');
|
|
144
|
+
assert.equal(gotData.task.estimated_days, 2.5);
|
|
145
|
+
});
|
|
146
|
+
} finally {
|
|
147
|
+
fs.rmSync(proj.dir, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('state-backed MCP tools fail closed when a non-empty v4.4 tasks.json meets an empty state.db', async () => {
|
|
152
|
+
const proj = tmpProject();
|
|
153
|
+
try {
|
|
154
|
+
const tasksDir = path.join(proj.dir, '.ultra', 'tasks');
|
|
155
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
156
|
+
fs.writeFileSync(path.join(tasksDir, 'tasks.json'), JSON.stringify({
|
|
157
|
+
version: '4.4',
|
|
158
|
+
tasks: [{
|
|
159
|
+
id: 'legacy-1', title: 'legacy task', type: 'feature', priority: 'P1',
|
|
160
|
+
status: 'pending', dependencies: [], estimated_days: 1,
|
|
161
|
+
context_file: 'contexts/task-legacy-1.md',
|
|
162
|
+
}],
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
await withClient(proj, async (client) => {
|
|
166
|
+
const listed = await client.callTool({ name: 'task.list', arguments: {} });
|
|
167
|
+
const error = expectError(listed);
|
|
168
|
+
assert.equal(error.code, 'LEGACY_STATE_MIGRATION_REQUIRED');
|
|
169
|
+
assert.match(error.message, /ultra-tools migrate --from=4\.4 --to=4\.5/);
|
|
170
|
+
assert.equal(error.details.legacy_task_count, 1);
|
|
171
|
+
|
|
172
|
+
const created = await client.callTool({
|
|
173
|
+
name: 'task.create',
|
|
174
|
+
arguments: { title: 'must not overwrite legacy', type: 'feature', priority: 'P1' },
|
|
175
|
+
});
|
|
176
|
+
assert.equal(expectError(created).code, 'LEGACY_STATE_MIGRATION_REQUIRED');
|
|
144
177
|
});
|
|
145
178
|
} finally {
|
|
146
179
|
fs.rmSync(proj.dir, { recursive: true, force: true });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
|
|
9
|
+
const stateDb = require('../lib/state-db.cjs');
|
|
10
|
+
|
|
11
|
+
test('initStateDb upgrades a v8A.1 database in place with estimated_days', () => {
|
|
12
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-db-upgrade-'));
|
|
13
|
+
const file = path.join(dir, 'state.db');
|
|
14
|
+
try {
|
|
15
|
+
const db = stateDb.openStateDb(file);
|
|
16
|
+
const oldSql = fs.readFileSync(stateDb.SCHEMA_FILE, 'utf8')
|
|
17
|
+
.replace(/^\s*estimated_days.*\n/m, '')
|
|
18
|
+
.replace(/^INSERT OR IGNORE INTO schema_version \(version, description\)\nVALUES \('8A\.2'.*\n/m, '');
|
|
19
|
+
db.exec(oldSql);
|
|
20
|
+
stateDb.closeStateDb(db);
|
|
21
|
+
|
|
22
|
+
const upgraded = stateDb.initStateDb(file);
|
|
23
|
+
const columns = upgraded.db.prepare('PRAGMA table_info(tasks)').all().map((row) => row.name);
|
|
24
|
+
assert.ok(columns.includes('estimated_days'));
|
|
25
|
+
assert.equal(upgraded.schema_version, '8A.2');
|
|
26
|
+
assert.ok(upgraded.db.prepare("SELECT 1 FROM schema_version WHERE version = '8A.2'").get());
|
|
27
|
+
stateDb.closeStateDb(upgraded.db);
|
|
28
|
+
} finally {
|
|
29
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
@@ -20,14 +20,15 @@ function freshDb() {
|
|
|
20
20
|
return { ...t, db: init.db };
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
test('createTask inserts a row, defaults status=pending, emits task_created', () => {
|
|
23
|
+
test('createTask inserts a row, preserves estimated_days, defaults status=pending, emits task_created', () => {
|
|
24
24
|
const { dir, db } = freshDb();
|
|
25
25
|
try {
|
|
26
26
|
const out = ops.createTask(db, {
|
|
27
|
-
id: 'task-001', title: 'first task', type: 'feature', priority: 'P1',
|
|
27
|
+
id: 'task-001', title: 'first task', type: 'feature', priority: 'P1', estimated_days: 2.5,
|
|
28
28
|
});
|
|
29
29
|
assert.equal(out.id, 'task-001');
|
|
30
30
|
assert.equal(out.status, 'pending');
|
|
31
|
+
assert.equal(out.estimated_days, 2.5);
|
|
31
32
|
const events = db.prepare('SELECT type, task_id FROM events').all();
|
|
32
33
|
assert.equal(events.length, 1);
|
|
33
34
|
assert.equal(events[0].type, 'task_created');
|
|
@@ -38,6 +39,18 @@ test('createTask inserts a row, defaults status=pending, emits task_created', ()
|
|
|
38
39
|
}
|
|
39
40
|
});
|
|
40
41
|
|
|
42
|
+
test('patchTask updates estimated_days', () => {
|
|
43
|
+
const { dir, db } = freshDb();
|
|
44
|
+
try {
|
|
45
|
+
ops.createTask(db, { id: 'estimate', title: 'estimate task', type: 'feature', priority: 'P2' });
|
|
46
|
+
const out = ops.patchTask(db, 'estimate', { estimated_days: 1.5 });
|
|
47
|
+
assert.equal(out.estimated_days, 1.5);
|
|
48
|
+
closeStateDb(db);
|
|
49
|
+
} finally {
|
|
50
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
41
54
|
test('createTask rejects duplicates with DUPLICATE_TASK_ID', () => {
|
|
42
55
|
const { dir, db } = freshDb();
|
|
43
56
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-builder-pro-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Multi-runtime installer for the Ultra Builder Pro agent engineering system — deploys commands, agents, skills, hooks, and MCP server to Claude Code, OpenCode, Codex CLI, and Gemini CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ultra-builder-pro-cli": "bin/install.js",
|
|
@@ -6,7 +6,6 @@ mcp_tools_required:
|
|
|
6
6
|
- task.update
|
|
7
7
|
- task.get
|
|
8
8
|
- task.list
|
|
9
|
-
cli_fallback: "task update"
|
|
10
9
|
---
|
|
11
10
|
|
|
12
11
|
# ultra-dev — Phase 3.4
|
|
@@ -17,6 +16,19 @@ MCP `task.update`; the projector regenerates `tasks.json` and context-md
|
|
|
17
16
|
frontmatter. The skill only writes context-md **bodies** (Change Log,
|
|
18
17
|
Completion, Dual-Write notes).
|
|
19
18
|
|
|
19
|
+
## Authority failure boundary
|
|
20
|
+
|
|
21
|
+
All task selection and transitions require the live MCP state tools. If they
|
|
22
|
+
are unavailable or return an authority error, stop. For
|
|
23
|
+
`LEGACY_STATE_MIGRATION_REQUIRED`, instruct:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ultra-tools migrate --from=4.4 --to=4.5 --source-dir <project-root>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Never fall back to `.ultra/tasks/tasks.json`, direct SQLite writes, or a task
|
|
30
|
+
CLI. The JSON file is only a projector-owned view.
|
|
31
|
+
|
|
20
32
|
## Design decisions vs pre-Phase-3
|
|
21
33
|
|
|
22
34
|
- **Single-write status**: Step 1.5 / Step 5 stop touching `contexts/task-{id}.md`
|
|
@@ -30,9 +42,7 @@ Completion, Dual-Write notes).
|
|
|
30
42
|
|
|
31
43
|
## Prerequisites
|
|
32
44
|
|
|
33
|
-
-
|
|
34
|
-
- At least one task with `status=pending`
|
|
35
|
-
- `state.db` initialized (first `task.*` call will init if missing)
|
|
45
|
+
- `task.list` succeeds against state.db and returns at least one `pending` task
|
|
36
46
|
|
|
37
47
|
## Arguments
|
|
38
48
|
|
|
@@ -82,8 +92,6 @@ Acceptance sections. Use the Acceptance list as the initial todo set.
|
|
|
82
92
|
{ "id": "<id>", "patch": { "status": "in_progress" } }
|
|
83
93
|
```
|
|
84
94
|
|
|
85
|
-
**CLI fallback**: `ultra-tools task update <id> --status in_progress`.
|
|
86
|
-
|
|
87
95
|
Projector runs after the write → `tasks.json` + `contexts/task-{id}.md`
|
|
88
96
|
frontmatter re-generated with `status: in_progress`. **Do not hand-edit either.**
|
|
89
97
|
|
|
@@ -191,14 +199,11 @@ Checkpoint `step="4.5", status="review_done", review_session=<id>, review_iterat
|
|
|
191
199
|
{
|
|
192
200
|
"id": "<id>",
|
|
193
201
|
"patch": {
|
|
194
|
-
"status": "completed"
|
|
195
|
-
"completion_commit": "_pending_" // overwritten in Step 6 after commit
|
|
202
|
+
"status": "completed"
|
|
196
203
|
}
|
|
197
204
|
}
|
|
198
205
|
```
|
|
199
206
|
|
|
200
|
-
**CLI fallback**: `ultra-tools task update <id> --status completed`.
|
|
201
|
-
|
|
202
207
|
Projector regenerates frontmatter. **Then** write the context-md **body**
|
|
203
208
|
Completion section (skill responsibility, not projector):
|
|
204
209
|
|
|
@@ -297,15 +302,15 @@ Log in context-md body Change Log:
|
|
|
297
302
|
history. Specs may grow (EXPANSION) or be corrected (CORRECTION). Specs must
|
|
298
303
|
never silently shrink (REDUCTION without gate).
|
|
299
304
|
|
|
300
|
-
## MCP
|
|
305
|
+
## MCP failure matrix
|
|
301
306
|
|
|
302
|
-
| Purpose | MCP tool (phase) |
|
|
303
|
-
|
|
304
|
-
| Select pending task | `task.list` (2) / `task.get` (2) |
|
|
305
|
-
| Status → in_progress | `task.update` (2) |
|
|
307
|
+
| Purpose | MCP tool (phase) | Failure behavior |
|
|
308
|
+
|---------|------------------|------------------|
|
|
309
|
+
| Select pending task | `task.list` (2) / `task.get` (2) | stop and surface the MCP error |
|
|
310
|
+
| Status → in_progress | `task.update` (2) | stop; do not edit a projection |
|
|
306
311
|
| Pre-review checkpoint | none | write `.ultra/workflow-state.json`; lifecycle hooks restore it |
|
|
307
312
|
| Run review | none | `/ultra-review all` + Host-native review agents |
|
|
308
|
-
| Status → completed | `task.update` (2) |
|
|
313
|
+
| Status → completed | `task.update` (2) | stop; do not claim completion |
|
|
309
314
|
| Ask user | none | current Host's native user-interaction surface |
|
|
310
315
|
|
|
311
316
|
## What this skill DOES NOT do
|
|
@@ -319,6 +324,6 @@ never silently shrink (REDUCTION without gate).
|
|
|
319
324
|
|
|
320
325
|
| | |
|
|
321
326
|
|---|---|
|
|
322
|
-
| **Input** |
|
|
327
|
+
| **Input** | state.db via MCP, plus the `context_file` body returned for the selected task |
|
|
323
328
|
| **Writes** | state.db (via `task.update`), context-md body (Completion + Change Log), workflow-state.json checkpoints |
|
|
324
329
|
| **Next** | `/ultra-dev <next-id>` or `/ultra-test` when all Walking Skeleton + critical-path tasks complete |
|
|
@@ -8,7 +8,6 @@ mcp_tools_required:
|
|
|
8
8
|
- task.dependency_topo
|
|
9
9
|
- plan.export
|
|
10
10
|
- plan.get
|
|
11
|
-
cli_fallback: "task create"
|
|
12
11
|
---
|
|
13
12
|
|
|
14
13
|
# ultra-plan — Phase 3.3
|
|
@@ -18,6 +17,20 @@ the authority** (D32): every new task is created via MCP `task.create`; the
|
|
|
18
17
|
projector regenerates `.ultra/tasks/tasks.json` and context-md frontmatter
|
|
19
18
|
automatically. Context-md bodies are written by this skill (not projector).
|
|
20
19
|
|
|
20
|
+
## Authority failure boundary
|
|
21
|
+
|
|
22
|
+
Before planning, call a state-backed MCP tool. If the MCP server is missing,
|
|
23
|
+
unreachable, or returns any authority error, stop before creating tasks. In
|
|
24
|
+
particular, `LEGACY_STATE_MIGRATION_REQUIRED` means the project still has a
|
|
25
|
+
non-empty v4.4 projection; tell the user to run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ultra-tools migrate --from=4.4 --to=4.5 --source-dir <project-root>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Never read or write `.ultra/tasks/tasks.json` as a fallback. It is a generated
|
|
32
|
+
projection, not an alternate task store. There is no task-mutation CLI fallback.
|
|
33
|
+
|
|
21
34
|
## Prerequisites
|
|
22
35
|
|
|
23
36
|
- `/ultra-research` completed — specs must be 100% (no `[NEEDS CLARIFICATION]`)
|
|
@@ -26,6 +39,13 @@ automatically. Context-md bodies are written by this skill (not projector).
|
|
|
26
39
|
|
|
27
40
|
## Workflow
|
|
28
41
|
|
|
42
|
+
### Workflow activation
|
|
43
|
+
|
|
44
|
+
At entry, write `.ultra/workflow-state.json` with
|
|
45
|
+
`{"command":"ultra-plan","task_id":"plan","step":"0","status":"active","ts":"<ISO8601>"}`.
|
|
46
|
+
This activates the workflow-only hooks. Mark it terminal only after the MCP
|
|
47
|
+
task writes and verification complete.
|
|
48
|
+
|
|
29
49
|
### Input Mode Selection (BEFORE Step 0)
|
|
30
50
|
|
|
31
51
|
Two entry points lead into the planner:
|
|
@@ -140,7 +160,7 @@ On fail → instruct user to run `/ultra-research` targeting the gap.
|
|
|
140
160
|
|
|
141
161
|
Fields per task: `id`, `title` (action verb + target), `type`
|
|
142
162
|
(architecture/feature/bugfix), `priority` (P0-P3), `complexity` (1-10),
|
|
143
|
-
`status` (pending initially), `
|
|
163
|
+
`status` (pending initially), `deps` (task IDs), `estimated_days`,
|
|
144
164
|
`context_file`, `trace_to` (spec section anchor).
|
|
145
165
|
|
|
146
166
|
#### Integration tasks — MANDATORY inserts
|
|
@@ -187,19 +207,13 @@ For **each** task in the planned list (topological order):
|
|
|
187
207
|
"type": "architecture",
|
|
188
208
|
"priority": "P0",
|
|
189
209
|
"complexity": 4,
|
|
210
|
+
"estimated_days": 2,
|
|
190
211
|
"deps": [],
|
|
191
212
|
"tag": "<git branch>", // optional
|
|
192
213
|
"trace_to": ".ultra/specs/product.md#US-01"
|
|
193
214
|
}
|
|
194
215
|
```
|
|
195
216
|
|
|
196
|
-
**CLI fallback** (per task):
|
|
197
|
-
```bash
|
|
198
|
-
ultra-tools task create \
|
|
199
|
-
--title "Walking skeleton: …" --type architecture --priority P0 \
|
|
200
|
-
--complexity 4 --trace-to ".ultra/specs/product.md#US-01"
|
|
201
|
-
```
|
|
202
|
-
|
|
203
217
|
After each `task.create`, the server auto-runs the projector — `tasks.json`
|
|
204
218
|
and `contexts/task-{id}.md` (frontmatter only) appear.
|
|
205
219
|
|
|
@@ -299,16 +313,17 @@ Summary output:
|
|
|
299
313
|
| **Output (projected)** | `.ultra/tasks/tasks.json`, `.ultra/tasks/contexts/task-*.md` |
|
|
300
314
|
| **Next** | `/ultra-dev` |
|
|
301
315
|
|
|
302
|
-
## MCP
|
|
316
|
+
## MCP failure matrix
|
|
303
317
|
|
|
304
|
-
| Purpose | MCP tool |
|
|
305
|
-
|
|
318
|
+
| Purpose | MCP tool | Failure behavior |
|
|
319
|
+
|---------|----------|------------------|
|
|
306
320
|
| Scope mode prompt | none | current Host's native user-interaction surface |
|
|
307
|
-
| Create task | `task.create` |
|
|
321
|
+
| Create task | `task.create` | stop; surface the MCP error; never write a projection |
|
|
308
322
|
|
|
309
323
|
## What this skill DOES NOT do
|
|
310
324
|
|
|
311
325
|
- Does NOT write state.db directly — all task writes go through `task.create`
|
|
312
326
|
- Does NOT regenerate `tasks.json` — projector owns that
|
|
327
|
+
- Does NOT read `tasks.json` when MCP state is empty or unavailable
|
|
313
328
|
- Does NOT start implementation — that's `/ultra-dev`
|
|
314
329
|
- Does NOT re-run research — if specs are incomplete, redirects to `/ultra-research`
|
|
@@ -16,13 +16,20 @@ Single-call dashboard. Pulls task state from state.db (authoritative), layers
|
|
|
16
16
|
in `.ultra/test-report.json` + `.ultra/delivery-report.json` (file artifacts),
|
|
17
17
|
reports progress + risks + workflow routing.
|
|
18
18
|
|
|
19
|
+
## Authority failure boundary
|
|
20
|
+
|
|
21
|
+
`task.list` and `task.get` are the only task-state inputs. If either is
|
|
22
|
+
unavailable or returns an authority error, surface it and stop. Never fall back to `.ultra/tasks/tasks.json`.
|
|
23
|
+
On `LEGACY_STATE_MIGRATION_REQUIRED`, instruct the
|
|
24
|
+
user to run `ultra-tools migrate --from=4.4 --to=4.5 --source-dir <project-root>`.
|
|
25
|
+
|
|
19
26
|
## Workflow
|
|
20
27
|
|
|
21
28
|
### Phase 0 — Environment validation
|
|
22
29
|
|
|
23
30
|
| Check | If missing |
|
|
24
31
|
|-------|-----------|
|
|
25
|
-
|
|
|
32
|
+
| state.db reachable through `task.list` | suggest `/ultra-init` only when `.ultra/` is absent; otherwise surface the MCP error |
|
|
26
33
|
| At least one task visible via `task.list` | suggest `/ultra-plan` |
|
|
27
34
|
| `task.list` returns valid rows | surface error |
|
|
28
35
|
|
|
@@ -42,8 +49,6 @@ reports progress + risks + workflow routing.
|
|
|
42
49
|
// → { events: [...], next_since_id: M }
|
|
43
50
|
```
|
|
44
51
|
|
|
45
|
-
**CLI fallback**: `ultra-tools task list` + `ultra-tools session list`.
|
|
46
|
-
|
|
47
52
|
Also read:
|
|
48
53
|
- `.ultra/test-report.json` — if present
|
|
49
54
|
- `.ultra/delivery-report.json` — if present
|
|
@@ -132,12 +137,12 @@ When `task-id` provided:
|
|
|
132
137
|
Display: title, status, priority, complexity, deps, files_modified (if set),
|
|
133
138
|
session_id (if active), `context_file`, `trace_to`, stale flag, update history.
|
|
134
139
|
|
|
135
|
-
## MCP
|
|
140
|
+
## MCP and CLI matrix
|
|
136
141
|
|
|
137
142
|
| Purpose | MCP tool | CLI fallback |
|
|
138
143
|
|---------|----------|--------------|
|
|
139
|
-
| List tasks | `task.list` |
|
|
140
|
-
| Single task detail | `task.get` |
|
|
144
|
+
| List tasks | `task.list` | none; fail closed |
|
|
145
|
+
| Single task detail | `task.get` | none; fail closed |
|
|
141
146
|
| Cost panel (Phase 6.3) | — | `ultra-tools status --cost --json --since 7d` |
|
|
142
147
|
|
|
143
148
|
## Cost panel (Phase 6.3)
|
|
@@ -4,7 +4,6 @@ description: "Pre-delivery quality audit — Anti-Pattern + Coverage Gap + Wirin
|
|
|
4
4
|
runtime: all
|
|
5
5
|
mcp_tools_required:
|
|
6
6
|
- task.list
|
|
7
|
-
cli_fallback: "task list"
|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
# ultra-test — Phase 3.5
|
|
@@ -14,6 +13,13 @@ each writes a JSON gate result; the skill aggregates into `.ultra/test-report.js
|
|
|
14
13
|
This is **not** for running unit tests (that is `/ultra-dev`). This is the
|
|
15
14
|
pre-ship gate.
|
|
16
15
|
|
|
16
|
+
## Authority failure boundary
|
|
17
|
+
|
|
18
|
+
The completed-task precheck requires MCP `task.list`. If it is unavailable or
|
|
19
|
+
returns an authority error, stop the audit. Never fall back to
|
|
20
|
+
`.ultra/tasks/tasks.json`. For `LEGACY_STATE_MIGRATION_REQUIRED`, instruct
|
|
21
|
+
`ultra-tools migrate --from=4.4 --to=4.5 --source-dir <project-root>`.
|
|
22
|
+
|
|
17
23
|
## Prerequisites
|
|
18
24
|
|
|
19
25
|
- `task.list` returns ≥1 task with `status=completed`
|
|
@@ -173,11 +179,11 @@ when `passed=true`.
|
|
|
173
179
|
| Performance | all Core Web Vitals (if frontend) |
|
|
174
180
|
| Security | 0 critical/high |
|
|
175
181
|
|
|
176
|
-
## MCP
|
|
182
|
+
## MCP failure matrix
|
|
177
183
|
|
|
178
184
|
| Purpose | MCP tool | CLI fallback |
|
|
179
185
|
|---------|----------|--------------|
|
|
180
|
-
| Confirm ≥1 completed task | `task.list { status: "completed" }` |
|
|
186
|
+
| Confirm ≥1 completed task | `task.list { status: "completed" }` | none; fail closed |
|
|
181
187
|
| Confirm risky auto-fix | none | current Host's native user-interaction surface |
|
|
182
188
|
|
|
183
189
|
## What this skill DOES NOT do
|
package/spec/cli-protocol.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# `ultra-tools` CLI protocol
|
|
2
2
|
|
|
3
|
-
`ultra-tools` is the
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
`ultra-tools` is the maintenance CLI around the live MCP runtime (PLAN §4,
|
|
4
|
+
D12). The mapping registry below reserves stable command names for MCP tools;
|
|
5
|
+
it does not mean every mapping is executable. Task lifecycle workflows must use
|
|
6
|
+
the live MCP server and fail closed when it is unavailable.
|
|
7
7
|
|
|
8
8
|
Trace: PLAN §6 Phase 1.4, decisions D12 / D29 / D33 / D37.
|
|
9
9
|
|
|
@@ -74,9 +74,10 @@ informational. Never rely on stderr for status.
|
|
|
74
74
|
|
|
75
75
|
## 5. Tool ↔ CLI mapping
|
|
76
76
|
|
|
77
|
-
The mapping below is generated from `spec/mcp-tools.yaml` by
|
|
77
|
+
The compatibility mapping below is generated from `spec/mcp-tools.yaml` by
|
|
78
78
|
`spec/scripts/check-cli-mapping.cjs` and asserted at gate time. This table
|
|
79
|
-
is
|
|
79
|
+
is a naming contract, not an implementation inventory; do not advertise a row
|
|
80
|
+
as a fallback unless `ultra-tools <family> --help` lists it.
|
|
80
81
|
|
|
81
82
|
| MCP tool | CLI subcommand | Phase | Writer |
|
|
82
83
|
|---------------------------|-----------------------------|------:|---------|
|
|
@@ -102,13 +103,11 @@ is the authoritative reference; do not duplicate it in command md files.
|
|
|
102
103
|
| `plan.export` | `plan export` | 8a | mcp |
|
|
103
104
|
| `plan.get` | `plan get` | 8a | any |
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
INSERTs collision-free under WAL + `busy_timeout`. The full policy lives
|
|
111
|
-
in `docs/STATE-DB-ACCESS-POLICY.md` (Phase 2.2 / R25).
|
|
106
|
+
Current executable maintenance surfaces are `task init-project`,
|
|
107
|
+
`session close|get|list|admission|heartbeat|subscribe|reap`, `status`, `db`,
|
|
108
|
+
`migrate`, and `legacy-memory`. In particular, `task create|update|list|get`
|
|
109
|
+
are not CLI fallbacks. The full access policy lives in
|
|
110
|
+
`docs/STATE-DB-ACCESS-POLICY.md` (Phase 2.2 / R25).
|
|
112
111
|
|
|
113
112
|
## 6. Versioning
|
|
114
113
|
|
package/spec/mcp-tools.yaml
CHANGED
|
@@ -14,6 +14,8 @@ info:
|
|
|
14
14
|
Host-native review, impact, skill discovery, and human-interaction capabilities
|
|
15
15
|
are documented by each adapter's capability map rather than advertised as MCP.
|
|
16
16
|
Generators consume this YAML to derive types, validators, and CLI parsers (R19).
|
|
17
|
+
cli_subcommand values reserve stable names; runtime availability is defined by
|
|
18
|
+
ultra-tools <family> --help. Task lifecycle skills use MCP and fail closed.
|
|
17
19
|
Adding a tool requires: (a) update this file, (b) add valid+invalid
|
|
18
20
|
fixture to spec/fixtures/{valid,invalid}/, (c) `npm run test:spec` green.
|
|
19
21
|
|
|
@@ -40,6 +42,7 @@ tools:
|
|
|
40
42
|
type: { type: string, enum: [architecture, feature, bugfix] }
|
|
41
43
|
priority: { type: string, enum: [P0, P1, P2, P3] }
|
|
42
44
|
complexity: { type: integer, minimum: 1, maximum: 10 }
|
|
45
|
+
estimated_days: { type: number, exclusiveMinimum: 0, description: 'estimated implementation effort in days' }
|
|
43
46
|
deps: { type: array, items: { type: string } }
|
|
44
47
|
files_modified: { type: array, items: { type: string }, description: 'Phase 8A conflict detection' }
|
|
45
48
|
parent_id: { type: string, description: 'Phase 8A.3 parent reference for task.expand children' }
|
|
@@ -56,6 +59,7 @@ tools:
|
|
|
56
59
|
errors:
|
|
57
60
|
- { code: VALIDATION_ERROR, description: 'input failed schema validation' }
|
|
58
61
|
- { code: DUPLICATE_TASK_ID, description: 'task id already exists' }
|
|
62
|
+
- { code: LEGACY_STATE_MIGRATION_REQUIRED, description: 'non-empty v4.4 projection must be migrated before state-backed tools can run' }
|
|
59
63
|
- { code: STATE_DB_LOCKED, description: 'database is locked', retriable: true }
|
|
60
64
|
cli_subcommand: task create
|
|
61
65
|
emits_events: [task_created]
|
|
@@ -79,6 +83,7 @@ tools:
|
|
|
79
83
|
status: { type: string, enum: [pending, in_progress, completed, blocked, expanded] }
|
|
80
84
|
priority: { type: string, enum: [P0, P1, P2, P3] }
|
|
81
85
|
complexity: { type: integer, minimum: 1, maximum: 10 }
|
|
86
|
+
estimated_days: { type: number, exclusiveMinimum: 0 }
|
|
82
87
|
deps: { type: array, items: { type: string } }
|
|
83
88
|
files_modified: { type: array, items: { type: string } }
|
|
84
89
|
session_id: { type: string }
|
|
@@ -96,6 +101,7 @@ tools:
|
|
|
96
101
|
- { code: TASK_NOT_FOUND, description: 'no task with given id' }
|
|
97
102
|
- { code: ILLEGAL_STATUS_TRANSITION, description: 'transition rejected by state machine' }
|
|
98
103
|
- { code: VALIDATION_ERROR, description: 'patch failed schema validation' }
|
|
104
|
+
- { code: LEGACY_STATE_MIGRATION_REQUIRED, description: 'non-empty v4.4 projection must be migrated before state-backed tools can run' }
|
|
99
105
|
cli_subcommand: task update
|
|
100
106
|
emits_events: [task_started, task_completed, task_blocked, task_stale_marked]
|
|
101
107
|
trace_to: '§7.3, §7.2'
|
|
@@ -123,6 +129,7 @@ tools:
|
|
|
123
129
|
count: { type: integer, minimum: 0 }
|
|
124
130
|
errors:
|
|
125
131
|
- { code: VALIDATION_ERROR, description: 'filter failed schema validation' }
|
|
132
|
+
- { code: LEGACY_STATE_MIGRATION_REQUIRED, description: 'non-empty v4.4 projection must be migrated before state-backed tools can run' }
|
|
126
133
|
cli_subcommand: task list
|
|
127
134
|
emits_events: []
|
|
128
135
|
trace_to: '§7.3'
|
|
@@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
21
21
|
type TEXT NOT NULL CHECK (type IN ('architecture', 'feature', 'bugfix')),
|
|
22
22
|
priority TEXT NOT NULL CHECK (priority IN ('P0', 'P1', 'P2', 'P3')),
|
|
23
23
|
complexity INTEGER CHECK (complexity BETWEEN 1 AND 10),
|
|
24
|
+
estimated_days REAL CHECK (estimated_days IS NULL OR estimated_days > 0),
|
|
24
25
|
status TEXT NOT NULL DEFAULT 'pending'
|
|
25
26
|
CHECK (status IN ('pending', 'in_progress', 'completed', 'blocked', 'expanded')),
|
|
26
27
|
deps TEXT, -- JSON array of task ids
|
|
@@ -152,3 +153,5 @@ INSERT OR IGNORE INTO schema_version (version, description)
|
|
|
152
153
|
VALUES ('5.2', 'Phase 5.2 — circuit_breaker table for per-task trip tracking');
|
|
153
154
|
INSERT OR IGNORE INTO schema_version (version, description)
|
|
154
155
|
VALUES ('8A.1', 'Phase 8A.1 — tasks.parent_id for task.expand parent→children');
|
|
156
|
+
INSERT OR IGNORE INTO schema_version (version, description)
|
|
157
|
+
VALUES ('8A.2', 'Phase 8A.2 — tasks.estimated_days preserved across MCP and v4.4 migration');
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"type": { "type": "string", "enum": ["architecture", "feature", "bugfix"] },
|
|
27
27
|
"priority": { "type": "string", "enum": ["P0", "P1", "P2", "P3"] },
|
|
28
28
|
"complexity": { "type": "integer", "minimum": 1, "maximum": 10 },
|
|
29
|
+
"estimated_days": { "type": "number", "exclusiveMinimum": 0 },
|
|
29
30
|
"status": {
|
|
30
31
|
"type": "string",
|
|
31
32
|
"enum": ["pending", "in_progress", "completed", "blocked", "expanded"],
|