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.
Files changed (40) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +1 -1
  3. package/adapters/_shared/codex-assets.cjs +1 -1
  4. package/adapters/codex.js +85 -13
  5. package/adapters/opencode.js +32 -1
  6. package/adapters/tests/claude.test.cjs +6 -0
  7. package/adapters/tests/codex-hook.test.cjs +25 -0
  8. package/adapters/tests/codex.test.cjs +7 -0
  9. package/adapters/tests/opencode.test.cjs +37 -0
  10. package/commands/ultra-dev.md +2 -1
  11. package/commands/ultra-plan.md +5 -1
  12. package/commands/ultra-status.md +2 -1
  13. package/commands/ultra-test.md +2 -1
  14. package/docs/COMMIT-HASH-BACKFILL.md +3 -3
  15. package/docs/RUNTIME-COMPAT-MATRIX.md +1 -1
  16. package/hooks/active_task_context.py +28 -0
  17. package/mcp-server/lib/projector.cjs +7 -2
  18. package/mcp-server/lib/state-authority.cjs +73 -0
  19. package/mcp-server/lib/state-db.cjs +19 -1
  20. package/mcp-server/lib/state-ops.cjs +3 -2
  21. package/mcp-server/server.cjs +2 -0
  22. package/mcp-server/tests/migrate.test.cjs +56 -1
  23. package/mcp-server/tests/projector.test.cjs +33 -1
  24. package/mcp-server/tests/server-contract.test.cjs +34 -1
  25. package/mcp-server/tests/state-db.test.cjs +31 -0
  26. package/mcp-server/tests/state-ops.test.cjs +15 -2
  27. package/package.json +1 -1
  28. package/skills/ultra-dev/SKILL.md +22 -17
  29. package/skills/ultra-plan/SKILL.md +28 -13
  30. package/skills/ultra-status/SKILL.md +11 -6
  31. package/skills/ultra-test/SKILL.md +9 -3
  32. package/spec/cli-protocol.md +12 -13
  33. package/spec/mcp-tools.yaml +7 -0
  34. package/spec/schemas/state-db.sql +3 -0
  35. package/spec/schemas/tasks.v4.5.schema.json +1 -0
  36. package/ultra-tools/cli.cjs +5 -5
  37. package/ultra-tools/commands/migrate.cjs +136 -48
  38. package/ultra-tools/commands/task.cjs +4 -6
  39. package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -57
  40. package/.ultra-template/tasks/tasks.json +0 -33
@@ -1,9 +1,9 @@
1
1
  # `ultra-tools` CLI protocol
2
2
 
3
- `ultra-tools` is the shell fallback for the live MCP state contract
4
- (PLAN §4, D12). Every tool in `spec/mcp-tools.yaml` has a matching
5
- `ultra-tools <subcommand>`; host-native review, impact discovery, skill
6
- resolution, and human interaction are intentionally outside this protocol.
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 the authoritative reference; do not duplicate it in command md files.
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
- `Writer = mcp` rows go through the MCP server's single-writer connection;
106
- the CLI shells out to the running MCP server over stdio rather than
107
- touching `.ultra/state.db` directly. `Writer = any` rows are safe to
108
- execute from any process — read-only queries and `events` appends fall
109
- into this bucket because `events.id AUTOINCREMENT` makes concurrent
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
 
@@ -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"],
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * ultra-tools — runtime-agnostic state engine for Ultra Builder Pro.
5
5
  *
6
- * Shell fallback for Ultra task/session state, status, database maintenance,
7
- * migration, and explicit legacy-memory cleanup. User interaction, skill
8
- * discovery, and subagent delegation stay on each host's native surfaces.
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 create | update | list | get | delete
37
- session spawn | close | get | list | admission | heartbeat | subscribe
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: tasksJson.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
- const taskInserted = ops.tx(db, () => {
128
- let n = 0;
129
- for (const t of plan.tasks) {
130
- const ctx = plan.contextHeaders[t.id];
131
- insertTask.run({
132
- id: t.id,
133
- title: t.title,
134
- type: t.type,
135
- priority: t.priority,
136
- complexity: t.complexity ?? null,
137
- status: t.status,
138
- deps: t.deps ? JSON.stringify(t.deps) : null,
139
- tag: t.tag ?? null,
140
- trace_to: t.trace_to ?? null,
141
- context_file: ctx ? ctx._file : null,
142
- created_at: t.created_at,
143
- updated_at: t.updated_at,
144
- });
145
- n++;
146
- }
147
- return n;
148
- });
149
-
150
- const eventsInserted = ops.tx(db, () => {
151
- let n = 0;
152
- for (const e of plan.events) {
153
- insertEvent.run({
154
- ts: e.ts || new Date().toISOString(),
155
- type: e.type,
156
- task_id: e.task_id ?? null,
157
- session_id: e.session_id ?? null,
158
- runtime: e.runtime ?? null,
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 = applyForward(db, plan);
224
- recordMigration(db, {
225
- from: SUPPORTED_FROM,
226
- to: SUPPORTED_TO,
227
- direction: 'forward',
228
- status: 'success',
229
- notes: `tasks=${counts.taskInserted} events=${counts.eventsInserted} warnings=${plan.warnings.length}`,
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 = { dispatch, USAGE, parseFlags, planForward, parseFrontmatter };
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 `task` family. Phase 3.1 implements `init-project`
4
- // as the fallback for /ultra-init when MCP is unreachable. Additional verbs
5
- // (create / update / list / ...) will be wired in Phase 3.3+ as each command
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}'; see spec/cli-protocol.md for supported task CLI verbs` } });
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
- }