ultra-builder-pro-cli 0.14.0 → 0.16.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 (163) hide show
  1. package/.ultra-template/docs/research/README.md +11 -7
  2. package/.ultra-template/specs/architecture.md +36 -36
  3. package/.ultra-template/specs/discovery.md +46 -33
  4. package/.ultra-template/specs/product.md +38 -29
  5. package/.ultra-template/specs/research-distillate.md +48 -0
  6. package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
  7. package/CHANGELOG.md +77 -1
  8. package/README.md +46 -24
  9. package/adapters/_shared/codex-assets.cjs +6 -2
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/handbook.cjs +2 -1
  12. package/adapters/claude.js +3 -2
  13. package/adapters/codex.js +1 -0
  14. package/adapters/kimi.js +3 -2
  15. package/adapters/opencode.js +3 -2
  16. package/agents/review-coordinator.md +9 -6
  17. package/commands/ultra-change.md +1 -1
  18. package/commands/ultra-plan.md +1 -1
  19. package/commands/ultra-research.md +1 -1
  20. package/commands/ultra-think.md +2 -2
  21. package/docs/AGENT-CONTEXT.md +43 -19
  22. package/docs/ARCHITECTURE.md +54 -13
  23. package/docs/PLAN.zh-CN.md +4 -2
  24. package/docs/ROADMAP.md +10 -4
  25. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  26. package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
  27. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  28. package/docs/WORKFLOW-LIFECYCLE.md +342 -0
  29. package/hooks/README.md +6 -1
  30. package/hooks/health_check.py +12 -3
  31. package/hooks/subagent_tracker.py +52 -77
  32. package/mcp-server/hook-event.cjs +34 -0
  33. package/mcp-server/lib/archive-journal.cjs +17 -5
  34. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  35. package/mcp-server/lib/change-workflow.cjs +598 -85
  36. package/mcp-server/lib/context-spine.cjs +305 -25
  37. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  38. package/mcp-server/lib/doctor.cjs +42 -3
  39. package/mcp-server/lib/init-project.cjs +210 -16
  40. package/mcp-server/lib/plan-store.cjs +4 -1
  41. package/mcp-server/lib/pricing.cjs +7 -10
  42. package/mcp-server/lib/project-breadcrumb.cjs +15 -0
  43. package/mcp-server/lib/projector.cjs +57 -3
  44. package/mcp-server/lib/spec-learning.cjs +79 -2
  45. package/mcp-server/lib/state-db.cjs +168 -8
  46. package/mcp-server/lib/state-ops.cjs +160 -9
  47. package/mcp-server/lib/workflow-state.cjs +2305 -0
  48. package/mcp-server/server.cjs +215 -15
  49. package/orchestrator/daemon.cjs +1 -2
  50. package/orchestrator/dispatch-rules.cjs +5 -31
  51. package/orchestrator/planner/plan-builder.cjs +21 -12
  52. package/package.json +13 -3
  53. package/skills/ultra-change/SKILL.md +94 -80
  54. package/skills/ultra-change/references/change-contract.md +36 -0
  55. package/skills/ultra-deliver/SKILL.md +76 -79
  56. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  57. package/skills/ultra-dev/SKILL.md +62 -59
  58. package/skills/ultra-doctor/SKILL.md +61 -82
  59. package/skills/ultra-init/SKILL.md +101 -103
  60. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  61. package/skills/ultra-plan/SKILL.md +99 -85
  62. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  63. package/skills/ultra-research/SKILL.md +102 -60
  64. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  65. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  66. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  67. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  68. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  69. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  70. package/skills/ultra-research/references/10-user-personas.md +29 -0
  71. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  72. package/skills/ultra-research/references/20-user-stories.md +30 -0
  73. package/skills/ultra-research/references/21-features-scope.md +29 -0
  74. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  75. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  76. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  77. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  78. package/skills/ultra-research/references/40-deployment.md +29 -0
  79. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  80. package/skills/ultra-research/references/99-synthesis.md +35 -0
  81. package/skills/ultra-review/SKILL.md +63 -73
  82. package/skills/ultra-review/references/review-modes.md +27 -0
  83. package/skills/ultra-review/references/unified-schema.md +25 -0
  84. package/skills/ultra-review/scripts/review_wait.py +40 -3
  85. package/skills/ultra-status/SKILL.md +41 -41
  86. package/skills/ultra-test/SKILL.md +54 -55
  87. package/skills/ultra-think/SKILL.md +26 -12
  88. package/skills/ultra-think/references/decision-dialogue.md +130 -0
  89. package/skills/ultra-verify/SKILL.md +1 -1
  90. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  91. package/spec/cli-protocol.md +17 -3
  92. package/spec/mcp-tools.yaml +683 -64
  93. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  94. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  95. package/spec/schemas/mcp-tools.schema.json +1 -1
  96. package/spec/schemas/state-db.sql +140 -1
  97. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  98. package/spec/schemas/test-report.v1.schema.json +194 -0
  99. package/ultra-tools/cli.cjs +1 -1
  100. package/ultra-tools/commands/migrate.cjs +3 -3
  101. package/ultra-tools/commands/status.cjs +228 -12
  102. package/.ultra-template/delivery-report.json +0 -9
  103. package/.ultra-template/test-report.json +0 -8
  104. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  105. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  106. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  107. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  108. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  109. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  110. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
  111. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  112. package/adapters/_shared/tests/validate.test.cjs +0 -30
  113. package/adapters/tests/claude.test.cjs +0 -138
  114. package/adapters/tests/codex-hook.test.cjs +0 -142
  115. package/adapters/tests/codex.test.cjs +0 -430
  116. package/adapters/tests/kimi-hook.test.cjs +0 -149
  117. package/adapters/tests/kimi.test.cjs +0 -259
  118. package/adapters/tests/opencode.test.cjs +0 -357
  119. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  120. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  121. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  122. package/mcp-server/lib/context-spine.test.cjs +0 -93
  123. package/mcp-server/lib/doctor.test.cjs +0 -148
  124. package/mcp-server/lib/plan-store.test.cjs +0 -132
  125. package/mcp-server/lib/prd-parser.test.cjs +0 -82
  126. package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
  127. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  128. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  129. package/mcp-server/lib/state-db.test.cjs +0 -314
  130. package/mcp-server/lib/task-expander.test.cjs +0 -169
  131. package/mcp-server/lib/topo.test.cjs +0 -158
  132. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  133. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  134. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  135. package/mcp-server/tests/concurrency.test.cjs +0 -112
  136. package/mcp-server/tests/db-ops.test.cjs +0 -121
  137. package/mcp-server/tests/init-project.test.cjs +0 -393
  138. package/mcp-server/tests/migrate.test.cjs +0 -232
  139. package/mcp-server/tests/projector.test.cjs +0 -162
  140. package/mcp-server/tests/server-contract.test.cjs +0 -1467
  141. package/mcp-server/tests/staleness.test.cjs +0 -190
  142. package/mcp-server/tests/state-db.test.cjs +0 -106
  143. package/mcp-server/tests/state-ops.test.cjs +0 -449
  144. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  145. package/mcp-server/tests/telemetry.test.cjs +0 -219
  146. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  147. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  148. package/orchestrator/tests/daemon.test.cjs +0 -311
  149. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  150. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  151. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  152. package/orchestrator/tests/recovery.test.cjs +0 -190
  153. package/orchestrator/tests/session-runner.test.cjs +0 -314
  154. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  155. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  156. package/skills/ultra-research/references/architecture.md +0 -38
  157. package/skills/ultra-research/references/problem-product.md +0 -39
  158. package/skills/ultra-research/references/quality-delivery.md +0 -34
  159. package/skills/ultra-research/references/synthesis.md +0 -38
  160. package/skills/ultra-research/references/users-scope.md +0 -40
  161. package/ultra-tools/legacy-memory.test.cjs +0 -80
  162. package/ultra-tools/status-command.test.cjs +0 -181
  163. package/ultra-tools/system-command.test.cjs +0 -309
@@ -0,0 +1,342 @@
1
+ # Ultra Builder Pro workflow lifecycle
2
+
3
+ This document defines when Ultra writes authoritative state, how each workflow
4
+ advances, what invalidates evidence, and which decisions remain with the active
5
+ host and project owner. It is the operational companion to
6
+ [`ARCHITECTURE.md`](./ARCHITECTURE.md) and the machine contracts under `spec/`.
7
+
8
+ ## 1. Authority boundaries
9
+
10
+ | Surface | Owns | Must not own |
11
+ |---|---|---|
12
+ | Active host and owner | Product judgment, research content, implementation, review findings, release authorization | Durable workflow status or fabricated evidence digests |
13
+ | Skill | One reusable procedure and its evidence requirements | Project state, static product doctrine, host-specific tool fiction |
14
+ | MCP | IDs, transitions, task contracts, evidence references, output hashes, gate verdicts, next action | Research prose, code generation, memory, or model calls |
15
+ | `.ultra/state.db` | Baseline, change, decision, task, workflow, session, event, incident, projection, and evidence authority | Prompts, transcripts, external memory, or code-graph payloads |
16
+ | Generated JSON and Markdown | Read-only projections and durable artifacts | Independent lifecycle authority |
17
+ | Hook | Compact DB-derived lifecycle observation, minimal event metadata, and projection protection | Generic engineering judgment, prompt/transcript capture, parallel lifecycle logs, or arbitrary edit blocking |
18
+
19
+ Prompt input can supply facts, content paths, reasons, owner decisions, and
20
+ evidence references. MCP reads the current repository and DB, validates those
21
+ inputs, hashes outputs, and derives status. Prompt input cannot override a task's
22
+ public seam, verification command, context references, workflow summary, gate
23
+ verdict, output digest, or next action.
24
+
25
+ Load-bearing owner choices are normalized through `decision.*`; Prompt prose is not
26
+ durable authority. The host acquires observable facts autonomously, exposes one
27
+ current question, and stops. A workflow cannot advance while a matching decision
28
+ thread is active, waiting for checkpoint confirmation, or carrying a blocking
29
+ deferral.
30
+
31
+ Every change-bound research, plan, dev, test, review, and deliver run inherits
32
+ the baseline id from the owning change workflow. A Prompt cannot switch that
33
+ authority, and no new stage may start on an archived or cancelled change. Doctor
34
+ recovers a legacy change without this link into an explicitly blocked workflow;
35
+ it does not fabricate the missing provenance.
36
+
37
+ Ultra does not select an MVP. Product scope and delivery posture are owner
38
+ decisions. A reduced research mode is valid only for an explicitly bounded active
39
+ change with a recorded selection reason; initial greenfield and brownfield
40
+ baselines always use all seventeen research steps.
41
+
42
+ ## 2. How `ultra-init` classifies a repository
43
+
44
+ `task.init_project` scans a bounded repository view while ignoring `.git`,
45
+ `.ultra`, dependencies, virtual environments, generated build output, and other
46
+ known non-source directories.
47
+
48
+ `auto` classification is deterministic:
49
+
50
+ - `brownfield` when delivered-system evidence exists: application source, tests,
51
+ deployment configuration, or persisted-state/schema evidence;
52
+ - `greenfield` when none of those signals exists. A Git repository, README,
53
+ package manifest, workspace manifest, or starter documentation alone does not
54
+ prove delivered behavior;
55
+ - `migrated` is never inferred from source. It is a compatibility state created
56
+ when older Ultra authority is preserved and upgraded. It requires a new
57
+ evidence-backed brownfield adoption before normal work.
58
+
59
+ The scan also records bounded signal paths, detected verification commands,
60
+ evidence-backed project type and technology signals, monorepo markers, candidate
61
+ workspace roots, and whether the scan limit was reached. Explicit owner-supplied
62
+ project type or stack metadata overrides detection without deleting the observed
63
+ signals. In a monorepo, the owner selects the repository scope; baseline
64
+ worktree digests and accepted dirty files are restricted to that scope.
65
+
66
+ Classification and readiness are separate. Initialization immediately creates
67
+ or resumes `.ultra/state.db`, installs only the required scaffold, records the
68
+ classification, opens an `init` workflow, and opens `full` research for
69
+ greenfield or `adoption` research for brownfield. Empty templates are not an
70
+ approved baseline.
71
+
72
+ ## 3. Durable status machines
73
+
74
+ ### Baseline
75
+
76
+ ```text
77
+ draft -> adopting -> ready -> superseded
78
+ |
79
+ -> blocked -> adopting
80
+ ```
81
+
82
+ - `draft`: greenfield authority exists but has not been established.
83
+ - `adopting`: brownfield or migrated evidence is being recorded.
84
+ - `blocked`: current baseline evidence, verification, scope, or approval cannot
85
+ converge.
86
+ - `ready`: research, evidence, repository snapshot, gaps, and approval converged.
87
+ - `superseded`: preserved historical baseline after an explicitly authorized
88
+ replacement.
89
+
90
+ `ready` is not trusted as a label alone. Every baseline/status/doctor/change gate
91
+ revalidates its persisted fields, current files, research outputs, revision and
92
+ worktree, known-red acceptance, blocking gaps, and approval provenance. An ordinary
93
+ active change may downgrade only the expected drift created after its completed
94
+ `bind-baseline` step to warnings; it cannot downgrade adoption, migration, missing
95
+ evidence, or structural blockers. Approved incident break-glass is the only other
96
+ execution exception and creates a mandatory reconciliation gap.
97
+
98
+ ### Change
99
+
100
+ ```text
101
+ active <-> blocked -> ready -> archived
102
+ \--------------------------> cancelled
103
+ ```
104
+
105
+ Only a healthy `ready` baseline may authorize a new ordinary change. An incident
106
+ may bypass an unhealthy baseline only with a stored reason and approver; archive
107
+ then creates a blocking baseline-reconciliation gap.
108
+
109
+ ### Task
110
+
111
+ ```text
112
+ pending -> in_progress -> completed
113
+ | |
114
+ v v
115
+ blocked <------+
116
+ |
117
+ -> pending
118
+
119
+ pending -> expanded -> completed
120
+ ```
121
+
122
+ A task is executable only when its DB contract contains an observable outcome,
123
+ traceability, slice kind, public seam, exact verification command, acceptance
124
+ checks, context references, resolved documentation impact, owner, and valid
125
+ dependencies.
126
+
127
+ ### Workflow run and step
128
+
129
+ ```text
130
+ run: active <-> blocked -> ready -> completed
131
+ \----> cancelled
132
+
133
+ step: pending -> in_progress -> completed
134
+ | |
135
+ v v
136
+ blocked ------> in_progress/completed
137
+ ```
138
+
139
+ Required steps execute in definition order. A step that requires evidence or an
140
+ output cannot complete without it. When the final required step completes, the
141
+ run becomes `ready`; `workflow.complete` revalidates the entire stage before
142
+ making it `completed`.
143
+
144
+ ### Decision thread and item
145
+
146
+ ```text
147
+ thread: active -> checkpoint_ready -> confirmed
148
+ ^ |
149
+ |--------------+ revision/supersession
150
+
151
+ item: open -> answered | delegated | deferred | superseded
152
+ ```
153
+
154
+ A thread is bound to baseline, change, or workflow authority. A partial unique index
155
+ allows only one `open` item per thread. Resolved items remain immutable history;
156
+ changed evidence or intent uses `decision.supersede` and reopens the checkpoint.
157
+ `decision.checkpoint` first freezes a normalized decision digest, then binds explicit
158
+ owner approval to current artifact digests or a justified standalone no-artifact
159
+ case. It never stores the conversation transcript.
160
+
161
+ ## 4. Write and verification lifecycle
162
+
163
+ | Stage | Authoritative writes | Completion verification | Next route |
164
+ |---|---|---|---|
165
+ | `init` | Baseline classification/scope, init run, research run, initialization event, projection job | DB/schema opens, scaffold/projection succeeds; baseline convergence later completes init | `research`, re-adoption, or `change` for an already-ready baseline |
166
+ | `research` | Ordered step evidence, decision threads/checkpoints, typed semantic records with source digests, one immutable report per selected step, output paths and SHA-256 digests | Every load-bearing owner decision is confirmed; every report has the required structure; semantic kinds/attributes/source anchors are valid and current; full/adoption synthesis binds the current three specifications and distillate; all digests remain current | `baseline.record`, then approval/convergence; bounded change research returns to its change |
167
+ | baseline convergence | Spec/source/runtime refs, verification results, known failures, unknowns, gaps, branch/HEAD/worktree, approval | Full/adoption research complete; required discovery/product/architecture refs current; revision/worktree exact; blocking gaps resolved or explicitly accepted where allowed | `change` for every initial or daily outcome |
168
+ | `change` | Baseline-bound alignment checkpoint, complete Change Contract, profile/risk rationale, research disposition, intent/delta artifacts, linked change run, initial context snapshot | The linked alignment thread is confirmed; selected change research is current; current plan task set matches DB; task-bound context is ready; one next action is derived | selected `research`, then `plan`, or `dev` for an already approved current plan |
169
+ | `plan` | Plan decision checkpoint, plan-step evidence, complete change-owned task rows, acceptance coverage matrix, change-bound `.ultra/execution-plan.json`, explicit plan approval | Blocking planning choices are confirmed; every Change acceptance id is owned by a task; task trace targets resolve; quick has one task; ownership/dependencies valid; no cycles; exported topology and digest exactly match the current change task set | `dev` for the first dependency-ready task |
170
+ | `dev` | Task/session transitions, step evidence, immutable implementation context, completion/review references | Task completed; no live session; task contract unchanged; starting context and review artifacts valid; completion commits remain ancestors of current HEAD | Next task, then aggregate `test` |
171
+ | `test` | Immutable checking context and `.ultra/reports/tests/<workflow-id>.json` | Report change/task set, full HEAD, worktree digest, context digest, commands, public seam, blockers, and all acceptance/regression/integration/static/build/performance/security/recovery dimensions agree; every omission has a not-applicable rationale | `review` when passing, otherwise resume `dev`/`test` |
172
+ | `review` | Immutable review context, `task`/`change`/`plan` mode, worker selection provenance, two specialist axes, coordinated summary | Current diff/HEAD and context match; selected/skipped workers match recorded rationale; both axes complete; P0/P1 and axis verdicts deterministically derive final verdict | `deliver` only from approved `change` mode, otherwise resume implementation |
173
+ | `deliver` | Convergence context, verified learning resolutions, schema-validated semantic reconciliation manifest, change archive, durable release-authorization decision, immutable delivery report | Tasks/test/change-review current; every baseline update has an anchored before/after semantic record; `change.converge` derives evidence from DB and accepts no Prompt verdicts; archive and baseline transaction agree; external actions have evidence | Report completion or start the next `change` |
174
+ | `status` | None | Reads doctor, baseline, workflows, breadcrumb, tasks, sessions, Git, and DB-referenced reports | One exact current route |
175
+ | `doctor` | None by default; explicit repair writes only mechanical recovery state | Re-runs integrity, schema, workflow-output, projection, session, incident, archive, and install checks | Exact repair, restore/rebaseline decision, or workflow route |
176
+
177
+ Every mutating MCP operation appends an event and enqueues projection work when
178
+ the corresponding read-only view can change. Projection failure is recorded as
179
+ a retryable incident; it does not silently convert a failed write into success.
180
+ `task.append_event` accepts only non-authoritative observations. Lifecycle events are
181
+ emitted by their owning DB mutation and never act as a substitute state transition.
182
+
183
+ ## 5. Human-agent alignment lifecycle
184
+
185
+ The host first reads `decision.list` and resumes matching authority. It starts a new
186
+ thread only for a new decision cluster. For every current decision it must:
187
+
188
+ 1. inspect repository, runtime, tests, specifications, and primary-source evidence;
189
+ 2. resolve evidence-answerable facts without asking the owner;
190
+ 3. call `decision.open` for the earliest unresolved owner choice with a recommendation,
191
+ no more than three credible alternatives, evidence refs, and durable effects;
192
+ 4. present that one choice and end the turn;
193
+ 5. normalize the next owner response with `decision.resolve`, explicit reversible
194
+ delegation with `decision.delegate`, or a consequence-bearing deferral with
195
+ `decision.defer`;
196
+ 6. prepare one compact checkpoint, obtain one owner approval, update the affected
197
+ artifact, and confirm the checkpoint with its current digest. Workflow- and
198
+ change-bound threads must bind an artifact. If a shared specification will keep
199
+ evolving, bind a stable per-thread decision projection rather than the mutable
200
+ working draft.
201
+
202
+ `ultra-think` owns this interaction protocol. `ultra-research`, `ultra-change`, and
203
+ `ultra-plan` invoke it at their decision boundaries. `ultra-dev`, `ultra-test`,
204
+ `ultra-review`, and `ultra-deliver` stop and route back to the same thread when new
205
+ owner authority is required; they never answer on the owner's behalf. `ultra-status`
206
+ shows only the current decision and exact route, while `ultra-doctor` diagnoses stale
207
+ checkpoint artifacts and recovery state.
208
+
209
+ When only a bound artifact changes, the same resolved thread is prepared and approved
210
+ again against the current digest; this is checkpoint renewal, not a fabricated decision
211
+ revision. When the owner choice itself changes, supersession preserves the old item and
212
+ opens one replacement.
213
+
214
+ ## 6. Context and prompt follow-through
215
+
216
+ `change.context` compiles an immutable Context Manifest v2 snapshot. It combines:
217
+
218
+ - the bound baseline/change/task identity;
219
+ - a role and lifecycle gate;
220
+ - DB-backed task outcome, public seam, verification command, and required refs;
221
+ - local file hashes, current HEAD, branch, and worktree digest;
222
+ - optional metadata-only external memory or graph references;
223
+ - advisory file/token/context-share budgets;
224
+ - readiness blockers and one DB-derived next action.
225
+
226
+ Critical stage workflows bind a matching snapshot as a durable output:
227
+
228
+ | Workflow | Required role | Required gate |
229
+ |---|---|---|
230
+ | `change` | `plan` or `implement` | `planning` or `implementation` |
231
+ | `dev` | `implement` | `implementation` |
232
+ | `test` | `check` | `verification` |
233
+ | `review` | `review` | `review` |
234
+ | `deliver` | `check` | `convergence` |
235
+
236
+ The snapshot path and digest are then carried into test, review, and delivery
237
+ reports. This prevents a later Prompt from claiming evidence gathered under a
238
+ different context. File and token budgets warn; they do not block legitimate
239
+ work. Missing authority, required refs, task contracts, outputs, approval, or
240
+ current gate evidence do block.
241
+
242
+ ## 7. Invalidation rules
243
+
244
+ - Changing a task contract invalidates contexts and gates that depended on the
245
+ former contract.
246
+ - Changing required source/spec content invalidates the recorded output or
247
+ context digest.
248
+ - Changing a research semantic source invalidates that workflow even when its immutable
249
+ step report remains unchanged.
250
+ - Changing full HEAD or the worktree invalidates aggregate test, review, and
251
+ delivery evidence.
252
+ - A completed dev run remains valid across later task commits when its bound
253
+ task contract is unchanged and its recorded commits remain ancestors of the
254
+ current HEAD.
255
+ - Test, review, and delivery reports use workflow-id paths so a later run cannot
256
+ overwrite historical evidence.
257
+ - `.ultra` workflow artifacts are excluded from application-worktree drift, but
258
+ their own recorded SHA-256 digests are still authoritative.
259
+ - Generated `tasks.json` and task-context headers are projections. Manual edits
260
+ are overwritten and never repair DB authority.
261
+ - A changed checkpoint artifact digest or superseded decision invalidates the linked
262
+ decision checkpoint and blocks matching workflow advancement until reconfirmed.
263
+
264
+ ## 8. Entry flows
265
+
266
+ ### New project
267
+
268
+ ```text
269
+ ultra-init -> full research (17 steps with one-decision checkpoints) -> baseline approval
270
+ -> initial change alignment -> plan checkpoint -> dev per task -> test -> review -> deliver
271
+ ```
272
+
273
+ ### Existing project
274
+
275
+ ```text
276
+ ultra-init -> brownfield adoption research (17 steps with one-decision checkpoints)
277
+ -> characterization and known-red evidence -> gap ledger -> owner approval
278
+ -> first selected change alignment -> plan checkpoint -> dev -> test -> review -> deliver
279
+ ```
280
+
281
+ Adoption records the system that exists. It does not force the owner to recreate
282
+ market, persona, or roadmap claims that cannot be supported; those remain
283
+ `Unknown` or explicit gaps while every semantic step is still processed.
284
+
285
+ ### Earlier Ultra project
286
+
287
+ ```text
288
+ doctor/migrate with backup -> migrated compatibility authority
289
+ -> explicit brownfield re-adoption -> full convergence -> normal change loop
290
+ ```
291
+
292
+ Migration preserves prior files and rows but never carries old approval forward.
293
+
294
+ ### Daily maintenance
295
+
296
+ ```text
297
+ ultra-status -> ultra-change capture/alignment -> plan if task contracts are absent
298
+ -> dev -> test -> review -> deliver -> reconciled baseline
299
+ ```
300
+
301
+ Quick changes shorten the artifact burden only where the machine contract allows;
302
+ they do not skip task ownership, executable acceptance, current checking, review,
303
+ or baseline reconciliation. Incidents add structured diagnosis and recovery
304
+ evidence rather than bypassing them.
305
+
306
+ ### Command handoff graph
307
+
308
+ | Entry | Owns | Normal handoff |
309
+ |---|---|---|
310
+ | `ultra-init` | classification, scope, scaffold, baseline/run creation | `ultra-research`; an already-ready project routes to `ultra-change` |
311
+ | `ultra-research` | evidence acquisition, seventeen-step semantic coverage, research decisions | baseline approval then `ultra-change`; bounded research returns to `ultra-plan` |
312
+ | `ultra-think` | one current owner decision or read-only diagnosis | exact invoking workflow and step after checkpoint confirmation |
313
+ | `ultra-change` | outcome capture, alignment, Change Contract, risk/research routing | selected `ultra-research`, then `ultra-plan`; approved existing plan may route to `ultra-dev` |
314
+ | `ultra-plan` | plan decisions, task contracts, dependencies, approval | first ready task through `ultra-dev` |
315
+ | `ultra-dev` | one task/session vertical slice and focused review | next `ultra-dev` task or aggregate `ultra-test` |
316
+ | `ultra-test` | change-level acceptance and regression gate | `ultra-review` on pass, `ultra-dev` on implementation failure |
317
+ | `ultra-review` | independent specification and engineering verdicts | `ultra-deliver` on approval; otherwise `ultra-dev` or `ultra-test` |
318
+ | `ultra-deliver` | convergence, reconciliation, archive, authorized release evidence | `ultra-status` or the next `ultra-change` |
319
+ | `ultra-status` | read-only authoritative position and one route | whichever exact workflow owns the current step |
320
+ | `ultra-doctor` | read-only diagnosis and authorized mechanical recovery | exact recovered workflow, `ultra-init`, or `ultra-think` |
321
+
322
+ Every row routes to `ultra-think` when owner authority is missing and to
323
+ `ultra-doctor` when state or installed-runtime authority is unhealthy. Neither route
324
+ creates a parallel workflow or substitutes a Prompt summary for the DB state.
325
+
326
+ ## 9. Recovery and diagnosis
327
+
328
+ Use `system.doctor` for project authority and `ubp --doctor` for installed host
329
+ assets. Both are read-only unless an explicit project repair command is chosen.
330
+
331
+ During an explicit repair, a legacy active change that predates durable workflows gets
332
+ one blocked change run at `bind-baseline` with
333
+ `LEGACY_CHANGE_PROVENANCE_REQUIRED`; repair does not invent its evidence. Rebind it to
334
+ the re-adopted baseline and resume the same run before planning. Every plan must use
335
+ the exact baseline id stored by its owning change run; only an approved break-glass
336
+ incident may legitimately bind no baseline id.
337
+
338
+ Resume the same blocked workflow and current step after resolving its blocker.
339
+ Do not create parallel runs to escape state. Supported schema and projection
340
+ repairs are backup-first. A corrupt DB is preserved and requires an explicit
341
+ restore or rebaseline decision; neither action can manufacture research,
342
+ verification, or owner approval.
package/hooks/README.md CHANGED
@@ -14,12 +14,17 @@ advisory workflow conditions into refusal gates.
14
14
  | Before compact | `workflow_checkpoint.py` | Validate and atomically save a minimal workflow checkpoint |
15
15
  | After compact/resume | `workflow_resume.py` | Prefer the DB breadcrumb; restore the minimal file checkpoint only when no active change owns recovery |
16
16
  | Stop | `pre_stop_check.py` | Report an incomplete workflow boundary and allow stop |
17
- | Subagent lifecycle | `subagent_tracker.py` | Append lifecycle evidence under `.ultra/runtime/` |
17
+ | Subagent lifecycle | `subagent_tracker.py` | Append minimal start/stop metadata to the authoritative DB event stream |
18
18
 
19
19
  The plugin deliberately does not capture prompts, tool observations, transcripts, summaries, or
20
20
  cross-session memory or code-graph content. Generic command blocking and post-edit policy are
21
21
  user/repository governance. Persistent Memory/graph content belongs to separately installed providers.
22
22
 
23
+ `subagent_tracker.py` passes only agent id/type and the host session id to the bundled
24
+ `hook-event.cjs` helper. The helper binds the current change/task and appends
25
+ `subagent_started` or `subagent_stopped` to `state.db`; it never stores transcript
26
+ paths, messages, or a parallel JSONL lifecycle log.
27
+
23
28
  `context_spine.py` is an imported library, not an eighth lifecycle registration.
24
29
  Registering it separately would duplicate hook execution. The checkpoint is a recovery artifact, not a second authority. A valid newer
25
30
  live workflow wins; a newer valid checkpoint may restore a missing or older
@@ -14,7 +14,7 @@ REQUIRED_TABLES = {
14
14
  "tasks", "events", "sessions", "schema_version", "migration_history",
15
15
  "telemetry", "specs_refs", "circuit_breaker", "changes", "artifacts",
16
16
  "context_snapshots", "spec_learning_candidates", "trace_links", "incidents", "projection_jobs",
17
- "event_consumers",
17
+ "event_consumers", "workflow_runs", "workflow_steps",
18
18
  }
19
19
 
20
20
 
@@ -72,12 +72,21 @@ def inspect(root: Path) -> dict:
72
72
  ]
73
73
  if str(code).startswith("BASELINE_")
74
74
  ]
75
+ baseline = breadcrumb.get("baseline") if breadcrumb else None
76
+ baseline_is_ready = isinstance(baseline, dict) and baseline.get("status") == "ready"
77
+ baseline_status = (
78
+ "fail" if baseline_codes and baseline_is_ready
79
+ else "warning" if baseline_codes
80
+ else "pass"
81
+ )
75
82
  report["checks"]["baseline"] = {
76
- "baseline": breadcrumb.get("baseline") if breadcrumb else None,
83
+ "baseline": baseline,
77
84
  "blockers": baseline_codes,
78
85
  "readiness": "blocked" if baseline_codes else "ready",
79
- "status": "warning" if baseline_codes else "pass",
86
+ "status": baseline_status,
80
87
  }
88
+ if baseline_status == "fail":
89
+ issues.append("baseline")
81
90
 
82
91
  incidents = [
83
92
  {"id": row[0], "code": row[1], "severity": row[2], "retryable": bool(row[3])}
@@ -1,69 +1,67 @@
1
1
  #!/usr/bin/env python3
2
- """Subagent lifecycle tracker.
2
+ """Append minimal Ultra subagent lifecycle metadata to state.db events.
3
3
 
4
- Logs SubagentStart/Stop events to .ultra/runtime/subagent-log.jsonl (project-level)
5
- for debugging and cost analysis.
6
-
7
- Protocol fields:
8
- SubagentStart: agent_id, agent_type, session_id
9
- SubagentStop: agent_id, agent_type, session_id, agent_transcript_path,
10
- last_assistant_message, stop_hook_active
11
-
12
- Usage:
13
- python3 subagent_tracker.py start # called by SubagentStart hook
14
- python3 subagent_tracker.py stop # called by SubagentStop hook
4
+ The hook never stores prompts, messages, transcript paths, or a parallel JSONL log.
5
+ It is advisory: a failed lifecycle event is reported on stderr but never blocks the
6
+ host's subagent operation.
15
7
  """
16
8
 
17
9
  import json
10
+ import os
11
+ import shutil
12
+ import subprocess
18
13
  import sys
19
- from datetime import datetime, timezone
20
14
  from pathlib import Path
21
15
 
22
- from context_spine import ContextSpineError, find_root, read_breadcrumb
16
+ from context_spine import find_root
23
17
 
24
- MAX_LOG_LINES = 5000
25
18
 
19
+ def helper_path() -> Path:
20
+ hook_root = Path(__file__).resolve().parent
21
+ installed = hook_root.parent / "runtime" / "hook-event.cjs"
22
+ if installed.is_file():
23
+ return installed
24
+ source = hook_root.parent / "mcp-server" / "hook-event.cjs"
25
+ if source.is_file():
26
+ return source
27
+ raise RuntimeError("bundled hook lifecycle helper is missing")
26
28
 
27
- def get_log_dir(hook_input: dict):
28
- root = find_root(Path(hook_input.get("cwd") or Path.cwd()).resolve())
29
- if root is None:
30
- return None
31
- try:
32
- breadcrumb = read_breadcrumb(root)
33
- except ContextSpineError as exc:
34
- print(f"[subagent_tracker] cannot inspect Context Spine: {exc}", file=sys.stderr)
35
- return None
36
- if breadcrumb and breadcrumb.get("change_id"):
37
- return root / ".ultra" / "runtime"
38
- return None
39
29
 
30
+ def node_binary() -> str:
31
+ configured = os.environ.get("UBP_NODE")
32
+ if configured:
33
+ return configured
34
+ discovered = shutil.which("node")
35
+ if discovered:
36
+ return discovered
37
+ raise RuntimeError("node executable is unavailable")
40
38
 
41
- def rotate_log(log_file: Path) -> None:
42
- """Keep last MAX_LOG_LINES entries to prevent unbounded growth."""
43
- try:
44
- if not log_file.exists():
45
- return
46
- with open(log_file, "r", encoding="utf-8") as f:
47
- lines = f.readlines()
48
- if len(lines) <= MAX_LOG_LINES:
49
- return
50
- with open(log_file, "w", encoding="utf-8") as f:
51
- f.writelines(lines[-MAX_LOG_LINES:])
52
- except OSError as exc:
53
- print(f"[subagent_tracker] log rotation failed: {exc}", file=sys.stderr)
54
-
55
-
56
- def main():
57
- if len(sys.argv) < 2:
58
- print(json.dumps({}))
59
- return
60
39
 
61
- action = sys.argv[1]
40
+ def append_event(root: Path, action: str, hook_input: dict) -> None:
41
+ payload = {
42
+ "agent_id": hook_input.get("agent_id", "unknown"),
43
+ "agent_type": hook_input.get("agent_type", "unknown"),
44
+ "session_id": hook_input.get("session_id", ""),
45
+ }
46
+ result = subprocess.run(
47
+ [node_binary(), str(helper_path()), str(root.resolve()), action],
48
+ input=json.dumps(payload),
49
+ text=True,
50
+ capture_output=True,
51
+ check=False,
52
+ timeout=5,
53
+ )
54
+ if result.returncode != 0:
55
+ detail = result.stderr.strip() or f"exit {result.returncode}"
56
+ raise RuntimeError(detail)
57
+
58
+
59
+ def main() -> None:
60
+ action = sys.argv[1] if len(sys.argv) > 1 else ""
62
61
  if action not in ("start", "stop"):
63
62
  print(json.dumps({}))
64
63
  return
65
64
 
66
- # Read hook input from stdin
67
65
  try:
68
66
  raw = sys.stdin.read()
69
67
  hook_input = json.loads(raw) if raw.strip() else {}
@@ -73,35 +71,12 @@ def main():
73
71
  print(f"[subagent_tracker] invalid hook input: {exc}", file=sys.stderr)
74
72
  hook_input = {}
75
73
 
76
- # Lazy init: avoid module-level subprocess
77
- log_dir = get_log_dir(hook_input)
78
- if log_dir is None:
79
- print(json.dumps({}))
80
- return
81
- log_file = log_dir / "subagent-log.jsonl"
82
- log_dir.mkdir(parents=True, exist_ok=True)
83
-
84
- entry = {
85
- "timestamp": datetime.now(timezone.utc).isoformat(),
86
- "event": f"subagent_{action}",
87
- "agent_id": hook_input.get("agent_id", "unknown"),
88
- "agent_type": hook_input.get("agent_type", "unknown"),
89
- "session_id": hook_input.get("session_id", ""),
90
- }
91
-
92
- # SubagentStop provides additional fields
93
- if action == "stop":
94
- transcript = hook_input.get("agent_transcript_path", "")
95
- if transcript:
96
- entry["agent_transcript_path"] = transcript
97
-
98
- try:
99
- with open(log_file, "a", encoding="utf-8") as f:
100
- f.write(json.dumps(entry, ensure_ascii=False) + "\n")
101
- except OSError as exc:
102
- print(f"[subagent_tracker] event write failed: {exc}", file=sys.stderr)
103
-
104
- rotate_log(log_file)
74
+ root = find_root(Path(hook_input.get("cwd") or Path.cwd()).resolve())
75
+ if root is not None:
76
+ try:
77
+ append_event(root, action, hook_input)
78
+ except (OSError, RuntimeError, subprocess.TimeoutExpired) as exc:
79
+ print(f"[subagent_tracker] lifecycle event unavailable: {exc}", file=sys.stderr)
105
80
 
106
81
  print(json.dumps({}))
107
82
 
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const runtime = fs.existsSync(path.join(__dirname, 'index.cjs'))
8
+ ? require('./index.cjs')
9
+ : require('./server.cjs');
10
+
11
+ function main() {
12
+ const rootDir = path.resolve(process.argv[2] || process.cwd());
13
+ const action = process.argv[3];
14
+ let hookInput = {};
15
+ const raw = fs.readFileSync(0, 'utf8');
16
+ if (raw.trim()) {
17
+ hookInput = JSON.parse(raw);
18
+ if (!hookInput || typeof hookInput !== 'object' || Array.isArray(hookInput)) {
19
+ throw new Error('hook input must be a JSON object');
20
+ }
21
+ }
22
+ const result = runtime.appendHookLifecycleEvent({ rootDir, action, hookInput });
23
+ process.stdout.write(`${JSON.stringify(result)}\n`);
24
+ }
25
+
26
+ if (require.main === module) {
27
+ try { main(); }
28
+ catch (error) {
29
+ process.stderr.write(`hook lifecycle event failed: ${error.message}\n`);
30
+ process.exit(1);
31
+ }
32
+ }
33
+
34
+ module.exports = { main };
@@ -31,6 +31,9 @@ function assertIntent(intent, rootDir) {
31
31
  || intent.schema_version !== INTENT_VERSION
32
32
  || typeof intent.change_id !== 'string' || !intent.change_id
33
33
  || !Array.isArray(intent.baseline_updates)
34
+ || typeof intent.reconciliation_path !== 'string' || !intent.reconciliation_path
35
+ || !/^[0-9a-f]{64}$/.test(String(intent.reconciliation_digest || ''))
36
+ || !intent.reconciliation_manifest || typeof intent.reconciliation_manifest !== 'object'
34
37
  || typeof intent.summary !== 'string' || intent.summary.trim().length < 3) {
35
38
  throw new ArchiveJournalError('ARCHIVE_INTENT_INVALID', 'archive intent shape is invalid');
36
39
  }
@@ -46,6 +49,7 @@ function assertIntent(intent, rootDir) {
46
49
  throw new ArchiveJournalError('ARCHIVE_INTENT_INVALID', 'archive intent destination is not the change archive root');
47
50
  }
48
51
  for (const update of intent.baseline_updates) resolveInside(rootDir, update, 'baseline update');
52
+ resolveInside(rootDir, intent.reconciliation_path, 'reconciliation path');
49
53
  if (intent.previous_summary !== null && typeof intent.previous_summary !== 'string') {
50
54
  throw new ArchiveJournalError('ARCHIVE_INTENT_INVALID', 'previous_summary must be string or null');
51
55
  }
@@ -77,16 +81,21 @@ function renderSummary(change, summary, baselineUpdates, noBaselineChangeReason)
77
81
  ].join('\n');
78
82
  }
79
83
 
80
- function sameOperation(intent, { change, summary, baselineUpdates, noBaselineChangeReason }) {
84
+ function sameOperation(intent, {
85
+ change, summary, baselineUpdates, noBaselineChangeReason, reconciliationPath, reconciliationDigest,
86
+ }) {
81
87
  return intent.change_id === change.id
82
88
  && intent.source === change.artifact_root
83
89
  && intent.summary === summary.trim()
84
90
  && JSON.stringify(intent.baseline_updates) === JSON.stringify(baselineUpdates)
85
- && (intent.no_baseline_change_reason || null) === (noBaselineChangeReason || null);
91
+ && (intent.no_baseline_change_reason || null) === (noBaselineChangeReason || null)
92
+ && intent.reconciliation_path === reconciliationPath
93
+ && intent.reconciliation_digest === reconciliationDigest;
86
94
  }
87
95
 
88
96
  function prepareArchiveMove({
89
- rootDir, change, summary, baselineUpdates, noBaselineChangeReason, now = new Date(),
97
+ rootDir, change, summary, baselineUpdates, noBaselineChangeReason,
98
+ reconciliationPath, reconciliationDigest, reconciliationManifest, now = new Date(),
90
99
  }) {
91
100
  const date = now.toISOString().slice(0, 10);
92
101
  const source = resolveInside(rootDir, change.artifact_root, 'source');
@@ -98,7 +107,7 @@ function prepareArchiveMove({
98
107
  if (!fs.existsSync(source) && fs.existsSync(destinationIntentFile)) {
99
108
  const resumed = readIntentFile(destinationIntentFile, rootDir);
100
109
  if (!sameOperation(resumed.intent, {
101
- change, summary, baselineUpdates, noBaselineChangeReason,
110
+ change, summary, baselineUpdates, noBaselineChangeReason, reconciliationPath, reconciliationDigest,
102
111
  })) {
103
112
  throw new ArchiveJournalError('ARCHIVE_INTENT_CONFLICT', 'existing archive intent differs from retry input');
104
113
  }
@@ -115,7 +124,7 @@ function prepareArchiveMove({
115
124
  if (fs.existsSync(sourceIntentFile)) {
116
125
  prepared = readIntentFile(sourceIntentFile, rootDir);
117
126
  if (!sameOperation(prepared.intent, {
118
- change, summary, baselineUpdates, noBaselineChangeReason,
127
+ change, summary, baselineUpdates, noBaselineChangeReason, reconciliationPath, reconciliationDigest,
119
128
  })) {
120
129
  throw new ArchiveJournalError('ARCHIVE_INTENT_CONFLICT', 'prepared archive intent differs from retry input');
121
130
  }
@@ -130,6 +139,9 @@ function prepareArchiveMove({
130
139
  summary: summary.trim(),
131
140
  baseline_updates: baselineUpdates,
132
141
  no_baseline_change_reason: noBaselineChangeReason || null,
142
+ reconciliation_path: reconciliationPath,
143
+ reconciliation_digest: reconciliationDigest,
144
+ reconciliation_manifest: reconciliationManifest,
133
145
  previous_summary: previousSummary,
134
146
  created_at: now.toISOString(),
135
147
  };