ultra-builder-pro-cli 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +55 -25
  3. package/adapters/_shared/codex-assets.cjs +8 -3
  4. package/adapters/_shared/interaction-contract.cjs +13 -6
  5. package/adapters/_shared/runtime-assets.cjs +1 -1
  6. package/adapters/claude.js +1 -1
  7. package/adapters/kimi.js +1 -1
  8. package/adapters/opencode.js +1 -1
  9. package/docs/AGENT-CONTEXT.md +38 -40
  10. package/docs/ARCHITECTURE.md +29 -33
  11. package/docs/ARTIFACT-AUTHORITY.md +15 -16
  12. package/docs/ROADMAP.md +2 -2
  13. package/docs/RUNTIME-COMPAT-MATRIX.md +7 -6
  14. package/docs/STATE-DB-ACCESS-POLICY.md +14 -14
  15. package/docs/WORKFLOW-LIFECYCLE.md +39 -33
  16. package/mcp-server/lib/artifact-registry.cjs +25 -1
  17. package/mcp-server/lib/change-workflow.cjs +42 -6
  18. package/mcp-server/lib/ultra-facade.cjs +646 -0
  19. package/mcp-server/lib/workflow-state.cjs +166 -2
  20. package/mcp-server/server.cjs +73 -21
  21. package/package.json +1 -1
  22. package/skills/ultra-change/SKILL.md +38 -98
  23. package/skills/ultra-deliver/SKILL.md +42 -65
  24. package/skills/ultra-dev/SKILL.md +34 -88
  25. package/skills/ultra-doctor/SKILL.md +35 -57
  26. package/skills/ultra-init/SKILL.md +37 -78
  27. package/skills/ultra-plan/SKILL.md +52 -132
  28. package/skills/ultra-research/SKILL.md +41 -100
  29. package/skills/ultra-review/SKILL.md +25 -46
  30. package/skills/ultra-status/SKILL.md +21 -44
  31. package/skills/ultra-test/SKILL.md +25 -61
  32. package/skills/ultra-think/SKILL.md +29 -48
  33. package/skills/ultra-think/references/decision-dialogue.md +6 -4
  34. package/spec/cli-protocol.md +12 -0
  35. package/spec/mcp-tools.yaml +159 -0
  36. package/spec/schemas/mcp-tools.schema.json +1 -1
@@ -59,14 +59,12 @@ inspect -> suggest -> host-native ask -> normalize -> persist -> apply -> read b
59
59
  The host-native ask is conditional. An explicit owner instruction can already resolve
60
60
  a choice, so the model must not ask
61
61
  again merely to manufacture proof. When interaction is needed, the adapter uses the
62
- host-native question surface. Once the model writes the normalized result through
63
- `decision.resolve`, `decision.delegate`, or `decision.defer`, the DB treats that result
64
- as current cross-session intent authority. It does not store or verify a UI receipt,
65
- raw prompt, or transcript.
62
+ host-native question surface. Once the model batches the normalized result through
63
+ `ultra.record`, the DB treats that result as current cross-session intent authority. It
64
+ does not store or verify a UI receipt, raw prompt, or transcript.
66
65
 
67
66
  If the accepted decision changes another Ultra authority, the model applies that change
68
- through its owning MCP tool or digest-bound artifact, reads the result back, and passes
69
- typed `applied_refs` to `decision.complete`. A decision whose normalized record is
67
+ through `ultra.record` or a digest-bound artifact and reads the result back. A decision whose normalized record is
70
68
  itself the complete authority may have no applied reference. Breadcrumb and decision
71
69
  read paths recall accepted intent only after the dialogue thread is completed or
72
70
  confirmed. A row-backed applied reference names an exact field and canonical value,
@@ -77,7 +75,7 @@ the exact file digest. Row existence alone never proves that intent was applied.
77
75
 
78
76
  Working material becomes durable only when all applicable conditions hold:
79
77
 
80
- 1. the active workflow permits the write;
78
+ 1. the current Change and owner authority permit the write;
81
79
  2. consequential claims are verified against the checkout, runtime, test result, or
82
80
  primary source;
83
81
  3. the model writes through the owning MCP operation or immutable artifact path;
@@ -122,9 +120,9 @@ as delivered.
122
120
 
123
121
  ## Context and plan authority
124
122
 
125
- `change.context` is a pure compiler. It reads accepted Change, task, baseline, checkout,
126
- and provider-reference authority and writes one immutable manifest plus its DB snapshot
127
- row. It never updates provider references or semantic Change fields. Each snapshot is
123
+ The Context compiler used by `ultra.checkpoint` reads accepted Change, task, baseline,
124
+ checkout, and provider-reference authority and writes one immutable manifest plus its
125
+ DB snapshot row. It never updates provider references or semantic Change fields. Each snapshot is
128
126
  identified exactly by `change_id`, nullable `task_id`, `role`, and `gate`; consumers
129
127
  must not substitute a newer snapshot from a different tuple.
130
128
 
@@ -141,8 +139,8 @@ A current plan is a Change-owned pair:
141
139
  - `<artifact_root>/plan.md` is the deterministic human projection of the same plan and
142
140
  task contracts.
143
141
 
144
- `plan.export` selects both paths from the owning Change and accepts no arbitrary output
145
- path. `verify-plan` binds the JSON digest and the preceding `plan/planning` snapshot;
142
+ The Plan checkpoint selects both paths from the owning Change and accepts no arbitrary
143
+ output path. It binds the JSON digest and the preceding `plan/planning` snapshot;
146
144
  the artifact registry records both plan files with that context provenance. Publication
147
145
  preflights both path authorities and prior digests, rejects symlinked ancestors or
148
146
  targets, and journals the two file replacements so a registry or event failure restores
@@ -152,7 +150,7 @@ never current write or dispatch authority.
152
150
 
153
151
  ## Registry contract
154
152
 
155
- `artifact.record` is the public writer for a semantic or evidence file that is not
153
+ `ultra.record` is the public writer for a semantic or evidence file that is not
156
154
  already registered by its owning workflow operation. Every managed registry row has:
157
155
 
158
156
  - a typed `owner_type` and `owner_id`;
@@ -190,7 +188,7 @@ pretending that an old row satisfies the current registry contract.
190
188
 
191
189
  ## Dependency and orphan diagnosis
192
190
 
193
- `system.doctor` compares the registry, dependency graph, filesystem, and owner
191
+ `ultra.doctor` compares the registry, dependency graph, filesystem, and owner
194
192
  tables. It reports:
195
193
 
196
194
  - unregistered semantic or evidence files;
@@ -235,7 +233,7 @@ Change creation or update, plan acceptance, durable task-contract or status chan
235
233
  task expansion or deletion, and Change convergence or archive. A direct edit is
236
234
  rejected by host hooks and fails digest validation even without hooks.
237
235
 
238
- `task.ledger_import` validates schema, full and per-record digests, bounded ancestry,
236
+ `ultra.sync { action: import }` validates schema, full and per-record digests, bounded ancestry,
239
237
  Change ownership, task parents, and local active sessions. A clean record fast-forwards.
240
238
  A record changed on both sides, a non-descendant checkpoint, or a remote update to a
241
239
  locally active task stops with a typed conflict. Re-importing the same checkpoint
@@ -245,7 +243,8 @@ HEAD, scope bytes, verification, or accepted dirty state. Publication remains bl
245
243
  until that local baseline converges, preventing one unvalidated checkout from
246
244
  downgrading team authority.
247
245
 
248
- Legacy v4.4/v4.5 projections are replaced only through `task.ledger_publish`. The
246
+ Legacy v4.4/v4.5 projections are replaced only through
247
+ `ultra.sync { action: publish }`. The
249
248
  publisher first compares every available durable field with SQLite, writes the exact
250
249
  legacy bytes to `.ultra/.runtime/backups/task-ledger/`, and refuses any mismatch.
251
250
  Doctor diagnoses this state but never performs the semantic replacement.
package/docs/ROADMAP.md CHANGED
@@ -57,14 +57,14 @@ sharing one authoritative workflow store.
57
57
 
58
58
  ```
59
59
  spec/ ← Phase 1 single source of truth
60
- ├── mcp-tools.yaml (60 live tools across 9 families)
60
+ ├── mcp-tools.yaml (7 public kernel tools + 60 hidden compatibility operations)
61
61
  ├── cli-protocol.md (CLI ↔ MCP mapping table)
62
62
  ├── schemas/ (state-db.sql + 4 JSON schemas)
63
63
  ├── fixtures/{valid,invalid}/ (+ v4.4-project for migration)
64
64
  └── scripts/test-all.cjs (npm run test:spec — 7 validation stages)
65
65
 
66
66
  mcp-server/ ← Phase 2 authoritative state layer
67
- ├── server.cjs (stdio MCP server, 60 task/session/baseline/change/decision/workflow/artifact/system/plan tools)
67
+ ├── server.cjs (stdio MCP server with a 7-tool public façade)
68
68
  ├── lib/
69
69
  │ ├── state-db.cjs (SQLite + WAL + pragmas)
70
70
  │ ├── state-ops.cjs (full write API, status state machine)
@@ -46,7 +46,7 @@ not presented as nonexistent custom agents.
46
46
 
47
47
  | Lifecycle | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
48
48
  |---|---|---|---|---|
49
- | Session context/health | FULL, active-workflow breadcrumb on native `SessionStart` | FULL, native JS plugin injects only an active-workflow breadcrumb; health via `system.doctor` | FULL, active-workflow breadcrumb on native `SessionStart` | FULL, active-workflow breadcrumb via native hooks |
49
+ | Session context/health | FULL, active-workflow breadcrumb on native `SessionStart` | FULL, native JS plugin injects only an active-workflow breadcrumb; health via `ultra.doctor` | FULL, active-workflow breadcrumb on native `SessionStart` | FULL, active-workflow breadcrumb via native hooks |
50
50
  | Active edit boundary | FULL, `PreToolUse Edit|Write` | FULL, `tool.execute.before` rejects managed checkpoint/projection writes | FULL, `PreToolUse Edit|Write|apply_patch` | FULL, native `PreToolUse Edit|Write` deny contract |
51
51
  | Pre-compact checkpoint | FULL | FULL | FULL | FULL, native `PreCompact` |
52
52
  | Post-compact context injection | FULL | FULL, native compacting context | FULL, native `PostCompact` restore | DEGRADED; checkpoint restoration runs, but Kimi 0.26/0.27 does not reinject fire-and-forget hook text |
@@ -68,7 +68,8 @@ they do not deny edits or stop active incident work.
68
68
  | Capability | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
69
69
  |---|---|---|---|---|
70
70
  | stdio MCP registration | plugin `.mcp.json` | `opencode.json` local MCP entry | plugin `.mcp.json` | plugin `mcpServers` entry |
71
- | Live/declared contracts | 60 | 60 | 60 | 60 |
71
+ | Public discovered MCP tools | 7 | 7 | 7 | 7 |
72
+ | Hidden 0.22 compatibility operations | 60 | 60 | 60 | 60 |
72
73
  | Structured user alignment | `AskUserQuestion` in interactive sessions | `question` when permission is not denied | `request_user_input` when the current mode exposes it | `AskUserQuestion` outside auto mode |
73
74
  | Greenfield/brownfield baseline adoption | FULL | FULL | FULL | FULL |
74
75
  | Context Manifest v3 / breadcrumb | FULL | FULL, same bundled read-only DB reader as every hook | FULL | FULL |
@@ -79,10 +80,10 @@ they do not deny edits or stop active incident work.
79
80
  | Git team checkpoint | `.ultra/tasks/tasks.json` | `.ultra/tasks/tasks.json` | `.ultra/tasks/tasks.json` | `.ultra/tasks/tasks.json` |
80
81
  | General memory-provider API | N/A | N/A | N/A | N/A |
81
82
 
82
- All 60 `task.*`, `session.*`, `baseline.*`, `change.*`, `decision.*`,
83
- `workflow.*`, `artifact.*`, `system.*`, and `plan.*` operations
84
- registered by `mcp-server/server.cjs` are live. Review, impact discovery, skill
85
- loading, and user interaction remain host-native surfaces.
83
+ All hosts discover the seven `ultra.*` persistence and safety tools. The sixty
84
+ fine-grained 0.22 operations remain callable but undiscoverable for one compatibility
85
+ release; new Skills do not call them. Review, impact discovery, skill loading, model
86
+ reasoning, and user interaction remain host-native surfaces.
86
87
 
87
88
  When a native structured question surface is unavailable, the shared interaction
88
89
  contract permits one concise direct question only if ordinary conversation is still
@@ -26,7 +26,7 @@ bugs.
26
26
  but never `UPDATE` / `DELETE`.
27
27
 
28
28
  The CLI may indirectly mutate ordinary workflow tables by spawning the MCP server over
29
- stdio and calling `task.update`, `session.spawn`, etc. — but the actual
29
+ stdio and calling the public `ultra.*` kernel — but the actual
30
30
  SQLite write is performed by the MCP server's writer connection. The documented
31
31
  exceptions are initial schema/baseline creation, migration, and explicitly
32
32
  authorized backup-first doctor recovery. The CLI never provides a parallel raw
@@ -36,8 +36,8 @@ The append-only carve-out for `events` exists because `events.id INTEGER
36
36
  PRIMARY KEY AUTOINCREMENT` makes concurrent inserts collision-free under
37
37
  WAL + `busy_timeout`. This lets hooks and short-lived CLI invocations
38
38
  record audit observations without paying the cost of a full MCP round trip.
39
- It does not grant lifecycle authority: external writers and public
40
- `task.append_event` use the published observation allowlist, while lifecycle event
39
+ It does not grant lifecycle authority: external writers use the published observation
40
+ allowlist, while lifecycle event
41
41
  names are emitted only inside the mutation that owns the corresponding state change.
42
42
  Consumers must verify mutable rows rather than infer success from an event name.
43
43
 
@@ -74,16 +74,16 @@ resolve the mount/runtime constraint before retrying.
74
74
 
75
75
  | Table | Writer of record |
76
76
  |--------------------|--------------------------------------------------------|
77
- | `baselines` | MCP server (`baseline.start` / `baseline.record` / `baseline.converge`); initialization and legacy-projection migration may create only the first draft or compatibility row |
78
- | `tasks` | MCP server (`task.create` / `task.update` / `task.delete`) |
79
- | `changes` | MCP server (`change.create` / `change.update` / `change.converge` / `change.archive`) |
80
- | `decision_threads`, `decision_items` | MCP server (`decision.thread_start` / `decision.open` / `decision.resolve` / `decision.delegate` / `decision.defer` / `decision.supersede` / `decision.complete` / `decision.checkpoint`); pending recovery state, normalized accepted intent, durable effects, typed applied references, lifecycle completion, and optional artifact checkpoints are stored, never UI receipts, prompts, or transcripts |
81
- | `workflow_runs`, `workflow_steps` | MCP server (`workflow.start` / `workflow.step` / `workflow.complete`); skills provide evidence inputs but never write rows directly |
82
- | `artifacts`, `artifact_edges` | MCP server (`artifact.record`); workflow-owned registration may use the same internal registry transaction |
83
- | `context_snapshots`, `spec_learning_candidates`, `trace_links` | MCP server through change lifecycle tools |
77
+ | `baselines` | MCP server through `ultra.record`, `ultra.checkpoint`, and `ultra.archive`; initialization and legacy migration may create only the first draft or compatibility row |
78
+ | `tasks` | MCP server through `ultra.record`, `ultra.checkpoint`, and `ultra.sync` |
79
+ | `changes` | MCP server through `ultra.record`, `ultra.checkpoint`, and `ultra.archive` |
80
+ | `decision_threads`, `decision_items` | MCP server through `ultra.record`; normalized accepted intent and applied references are stored, never UI receipts, prompts, or transcripts |
81
+ | `workflow_runs`, `workflow_steps` | MCP server internally through `ultra.checkpoint`; Skills supply semantic evidence but never drive individual rows |
82
+ | `artifacts`, `artifact_edges` | MCP server through `ultra.record` and checkpoint-owned registration |
83
+ | `context_snapshots`, `spec_learning_candidates`, `trace_links` | MCP server through `ultra.record` and `ultra.checkpoint` |
84
84
  | `incidents`, `projection_jobs`, `event_consumers`, `circuit_breaker` | MCP server; backup-first doctor recovery may perform only documented mechanical transitions |
85
- | `events` | MCP server for lifecycle events; approved processes and `task.append_event` for allowlisted append-only observations |
86
- | `sessions` | MCP server (`session.spawn` / `session.close` / `session.heartbeat`); orchestrator may write status transitions |
85
+ | `events` | MCP server for lifecycle events; approved processes and `ultra.record` for allowlisted append-only observations |
86
+ | `sessions` | MCP server through `ultra.session`; orchestrator may write status transitions |
87
87
  | `telemetry` | MCP server (collected from tool-call wrappers); orchestrator may dump bulk samples |
88
88
  | `specs_refs` | MCP server (rebuilt on `spec_changed` event); orchestrator may rebuild |
89
89
  | `migration_history`| schema initializer and `ultra-tools migrate` or doctor repair |
@@ -91,8 +91,8 @@ resolve the mount/runtime constraint before retrying.
91
91
 
92
92
  ## 5. Git team checkpoint
93
93
 
94
- Only `task.ledger_publish` may derive `.ultra/tasks/tasks.json` from SQLite, and only
95
- `task.ledger_import` may merge it into SQLite. Server startup performs one import after
94
+ Only `ultra.sync { action: publish }` may derive `.ultra/tasks/tasks.json` from SQLite,
95
+ and only `ultra.sync { action: import }` may merge it into SQLite. Server startup performs one import after
96
96
  opening project authority; later pulls in a long-running process require an explicit
97
97
  import. The same checkpoint is idempotent and must not append an event again.
98
98
 
@@ -11,7 +11,7 @@ host and project owner. It is the operational companion to
11
11
  |---|---|---|
12
12
  | Active host and owner | Product judgment, research content, implementation, review findings, and authorization for destructive or external effects | Durable workflow status or fabricated evidence digests |
13
13
  | Skill | One reusable procedure and its evidence requirements | Project state, static product doctrine, host-specific tool fiction |
14
- | MCP | IDs, valid transitions, hard recovery requirements, task contracts, evidence references, output hashes, and gate verdicts | Semantic route selection, research prose, code generation, general memory-provider payloads, or model calls |
14
+ | MCP | Durable checkpoints, task contracts, evidence references, output hashes, team sync, leases, archives, and mechanical recovery | Semantic route selection, research prose, code generation, general memory-provider payloads, or model calls |
15
15
  | `.ultra/.runtime/state.db` | Checkout-local lifecycle, index, transition, freshness, and coordination authority for baseline, change, decision, task, workflow, session, event, incident, projection, and evidence references | Prompts, transcripts, general external-memory payloads, semantic prose, or code-graph payloads |
16
16
  | `.ultra/tasks/tasks.json` | MCP-published Git checkpoint for portable baseline, Change, task-contract, dependency, and durable status records | Live sessions, leases, `in_progress` ownership, completion hashes, or a parallel write API |
17
17
  | Digest-bound artifacts | Registered specification, research, Change, context, test, review, delivery, and verification bodies | Independent lifecycle state or content whose digest no longer matches |
@@ -22,19 +22,22 @@ Together, `.ultra/` is project-local cross-session workflow memory. The detailed
22
22
  artifact classes and promotion rules live in
23
23
  [`ARTIFACT-AUTHORITY.md`](./ARTIFACT-AUTHORITY.md).
24
24
 
25
- Prompt input can supply facts, content paths, reasons, owner decisions, and
26
- evidence references. MCP reads the current repository and DB, validates those
27
- inputs, hashes outputs, and derives status. Prompt input cannot override a task's
28
- public seam, verification command, context references, workflow summary, gate
29
- verdict, output digest, or transition validity. A host recommendation is never stored
30
- as MCP authority.
25
+ Model input can supply facts, content paths, reasons, owner decisions, and evidence
26
+ references. `ultra.record` stores mutable typed drafts and `ultra.checkpoint` attempts
27
+ one durable semantic commit. A semantic rejection returns diagnostics and keeps the
28
+ draft editable. Model input cannot override a task's public seam, evidence digest, or
29
+ accepted checkpoint. A host recommendation is never stored as MCP authority.
31
30
 
32
- Load-bearing owner choices are normalized through `decision.*`; Prompt prose is not
33
- durable authority. The host acquires observable facts autonomously, exposes one
34
- current question, and stops. A workflow cannot advance while a matching decision has
35
- an unanswered blocking question, a blocking deferral, a prepared checkpoint awaiting
36
- confirmation, or a stale confirmed artifact. A normalized answer alone does not require
37
- a ceremonial checkpoint.
31
+ Load-bearing owner choices are normalized through `ultra.record`; prompt prose is not
32
+ durable authority. The host acquires observable facts autonomously, exposes one current
33
+ question, and stops. Unanswered material choices remain warnings or checkpoint
34
+ diagnostics until a capability truly needs their result. A normalized answer alone
35
+ does not require a ceremonial checkpoint.
36
+
37
+ Every host discovers seven public MCP tools: `ultra.context`, `ultra.record`,
38
+ `ultra.checkpoint`, `ultra.sync`, `ultra.session`, `ultra.archive`, and
39
+ `ultra.doctor`. The 0.22 fine-grained operations documented below remain hidden
40
+ compatibility internals for one release. Skills must not drive them one call at a time.
38
41
 
39
42
  Every change-bound research, plan, dev, test, review, and deliver run inherits
40
43
  the baseline id from the owning change workflow. A Prompt cannot switch that
@@ -190,8 +193,10 @@ execution failure.
190
193
  ### Workflow run and step
191
194
 
192
195
  ```text
193
- run: active <-> blocked -> ready -> completed
194
- \----> cancelled
196
+ draft: active <-> blocked <-> ready
197
+ | | |
198
+ +----------+---------+----> cancelled
199
+ \----> completed (accepted, immutable)
195
200
 
196
201
  step: pending -> in_progress -> completed
197
202
  | |
@@ -199,10 +204,10 @@ step: pending -> in_progress -> completed
199
204
  blocked ------> in_progress/completed
200
205
  ```
201
206
 
202
- Required steps execute in definition order. A step that requires evidence or an
203
- output cannot complete without it. When the final required step completes, the
204
- run becomes `ready`; `workflow.complete` revalidates the entire stage before
205
- making it `completed`.
207
+ Fine-grained steps are internal evidence bookkeeping. `ultra.checkpoint` records and
208
+ validates them in one public call. A missing output returns mutable diagnostics. Draft
209
+ artifact changes reopen `ready` to `active`; only an accepted `completed` revision is
210
+ immutable.
206
211
 
207
212
  ### Decision thread and item
208
213
 
@@ -315,7 +320,8 @@ opens one replacement.
315
320
 
316
321
  ## 6. Context and prompt follow-through
317
322
 
318
- `change.context` compiles an immutable Context Manifest v3 snapshot. It combines:
323
+ The Context compiler used internally by `ultra.checkpoint` creates an immutable
324
+ Context Manifest v3 snapshot. It combines:
319
325
 
320
326
  - the bound baseline/change/task identity;
321
327
  - a role and lifecycle gate;
@@ -328,11 +334,10 @@ opens one replacement.
328
334
  - optional metadata-only external memory or graph references;
329
335
  - advisory file/token/context-share budgets, including inline Change/task authority;
330
336
  - the current Change and task authority digests;
331
- - readiness blockers, `allowed_transitions`, and a `required_transition` only for a
332
- unique hard-recovery path.
337
+ - readiness warnings and hard mechanical blockers without a semantic route command.
333
338
 
334
339
  Compilation is pure with respect to accepted Change authority. Provider references are
335
- updated only through `change.update`; they cannot be smuggled into `change.context`.
340
+ recorded through `ultra.record`; they cannot be smuggled into a checkpoint context.
336
341
  Context references retain `expected_digest`, `anchor`, `scope`, and
337
342
  `freshness_policy`. `digest` requires byte-current content, `existence` allows an
338
343
  implementation target to change while remaining present, and `advisory` reports drift
@@ -349,9 +354,9 @@ Critical stage workflows bind a matching snapshot as a durable output:
349
354
  | `deliver` | `check` | `convergence` |
350
355
 
351
356
  Every lookup is exact on `change_id + task_id + role + gate`. The snapshot path and
352
- digest are carried into plan, test, review, and delivery artifacts. `plan.export`
353
- writes only the owning Change's `plan.json` and deterministic `plan.md`, and binds the
354
- exact planning snapshot and digest to both the artifact registry and `verify-plan`.
357
+ digest are carried into plan, test, review, and delivery artifacts. The Plan
358
+ `ultra.checkpoint` writes the owning Change's `plan.json` and deterministic `plan.md`
359
+ once, and binds the exact planning snapshot and digest to the artifact registry.
355
360
  The legacy global `.ultra/execution-plan.json` is read-only migration compatibility,
356
361
  not current authority. These rules prevent a later Prompt or a different role packet
357
362
  from claiming evidence gathered under another context. Referenced file content remains
@@ -389,9 +394,9 @@ evidence do block.
389
394
  fast-forward or conflict handling.
390
395
  - A changed checkpoint artifact digest or superseded decision invalidates the linked
391
396
  decision checkpoint and blocks matching workflow advancement until reconfirmed.
392
- - `workflow.revise` creates a candidate without deleting the completed prior run.
393
- `workflow.supersede` promotes only a completed matching candidate, preserves both
394
- histories, and invalidates only the prior run's transitive registered consumers.
397
+ - A rejected draft is repaired in place or explicitly abandoned. A new accepted
398
+ revision preserves prior accepted history and invalidates only true transitive
399
+ registered consumers.
395
400
 
396
401
  ## 8. Entry flows
397
402
 
@@ -459,13 +464,14 @@ evidence rather than bypassing them.
459
464
  | `ultra-doctor` | read-only diagnosis and authorized mechanical recovery | required recovery only when unique; otherwise safe alternatives |
460
465
 
461
466
  Every row permits `ultra-think` when material owner authority is missing and permits
462
- `ultra-doctor` when state or installed-runtime authority is unhealthy. MCP makes one
463
- of them required only when no safe alternative exists. Neither capability creates a
464
- parallel workflow or substitutes a Prompt summary for DB state.
467
+ `ultra-doctor` when state or installed-runtime authority is unhealthy. The host model
468
+ recommends either from current evidence; hard MCP blockers remain limited to the
469
+ mechanical safety boundary. Neither capability creates a parallel workflow or
470
+ substitutes a prompt summary for DB state.
465
471
 
466
472
  ## 9. Recovery and diagnosis
467
473
 
468
- Use `system.doctor` for project authority and
474
+ Use `ultra.doctor` for project authority and
469
475
  `ubp --all --global --doctor` for installed host assets. Both are read-only
470
476
  unless an explicit project repair command is chosen.
471
477
 
@@ -505,10 +505,34 @@ function invalidateConsumersFromEndpointInTx(db, start, {
505
505
  }
506
506
  if (endpoint.type === 'workflow') {
507
507
  const row = db.prepare(
508
- 'SELECT metadata_json, change_id, task_id FROM workflow_runs WHERE id = ?',
508
+ 'SELECT status, metadata_json, change_id, task_id FROM workflow_runs WHERE id = ?',
509
509
  ).get(endpoint.id);
510
510
  if (row) {
511
511
  const metadata = parseJson(row.metadata_json, {});
512
+ if (['active', 'blocked', 'ready'].includes(row.status)) {
513
+ delete metadata.authority_invalidation;
514
+ metadata.draft_dirty = true;
515
+ db.prepare(
516
+ `UPDATE workflow_runs
517
+ SET status = CASE WHEN status = 'ready' THEN 'active' ELSE status END,
518
+ metadata_json = ?,
519
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
520
+ WHERE id = ?`,
521
+ ).run(JSON.stringify(metadata), endpoint.id);
522
+ ops.appendEventInTx(db, {
523
+ type: 'workflow_draft_changed',
524
+ change_id: row.change_id,
525
+ task_id: row.task_id,
526
+ payload: {
527
+ workflow_id: endpoint.id,
528
+ source_type: start.type,
529
+ source_id: start.id,
530
+ reason,
531
+ },
532
+ });
533
+ queue.push(...outgoingEdges(db, endpoint));
534
+ continue;
535
+ }
512
536
  if (metadata.authority_invalidation?.invalidated !== true) {
513
537
  metadata.authority_invalidation = {
514
538
  invalidated: true,
@@ -39,7 +39,7 @@ const SEMANTIC_AUTHORITY_FIELDS = new Set([
39
39
  const CHANGE_TRANSITIONS = Object.freeze({
40
40
  active: new Set(['active', 'blocked', 'cancelled']),
41
41
  blocked: new Set(['active', 'blocked', 'cancelled']),
42
- ready: new Set(['ready']),
42
+ ready: new Set(['active', 'ready', 'cancelled']),
43
43
  archived: new Set(),
44
44
  cancelled: new Set(),
45
45
  });
@@ -702,11 +702,8 @@ function compileContext(db, input, { rootDir = process.cwd() } = {}) {
702
702
  const recommendation = spine.recommendation;
703
703
  const durableSpine = { ...spine };
704
704
  delete durableSpine.recommendation;
705
- const snapshotId = `ctx-${crypto.randomUUID().slice(0, 12)}`;
706
- const manifest = {
705
+ const semanticManifest = {
707
706
  schema_version: '3.0',
708
- snapshot_id: snapshotId,
709
- generated_at: nowIso(),
710
707
  change: {
711
708
  id: change.id, title: change.title, kind: change.kind, status: change.status,
712
709
  intent: change.intent, docs_impact: change.docs_impact, base_commit: change.base_commit,
@@ -734,6 +731,43 @@ function compileContext(db, input, { rootDir = process.cwd() } = {}) {
734
731
  providers,
735
732
  provider_boundary: 'metadata references only; memory and code graph content remain external',
736
733
  };
734
+ const identityHash = crypto.createHash('sha256')
735
+ .update(JSON.stringify(semanticManifest))
736
+ .digest('hex');
737
+ const snapshotId = `ctx-${identityHash.slice(0, 12)}`;
738
+ const existing = db.prepare(
739
+ `SELECT manifest_path, manifest_hash
740
+ FROM context_snapshots WHERE id = ?`,
741
+ ).get(snapshotId);
742
+ if (existing) {
743
+ const existingPath = path.resolve(rootDir, existing.manifest_path);
744
+ if (!fs.existsSync(existingPath)) {
745
+ throw new ChangeWorkflowError(
746
+ 'CONTEXT_SNAPSHOT_CORRUPT',
747
+ `content-addressed Context snapshot is missing: ${existing.manifest_path}`,
748
+ );
749
+ }
750
+ const bytes = fs.readFileSync(existingPath);
751
+ const existingHash = crypto.createHash('sha256').update(bytes).digest('hex');
752
+ if (existingHash !== existing.manifest_hash) {
753
+ throw new ChangeWorkflowError(
754
+ 'CONTEXT_SNAPSHOT_CORRUPT',
755
+ `content-addressed Context snapshot digest changed: ${existing.manifest_path}`,
756
+ );
757
+ }
758
+ return {
759
+ manifest: JSON.parse(bytes.toString('utf8')),
760
+ recommendation,
761
+ context_manifest_path: existingPath,
762
+ manifest_hash: existingHash,
763
+ };
764
+ }
765
+ const manifest = {
766
+ schema_version: semanticManifest.schema_version,
767
+ snapshot_id: snapshotId,
768
+ generated_at: nowIso(),
769
+ ...Object.fromEntries(Object.entries(semanticManifest).filter(([key]) => key !== 'schema_version')),
770
+ };
737
771
  const artifactDir = path.resolve(rootDir, change.artifact_root);
738
772
  fs.mkdirSync(artifactDir, { recursive: true });
739
773
  const manifestPath = path.join(artifactDir, 'contexts', `${snapshotId}.json`);
@@ -1005,7 +1039,7 @@ function assertTaskCreationAllowed(db, input = {}, { rootDir = process.cwd() } =
1005
1039
  function updateChange(db, id, patch = {}, { rootDir = process.cwd() } = {}) {
1006
1040
  const current = readChange(db, id);
1007
1041
  if (!current) throw new ChangeWorkflowError('CHANGE_NOT_FOUND', `change ${id} not found`);
1008
- if (['ready', 'archived', 'cancelled'].includes(current.status)) {
1042
+ if (['archived', 'cancelled'].includes(current.status)) {
1009
1043
  throw new ChangeWorkflowError('CHANGE_NOT_MUTABLE', `change ${id} is ${current.status}`);
1010
1044
  }
1011
1045
  for (const field of Object.keys(patch)) {
@@ -1059,6 +1093,8 @@ function updateChange(db, id, patch = {}, { rootDir = process.cwd() } = {}) {
1059
1093
  );
1060
1094
  }
1061
1095
  sets.push('status = ?'); values.push(patch.status);
1096
+ } else if (current.status === 'ready' && Object.keys(patch).length > 0) {
1097
+ sets.push('status = ?'); values.push('active');
1062
1098
  }
1063
1099
  if (sets.length === 0) return current;
1064
1100
  sets.push('updated_at = ?'); values.push(nowIso(), id);