ultra-builder-pro-cli 0.5.0 → 0.5.2
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 +38 -1
- package/README.md +1 -1
- package/adapters/_shared/codex-assets.cjs +1 -1
- package/adapters/codex.js +85 -13
- 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
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"],
|
package/ultra-tools/cli.cjs
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* ultra-tools — runtime-agnostic state engine for Ultra Builder Pro.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* migration, and explicit legacy-memory cleanup.
|
|
8
|
-
*
|
|
6
|
+
* Maintenance CLI for Ultra initialization, status, database operations,
|
|
7
|
+
* migration, and explicit legacy-memory cleanup. Authoritative task mutations
|
|
8
|
+
* stay on the live MCP server; this CLI is not a task-state fallback.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* ultra-tools <subcommand> [...]
|
|
@@ -33,8 +33,8 @@ USAGE:
|
|
|
33
33
|
ultra-tools <subcommand> [args]
|
|
34
34
|
|
|
35
35
|
SUBCOMMANDS:
|
|
36
|
-
task
|
|
37
|
-
session
|
|
36
|
+
task init-project
|
|
37
|
+
session close | get | list | admission | heartbeat | subscribe | reap
|
|
38
38
|
status [--cost] [--since <duration>] [--json]
|
|
39
39
|
db init | checkpoint | vacuum | integrity | backup (Phase 2)
|
|
40
40
|
migrate --from=4.4 --to=4.5 [--dry|--rollback] (Phase 2)
|
|
@@ -5,6 +5,7 @@ const path = require('node:path');
|
|
|
5
5
|
|
|
6
6
|
const { initStateDb, openStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
|
|
7
7
|
const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
8
|
+
const projector = require('../../mcp-server/lib/projector.cjs');
|
|
8
9
|
|
|
9
10
|
const SUPPORTED_FROM = '4.4';
|
|
10
11
|
const SUPPORTED_TO = '4.5';
|
|
@@ -78,12 +79,85 @@ function copyDirSync(src, dst) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function isoTimestamp(value, fallback) {
|
|
83
|
+
const candidate = value || fallback;
|
|
84
|
+
const date = new Date(candidate);
|
|
85
|
+
if (Number.isNaN(date.getTime())) {
|
|
86
|
+
throw new Error(`migrate: invalid timestamp ${JSON.stringify(candidate)}`);
|
|
87
|
+
}
|
|
88
|
+
return date.toISOString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function projectRelativeContextPath(sourceDir, value) {
|
|
92
|
+
if (!value) return null;
|
|
93
|
+
const normalized = String(value).replaceAll('\\', '/');
|
|
94
|
+
if (normalized.startsWith('.ultra/')) return normalized;
|
|
95
|
+
if (normalized.startsWith('contexts/')) return `.ultra/tasks/${normalized}`;
|
|
96
|
+
if (path.isAbsolute(value)) {
|
|
97
|
+
const relative = path.relative(sourceDir, value).replaceAll(path.sep, '/');
|
|
98
|
+
if (relative.startsWith('../') || relative === '..') {
|
|
99
|
+
throw new Error(`migrate: context_file escapes project root: ${value}`);
|
|
100
|
+
}
|
|
101
|
+
return relative;
|
|
102
|
+
}
|
|
103
|
+
return `.ultra/tasks/contexts/${path.basename(normalized)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sanitizeLegacyContextTemplate(sourceDir) {
|
|
107
|
+
const file = path.join(sourceDir, '.ultra', 'tasks', 'contexts', 'TEMPLATE.md');
|
|
108
|
+
if (!fs.existsSync(file)) return false;
|
|
109
|
+
const current = fs.readFileSync(file, 'utf8');
|
|
110
|
+
const next = current
|
|
111
|
+
.replace(/^>\s*\*\*Status\*\*:.*(?:\r?\n|$)(?:\r?\n)?/mi, '')
|
|
112
|
+
.replace(
|
|
113
|
+
/Read by mid_workflow_recall\.py and session_context\.py and injected into agent context\./g,
|
|
114
|
+
'Used as task-local acceptance criteria by the active Ultra workflow.',
|
|
115
|
+
);
|
|
116
|
+
if (next === current) return false;
|
|
117
|
+
fs.writeFileSync(file, next);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeLegacyTask(task, tasksJson, contextHeaders, sourceDir) {
|
|
122
|
+
if (!task || typeof task !== 'object') throw new Error('migrate: every task must be an object');
|
|
123
|
+
for (const field of ['id', 'title', 'type', 'priority', 'status']) {
|
|
124
|
+
if (task[field] === undefined || task[field] === null || task[field] === '') {
|
|
125
|
+
throw new Error(`migrate: task ${task.id || '(unknown)'} missing ${field}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const deps = task.dependencies ?? task.deps ?? [];
|
|
129
|
+
if (!Array.isArray(deps)) throw new Error(`migrate: task ${task.id} dependencies must be an array`);
|
|
130
|
+
if (task.estimated_days !== undefined
|
|
131
|
+
&& (!Number.isFinite(task.estimated_days) || task.estimated_days <= 0)) {
|
|
132
|
+
throw new Error(`migrate: task ${task.id} estimated_days must be a positive number`);
|
|
133
|
+
}
|
|
134
|
+
const createdAt = isoTimestamp(task.created_at, tasksJson.created);
|
|
135
|
+
const updatedAt = isoTimestamp(task.updated_at, tasksJson.updated || tasksJson.created);
|
|
136
|
+
const ctx = contextHeaders[task.id];
|
|
137
|
+
const contextFile = projectRelativeContextPath(
|
|
138
|
+
sourceDir,
|
|
139
|
+
task.context_file || (ctx && ctx._file),
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
...task,
|
|
143
|
+
deps,
|
|
144
|
+
estimated_days: task.estimated_days ?? null,
|
|
145
|
+
context_file: contextFile,
|
|
146
|
+
created_at: createdAt,
|
|
147
|
+
updated_at: updatedAt,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
81
151
|
function planForward(sourceDir) {
|
|
82
152
|
const tasksPath = path.join(sourceDir, '.ultra', 'tasks', 'tasks.json');
|
|
83
153
|
const tasksJson = readJsonOptional(tasksPath);
|
|
84
154
|
if (!tasksJson || !Array.isArray(tasksJson.tasks)) {
|
|
85
155
|
throw new Error(`migrate: tasks.json missing or malformed at ${tasksPath}`);
|
|
86
156
|
}
|
|
157
|
+
const version = String(tasksJson.version || tasksJson.schema_version || '');
|
|
158
|
+
if (version !== SUPPORTED_FROM) {
|
|
159
|
+
throw new Error(`migrate: expected v${SUPPORTED_FROM} tasks.json, found ${version || '(missing version)'}`);
|
|
160
|
+
}
|
|
87
161
|
|
|
88
162
|
const contextHeaders = {};
|
|
89
163
|
for (const file of findContexts(sourceDir)) {
|
|
@@ -107,9 +181,12 @@ function planForward(sourceDir) {
|
|
|
107
181
|
const eventsPath = path.join(sourceDir, '.ultra', 'activity-log.json');
|
|
108
182
|
const events = readJsonOptional(eventsPath);
|
|
109
183
|
const eventList = Array.isArray(events) ? events : [];
|
|
184
|
+
const tasks = tasksJson.tasks.map((task) => (
|
|
185
|
+
normalizeLegacyTask(task, tasksJson, contextHeaders, sourceDir)
|
|
186
|
+
));
|
|
110
187
|
|
|
111
188
|
return {
|
|
112
|
-
tasks
|
|
189
|
+
tasks,
|
|
113
190
|
events: eventList,
|
|
114
191
|
contextHeaders,
|
|
115
192
|
warnings,
|
|
@@ -118,50 +195,44 @@ function planForward(sourceDir) {
|
|
|
118
195
|
|
|
119
196
|
function applyForward(db, plan) {
|
|
120
197
|
const insertTask = db.prepare(
|
|
121
|
-
"INSERT INTO tasks (id, title, type, priority, complexity, status, deps, tag, trace_to, context_file, created_at, updated_at) VALUES (@id, @title, @type, @priority, @complexity, @status, @deps, @tag, @trace_to, @context_file, @created_at, @updated_at)",
|
|
198
|
+
"INSERT INTO tasks (id, title, type, priority, complexity, estimated_days, status, deps, tag, trace_to, context_file, created_at, updated_at) VALUES (@id, @title, @type, @priority, @complexity, @estimated_days, @status, @deps, @tag, @trace_to, @context_file, @created_at, @updated_at)",
|
|
122
199
|
);
|
|
123
200
|
const insertEvent = db.prepare(
|
|
124
201
|
"INSERT INTO events (ts, type, task_id, session_id, runtime, payload_json) VALUES (@ts, @type, @task_id, @session_id, @runtime, @payload)",
|
|
125
202
|
);
|
|
126
203
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
payload: e.payload === undefined ? null : JSON.stringify(e.payload),
|
|
160
|
-
});
|
|
161
|
-
n++;
|
|
162
|
-
}
|
|
163
|
-
return n;
|
|
164
|
-
});
|
|
204
|
+
let taskInserted = 0;
|
|
205
|
+
for (const t of plan.tasks) {
|
|
206
|
+
insertTask.run({
|
|
207
|
+
id: t.id,
|
|
208
|
+
title: t.title,
|
|
209
|
+
type: t.type,
|
|
210
|
+
priority: t.priority,
|
|
211
|
+
complexity: t.complexity ?? null,
|
|
212
|
+
estimated_days: t.estimated_days,
|
|
213
|
+
status: t.status,
|
|
214
|
+
deps: JSON.stringify(t.deps),
|
|
215
|
+
tag: t.tag ?? null,
|
|
216
|
+
trace_to: t.trace_to ?? null,
|
|
217
|
+
context_file: t.context_file,
|
|
218
|
+
created_at: t.created_at,
|
|
219
|
+
updated_at: t.updated_at,
|
|
220
|
+
});
|
|
221
|
+
taskInserted++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let eventsInserted = 0;
|
|
225
|
+
for (const e of plan.events) {
|
|
226
|
+
insertEvent.run({
|
|
227
|
+
ts: e.ts || new Date().toISOString(),
|
|
228
|
+
type: e.type,
|
|
229
|
+
task_id: e.task_id ?? null,
|
|
230
|
+
session_id: e.session_id ?? null,
|
|
231
|
+
runtime: e.runtime ?? null,
|
|
232
|
+
payload: e.payload === undefined ? null : JSON.stringify(e.payload),
|
|
233
|
+
});
|
|
234
|
+
eventsInserted++;
|
|
235
|
+
}
|
|
165
236
|
|
|
166
237
|
return { taskInserted, eventsInserted };
|
|
167
238
|
}
|
|
@@ -220,14 +291,23 @@ function cmdForward(flags) {
|
|
|
220
291
|
let db;
|
|
221
292
|
try {
|
|
222
293
|
db = initStateDb(dbPath).db;
|
|
223
|
-
const counts =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
294
|
+
const counts = ops.tx(db, () => {
|
|
295
|
+
const existing = db.prepare('SELECT COUNT(*) AS count FROM tasks').get().count;
|
|
296
|
+
if (existing > 0) {
|
|
297
|
+
throw new Error(`migrate: refusing to merge into non-empty state.db (tasks=${existing})`);
|
|
298
|
+
}
|
|
299
|
+
const inserted = applyForward(db, plan);
|
|
300
|
+
recordMigration(db, {
|
|
301
|
+
from: SUPPORTED_FROM,
|
|
302
|
+
to: SUPPORTED_TO,
|
|
303
|
+
direction: 'forward',
|
|
304
|
+
status: 'success',
|
|
305
|
+
notes: `tasks=${inserted.taskInserted} events=${inserted.eventsInserted} warnings=${plan.warnings.length}`,
|
|
306
|
+
});
|
|
307
|
+
return inserted;
|
|
230
308
|
});
|
|
309
|
+
projector.projectAll(db, { rootDir: sourceDir });
|
|
310
|
+
const contextTemplateSanitized = sanitizeLegacyContextTemplate(sourceDir);
|
|
231
311
|
emit({
|
|
232
312
|
ok: true,
|
|
233
313
|
data: {
|
|
@@ -240,6 +320,7 @@ function cmdForward(flags) {
|
|
|
240
320
|
tasks_inserted: counts.taskInserted,
|
|
241
321
|
events_inserted: counts.eventsInserted,
|
|
242
322
|
warnings: plan.warnings,
|
|
323
|
+
context_template_sanitized: contextTemplateSanitized,
|
|
243
324
|
},
|
|
244
325
|
});
|
|
245
326
|
return 0;
|
|
@@ -351,4 +432,11 @@ Rollback restores from the latest backup-v4.4-* directory and writes a
|
|
|
351
432
|
matching migration_history rollback row before dropping state.db.
|
|
352
433
|
`;
|
|
353
434
|
|
|
354
|
-
module.exports = {
|
|
435
|
+
module.exports = {
|
|
436
|
+
dispatch,
|
|
437
|
+
USAGE,
|
|
438
|
+
parseFlags,
|
|
439
|
+
planForward,
|
|
440
|
+
parseFrontmatter,
|
|
441
|
+
sanitizeLegacyContextTemplate,
|
|
442
|
+
};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// CLI dispatcher for the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// migrates — they share the same stdout envelope contract declared in
|
|
7
|
-
// spec/cli-protocol.md §2.
|
|
3
|
+
// CLI dispatcher for the only task-family maintenance command. Runtime task
|
|
4
|
+
// reads and writes intentionally remain MCP-only so a Host cannot silently
|
|
5
|
+
// bypass the state-authority and projection-conflict gates.
|
|
8
6
|
|
|
9
7
|
const { initProject, InitProjectError } = require('../../mcp-server/lib/init-project.cjs');
|
|
10
8
|
|
|
@@ -87,7 +85,7 @@ function dispatch(args) {
|
|
|
87
85
|
switch (verb) {
|
|
88
86
|
case 'init-project': return dispatchInitProject(rest);
|
|
89
87
|
default:
|
|
90
|
-
emit({ ok: false, error: { code: 'UNKNOWN_VERB', message: `unknown task verb '${verb}';
|
|
88
|
+
emit({ ok: false, error: { code: 'UNKNOWN_VERB', message: `unknown task verb '${verb}'; only init-project is supported by the maintenance CLI` } });
|
|
91
89
|
return 1;
|
|
92
90
|
}
|
|
93
91
|
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Task {id}: {title}
|
|
2
|
-
|
|
3
|
-
> **Status**: pending | **Priority**: {priority} | **Complexity**: {complexity}
|
|
4
|
-
|
|
5
|
-
## Context
|
|
6
|
-
|
|
7
|
-
**What**: [Clear description of what to build/change]
|
|
8
|
-
|
|
9
|
-
**Why**: [Business value linking to Persona + Scenario from specs]
|
|
10
|
-
|
|
11
|
-
**Constraints**:
|
|
12
|
-
- [Technical or business constraint 1]
|
|
13
|
-
- [Technical or business constraint 2]
|
|
14
|
-
|
|
15
|
-
## Implementation
|
|
16
|
-
|
|
17
|
-
**Target Files**:
|
|
18
|
-
- `path/to/file.ts` (create)
|
|
19
|
-
- `path/to/existing.ts` (modify: description)
|
|
20
|
-
|
|
21
|
-
**Pattern**: [Reference to existing code patterns to follow]
|
|
22
|
-
|
|
23
|
-
**Tech Notes**: [Framework/library specific guidance]
|
|
24
|
-
|
|
25
|
-
## Acceptance
|
|
26
|
-
|
|
27
|
-
**Tests**:
|
|
28
|
-
- [ ] `test command here`
|
|
29
|
-
- [ ] Pass: [scenario 1 description]
|
|
30
|
-
- [ ] Pass: [scenario 2 description]
|
|
31
|
-
|
|
32
|
-
**Verification**:
|
|
33
|
-
```bash
|
|
34
|
-
# Manual verification command
|
|
35
|
-
curl -X POST localhost:3000/api/example
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Trace
|
|
39
|
-
|
|
40
|
-
**Source**: `.ultra/specs/product.md#section-id`
|
|
41
|
-
|
|
42
|
-
## Change Log
|
|
43
|
-
|
|
44
|
-
| Date | Change | Specs Updated | Reason |
|
|
45
|
-
|------|--------|---------------|--------|
|
|
46
|
-
| {created_date} | Initial creation | - | Generated by /ultra-plan |
|
|
47
|
-
|
|
48
|
-
## Completion
|
|
49
|
-
|
|
50
|
-
> _Fill when task completed_
|
|
51
|
-
|
|
52
|
-
- **Completed**: {date}
|
|
53
|
-
- **Commit**: {hash}
|
|
54
|
-
- **Summary**: {brief description of what was delivered}
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
*Generated by /ultra-plan*
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "4.4",
|
|
3
|
-
"created": "",
|
|
4
|
-
"updated": "",
|
|
5
|
-
"project": {
|
|
6
|
-
"name": "",
|
|
7
|
-
"type": "",
|
|
8
|
-
"stack": ""
|
|
9
|
-
},
|
|
10
|
-
"tasks": [],
|
|
11
|
-
"metadata": {
|
|
12
|
-
"totalTasks": 0,
|
|
13
|
-
"completedTasks": 0,
|
|
14
|
-
"inProgressTasks": 0,
|
|
15
|
-
"pendingTasks": 0,
|
|
16
|
-
"totalEstimatedDays": 0
|
|
17
|
-
},
|
|
18
|
-
"_schema": {
|
|
19
|
-
"_comment": "Lightweight task registry - full context in contexts/task-{id}.md",
|
|
20
|
-
"task_example": {
|
|
21
|
-
"id": "1",
|
|
22
|
-
"title": "Action verb + specific target",
|
|
23
|
-
"type": "feature|architecture|bugfix",
|
|
24
|
-
"priority": "P0|P1|P2|P3",
|
|
25
|
-
"complexity": "1-10",
|
|
26
|
-
"status": "pending|in_progress|completed|blocked",
|
|
27
|
-
"dependencies": ["task_id"],
|
|
28
|
-
"estimated_days": 2,
|
|
29
|
-
"context_file": "contexts/task-1.md",
|
|
30
|
-
"trace_to": ".ultra/specs/product.md#section-id"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|