ultra-builder-pro-cli 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.ultra-template/docs/research/README.md +26 -5
  2. package/.ultra-template/specs/discovery.md +1 -1
  3. package/.ultra-template/tasks/tasks.json +1 -1
  4. package/CHANGELOG.md +66 -0
  5. package/README.md +54 -24
  6. package/adapters/_shared/codex-assets.cjs +69 -11
  7. package/adapters/_shared/interaction-contract.cjs +36 -3
  8. package/adapters/_shared/runtime-assets.cjs +41 -1
  9. package/adapters/claude.js +7 -5
  10. package/adapters/codex.js +1 -1
  11. package/adapters/kimi.js +2 -8
  12. package/adapters/opencode.js +30 -28
  13. package/bin/orchestrator.js +64 -24
  14. package/docs/AGENT-CONTEXT.md +74 -43
  15. package/docs/ARCHITECTURE.md +63 -31
  16. package/docs/ARTIFACT-AUTHORITY.md +234 -0
  17. package/docs/DECISIONS.md +10 -4
  18. package/docs/LEGACY-CLI-CROSSWALK.md +6 -4
  19. package/docs/PLUGIN-ISOLATION-CONTRACT.md +4 -3
  20. package/docs/ROADMAP.md +19 -5
  21. package/docs/RUNTIME-COMPAT-MATRIX.md +24 -13
  22. package/docs/STATE-DB-ACCESS-POLICY.md +11 -9
  23. package/docs/WORKFLOW-LIFECYCLE.md +70 -39
  24. package/hooks/README.md +6 -5
  25. package/hooks/active_task_context.py +97 -12
  26. package/hooks/context_spine.py +16 -4
  27. package/hooks/health_check.py +5 -4
  28. package/hooks/pre_stop_check.py +5 -2
  29. package/hooks/runtime_paths.py +396 -0
  30. package/hooks/subagent_tracker.py +5 -2
  31. package/hooks/workflow_checkpoint.py +16 -3
  32. package/hooks/workflow_context.py +11 -3
  33. package/hooks/workflow_resume.py +7 -4
  34. package/mcp-server/breadcrumb.cjs +20 -2
  35. package/mcp-server/lib/archive-journal.cjs +1026 -111
  36. package/mcp-server/lib/archive-mutation-worker.py +365 -0
  37. package/mcp-server/lib/artifact-registry.cjs +1126 -0
  38. package/mcp-server/lib/baseline-workflow.cjs +9 -4
  39. package/mcp-server/lib/change-packet.cjs +818 -0
  40. package/mcp-server/lib/change-workflow.cjs +932 -176
  41. package/mcp-server/lib/context-paths.cjs +111 -0
  42. package/mcp-server/lib/context-spine.cjs +511 -45
  43. package/mcp-server/lib/decision-dialogue.cjs +345 -3
  44. package/mcp-server/lib/delivery-transaction.cjs +471 -0
  45. package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
  46. package/mcp-server/lib/doctor.cjs +373 -11
  47. package/mcp-server/lib/git-bootstrap.cjs +459 -47
  48. package/mcp-server/lib/init-project.cjs +190 -95
  49. package/mcp-server/lib/plan-publication-worker.cjs +830 -0
  50. package/mcp-server/lib/plan-store.cjs +700 -5
  51. package/mcp-server/lib/project-breadcrumb.cjs +23 -4
  52. package/mcp-server/lib/projector.cjs +174 -40
  53. package/mcp-server/lib/runtime-paths.cjs +1687 -0
  54. package/mcp-server/lib/safe-project-file.cjs +385 -0
  55. package/mcp-server/lib/spec-learning.cjs +28 -12
  56. package/mcp-server/lib/state-db.cjs +263 -9
  57. package/mcp-server/lib/state-ops.cjs +248 -49
  58. package/mcp-server/lib/telemetry.cjs +12 -3
  59. package/mcp-server/lib/workflow-state.cjs +610 -58
  60. package/mcp-server/server.cjs +278 -33
  61. package/orchestrator/daemon.cjs +2 -10
  62. package/orchestrator/parallel-orchestrator.cjs +314 -50
  63. package/orchestrator/planner/plan-builder.cjs +1 -1
  64. package/orchestrator/recovery.cjs +125 -7
  65. package/orchestrator/session-close-journal-worker.cjs +5 -0
  66. package/orchestrator/session-close-journal.cjs +1014 -0
  67. package/orchestrator/session-runner.cjs +679 -69
  68. package/orchestrator/worktree-manager.cjs +186 -35
  69. package/package.json +2 -3
  70. package/skills/cc-collab/SKILL.md +4 -1
  71. package/skills/codex-collab/SKILL.md +5 -2
  72. package/skills/ultra-change/SKILL.md +26 -8
  73. package/skills/ultra-deliver/SKILL.md +23 -11
  74. package/skills/ultra-dev/SKILL.md +33 -6
  75. package/skills/ultra-doctor/SKILL.md +6 -2
  76. package/skills/ultra-init/SKILL.md +4 -2
  77. package/skills/ultra-plan/SKILL.md +49 -16
  78. package/skills/ultra-research/SKILL.md +30 -27
  79. package/skills/ultra-review/SKILL.md +9 -5
  80. package/skills/ultra-status/SKILL.md +9 -4
  81. package/skills/ultra-test/SKILL.md +10 -5
  82. package/skills/ultra-think/SKILL.md +6 -2
  83. package/skills/ultra-think/references/decision-dialogue.md +17 -6
  84. package/skills/ultra-verify/SKILL.md +4 -1
  85. package/spec/cli-protocol.md +18 -7
  86. package/spec/mcp-tools.yaml +502 -44
  87. package/spec/schemas/change-delta.v1.schema.json +153 -0
  88. package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
  89. package/spec/schemas/mcp-tools.schema.json +1 -1
  90. package/spec/schemas/state-db.sql +50 -13
  91. package/spec/schemas/tasks.v4.5.schema.json +1 -1
  92. package/ultra-tools/cli.cjs +3 -2
  93. package/ultra-tools/commands/db.cjs +76 -25
  94. package/ultra-tools/commands/migrate.cjs +995 -80
  95. package/ultra-tools/commands/session.cjs +34 -8
  96. package/ultra-tools/commands/status.cjs +32 -6
  97. package/ultra-tools/commands/system.cjs +129 -22
  98. package/commands/learn.md +0 -10
  99. package/orchestrator/skill-miner.cjs +0 -133
  100. package/output-styles/coding-vibes.md +0 -67
  101. package/output-styles/structural-thinking.md +0 -61
  102. package/skills/learn/SKILL.md +0 -58
@@ -14,29 +14,29 @@ not installed.
14
14
  | Capability | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
15
15
  |---|---|---|---|---|
16
16
  | Package form | `.claude-plugin` native plugin | native config bundle + JS plugin | personal `.codex-plugin` | managed `kimi.plugin.json` plugin |
17
- | Public entry | `/ultra-*`, `/learn` | `/ultra-*`, `/learn` | `$ultra-builder-pro:<skill>` | `/ultra-builder-pro:<command>` |
18
- | Public Ultra workflows | 12, model invocation disabled | 12 commands backed by private workflow assets | 12, implicit invocation disabled | 12, model invocation disabled |
17
+ | Public entry | `/ultra-builder-pro:ultra-*` | `/ultra-*` | `$ultra-builder-pro:ultra-*` | `/ultra-builder-pro:ultra-*` |
18
+ | Public Ultra capabilities | 11, model invocation disabled | 11 commands backed by private workflow assets | 11, implicit invocation disabled | 11, model invocation disabled |
19
19
  | Internal agent-rule skills | 4, non-user-facing | 4, non-user-facing | 4 with implicit invocation disabled | 4, consumed by review workers |
20
20
  | Collaboration companions | `codex-collab`, `ultra-verify` | `cc-collab`, `codex-collab`, `ultra-verify` | `cc-collab`, `ultra-verify` | `cc-collab`, `codex-collab`, `ultra-verify` |
21
21
  | Automatic workflow bootstrap | N/A | N/A | N/A | N/A |
22
22
  | External browser/deploy/framework skills | N/A | N/A | N/A | N/A |
23
23
 
24
- Codex converts workflows into namespaced skills and records eleven legacy
24
+ Codex converts workflows into namespaced skills and records ten compatibility
25
25
  command mappings in `command-map.json`; `ultra-review` remains directly
26
- invocable as a skill. Kimi registers all twelve commands through its native
26
+ invocable as a skill. Kimi registers all eleven commands through its native
27
27
  plugin command directory and exposes the allowlisted skills independently.
28
28
 
29
29
  ## 2. Agents and collaboration
30
30
 
31
31
  | Capability | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
32
32
  |---|---|---|---|---|
33
- | Bundled review/debug workers | FULL, native Markdown agents | FULL, native agent frontmatter | FULL, nine managed TOML agents | FUNCTIONAL, nine prompt templates consumed by `Agent` / `AgentSwarm` |
33
+ | Bundled review/debug workers | FULL, native Markdown agents | FULL, native agent frontmatter | FULL, 10 managed TOML agents | FUNCTIONAL, 10 prompt templates consumed by `Agent` / `AgentSwarm` |
34
34
  | Plugin custom-agent registration | FULL | FULL | FULL | N/A; the 0.26 manifest has no custom-agent field |
35
35
  | Native bounded delegation | native agent surface | native agent surface | native Codex subagents | native `Agent` / `AgentSwarm` |
36
36
  | Cross-host advisor | explicit and read-only | explicit and read-only | explicit and read-only | explicit and read-only |
37
37
  | Primary-agent ownership | FULL | FULL | FULL | FULL |
38
38
 
39
- No bundled worker owns private persistent memory. It receives current-checkout
39
+ No bundled worker owns private conversational memory. It receives current-checkout
40
40
  evidence and bounded context from the primary host, then returns a result for
41
41
  primary verification. On Kimi, the review workflow reads the bundled worker
42
42
  template before creating the corresponding `AgentSwarm` item; the templates are
@@ -54,7 +54,7 @@ not presented as nonexistent custom agents.
54
54
  | Subagent lifecycle evidence | FULL | DEGRADED, no equivalent packaged event | FULL | FULL, native `SubagentStart` / `SubagentStop` |
55
55
 
56
56
  Health/context, compact, stop, and subagent hooks remain silent unless
57
- `.ultra/state.db` proves an active, blocked, or ready workflow. Projection
57
+ `.ultra/.runtime/state.db` proves an active, blocked, or ready workflow. Projection
58
58
  protection applies to every initialized project because generated projections
59
59
  must never become a second authority. No host receives prompt capture, transcript capture,
60
60
  observation journaling, session-summary memory, generic command blocking, or
@@ -67,16 +67,18 @@ they do not deny edits or stop active incident work.
67
67
  | Capability | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
68
68
  |---|---|---|---|---|
69
69
  | stdio MCP registration | plugin `.mcp.json` | `opencode.json` local MCP entry | plugin `.mcp.json` | plugin `mcpServers` entry |
70
- | Live/declared contracts | 51 | 51 | 51 | 51 |
70
+ | Live/declared contracts | 57 | 57 | 57 | 57 |
71
71
  | 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 |
72
72
  | Greenfield/brownfield baseline adoption | FULL | FULL | FULL | FULL |
73
73
  | Context Manifest v3 / breadcrumb | FULL | FULL, same bundled read-only DB reader as every hook | FULL | FULL |
74
74
  | Approval-gated spec learning | FULL | FULL | FULL | FULL |
75
75
  | Host-native review/discovery/ask | native | native | documented in `codex-capability-map.json` | native Kimi tools and workers |
76
- | Durable authority | project `.ultra/state.db` | project `.ultra/state.db` | project `.ultra/state.db` | project `.ultra/state.db` |
77
- | Ultra memory API | N/A | N/A | N/A | N/A |
76
+ | Workflow-memory envelope | project `.ultra/` | project `.ultra/` | project `.ultra/` | project `.ultra/` |
77
+ | Lifecycle/index authority | project `.ultra/.runtime/state.db` | project `.ultra/.runtime/state.db` | project `.ultra/.runtime/state.db` | project `.ultra/.runtime/state.db` |
78
+ | General memory-provider API | N/A | N/A | N/A | N/A |
78
79
 
79
- All 51 `task.*`, `session.*`, `baseline.*`, `change.*`, `decision.*`, `workflow.*`, `system.*`, and `plan.*` operations
80
+ All 57 `task.*`, `session.*`, `baseline.*`, `change.*`, `decision.*`,
81
+ `workflow.*`, `artifact.*`, `system.*`, and `plan.*` operations
80
82
  registered by `mcp-server/server.cjs` are live. Review, impact discovery, skill
81
83
  loading, and user interaction remain host-native surfaces.
82
84
 
@@ -91,6 +93,15 @@ generated launcher recovers the project from the inherited `PWD`, sets
91
93
  cannot be established. On POSIX it intentionally uses `env node` because Kimi
92
94
  0.26/0.27's embedded Node ABI differs from the ABI used to install `better-sqlite3`.
93
95
 
96
+ Archive finalization and archive-journal recovery use an adjacent, explicitly
97
+ packaged Python 3 worker. The worker accepts only bounded, basename-only
98
+ operations and performs mutations relative to inherited, identity-checked
99
+ directory descriptors. This boundary requires a POSIX Python runtime with
100
+ `dir_fd`, no-follow `stat`, and `O_NOFOLLOW` support. If that prerequisite is
101
+ missing, archive operations fail closed with `ARCHIVE_RUNTIME_UNAVAILABLE`;
102
+ Ultra never falls back to ancestor-sensitive pathname mutation. Other MCP
103
+ operations remain available.
104
+
94
105
  ## 5. User instruction isolation
95
106
 
96
107
  | Capability | Claude Code | OpenCode | Codex | Kimi Code 0.26+ |
@@ -135,9 +146,9 @@ records. Uninstall refuses an unmanaged or conflicting root.
135
146
 
136
147
  | Capability | Contract |
137
148
  |---|---|
138
- | Current state schema | `19.0` |
149
+ | Current state schema | `20.0` |
139
150
  | Runtime values | `claude`, `opencode`, `codex`, `kimi` |
140
- | Upgrade from earlier schema | preserves runtime rows, adds Context Spine state through 10.0, authoritative baseline adoption in 11.0, repository evidence in 12.0, durable workflows in 13.0, continuous baseline revalidation in 14.0, typed research semantics and verified reconciliation in 15.0, resumable owner-agent decision threads and workflow alignment gates in 16.0, explicit unborn-Git authority in 17.0, adaptive transitions and legacy workflow normalization in 18.0, then non-ceremonial decision completion in 19.0 |
151
+ | Upgrade from earlier schema | preserves runtime rows, adds Context Spine state through 10.0, authoritative baseline adoption in 11.0, repository evidence in 12.0, durable workflows in 13.0, continuous baseline revalidation in 14.0, typed research semantics and verified reconciliation in 15.0, resumable owner-agent decision threads and workflow alignment gates in 16.0, explicit unborn-Git authority in 17.0, adaptive transitions and legacy workflow normalization in 18.0, non-ceremonial decision completion in 19.0, then typed artifact ownership and normalized dependency edges in 20.0 |
141
152
  | Preservation gate | rows, IDs, indexes, foreign keys, telemetry, incidents, and migration history remain intact |
142
153
  | Failure behavior | rollback; no partially upgraded authority database |
143
154
 
@@ -1,12 +1,13 @@
1
- # `.ultra/state.db` access policy
1
+ # `.ultra/.runtime/state.db` access policy
2
2
 
3
3
  > Multi-process access contract. Current authority is defined by
4
4
  > [`DECISIONS.md`](./DECISIONS.md) and the live database schema.
5
5
 
6
- `.ultra/state.db` is the only authoritative state store for baselines, changes,
7
- decision threads/items, tasks, workflow runs/steps, events, sessions, incidents,
8
- projections, telemetry, and spec references
9
- (D18, D52, D54). Every process
6
+ `.ultra/.runtime/state.db` is the only lifecycle, index, transition, freshness, and
7
+ coordination authority for baselines, changes, decision threads/items, tasks,
8
+ workflow runs/steps, events, sessions, incidents, projections, telemetry, and spec
9
+ references (D18, D52, D54). Registered digest-bound files carry semantic and evidence
10
+ bodies; the DB owns their references and current digests. Every process
10
11
  that touches it must follow the rules below; deviations are bugs.
11
12
 
12
13
  ## 1. Three-role write matrix
@@ -64,7 +65,7 @@ WAL is part of the authority contract, not an optional performance setting.
64
65
  `openStateDb` asks SQLite to enable WAL and checks the actual returned mode. If
65
66
  the storage layer returns anything else, startup fails with
66
67
  `STATE_DB_WAL_UNAVAILABLE`. Ultra does not silently downgrade to a different
67
- concurrency model. Move `.ultra/state.db` to storage that supports SQLite WAL or
68
+ concurrency model. Move `.ultra/.runtime/state.db` to storage that supports SQLite WAL or
68
69
  resolve the mount/runtime constraint before retrying.
69
70
 
70
71
  ## 4. Where each table is owned
@@ -74,9 +75,10 @@ resolve the mount/runtime constraint before retrying.
74
75
  | `baselines` | MCP server (`baseline.start` / `baseline.record` / `baseline.converge`); initialization and legacy-projection migration may create only the first draft or compatibility row |
75
76
  | `tasks` | MCP server (`task.create` / `task.update` / `task.delete`) |
76
77
  | `changes` | MCP server (`change.create` / `change.update` / `change.converge` / `change.archive`) |
77
- | `decision_threads`, `decision_items` | MCP server (`decision.thread_start` / `decision.open` / `decision.resolve` / `decision.delegate` / `decision.defer` / `decision.supersede` / `decision.complete` / `decision.checkpoint`); only pending questions needed for recovery, normalized choices, lifecycle completion, and optional artifact checkpoints are stored, never prompts or transcripts |
78
+ | `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 |
78
79
  | `workflow_runs`, `workflow_steps` | MCP server (`workflow.start` / `workflow.step` / `workflow.complete`); skills provide evidence inputs but never write rows directly |
79
- | `artifacts`, `context_snapshots`, `spec_learning_candidates`, `trace_links` | MCP server through change lifecycle tools |
80
+ | `artifacts`, `artifact_edges` | MCP server (`artifact.record`); workflow-owned registration may use the same internal registry transaction |
81
+ | `context_snapshots`, `spec_learning_candidates`, `trace_links` | MCP server through change lifecycle tools |
80
82
  | `incidents`, `projection_jobs`, `event_consumers`, `circuit_breaker` | MCP server; backup-first doctor recovery may perform only documented mechanical transitions |
81
83
  | `events` | MCP server for lifecycle events; approved processes and `task.append_event` for allowlisted append-only observations |
82
84
  | `sessions` | MCP server (`session.spawn` / `session.close` / `session.heartbeat`); orchestrator may write status transitions |
@@ -87,7 +89,7 @@ resolve the mount/runtime constraint before retrying.
87
89
 
88
90
  ## 5. Forbidden patterns
89
91
 
90
- - **No file copies for state.** Don't `cp .ultra/state.db
92
+ - **No file copies for state.** Don't `cp .ultra/.runtime/state.db
91
93
  somewhere/state.db` and edit; use `ultra-tools db backup`.
92
94
  - **No long-held writer connections** outside the MCP server. Pop a
93
95
  short transaction, finish, close.
@@ -11,11 +11,16 @@ 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, 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 |
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 |
15
+ | `.ultra/.runtime/state.db` | 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
+ | Digest-bound artifacts | Registered specification, research, Change, context, test, review, delivery, and verification bodies | Independent lifecycle state or content whose digest no longer matches |
17
+ | Generated projections and scratch | Human-readable views and temporary working material | Lifecycle, semantic, or evidence authority before supported promotion |
17
18
  | 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
 
20
+ Together, `.ultra/` is project-local cross-session workflow memory. The detailed
21
+ artifact classes and promotion rules live in
22
+ [`ARTIFACT-AUTHORITY.md`](./ARTIFACT-AUTHORITY.md).
23
+
19
24
  Prompt input can supply facts, content paths, reasons, owner decisions, and
20
25
  evidence references. MCP reads the current repository and DB, validates those
21
26
  inputs, hashes outputs, and derives status. Prompt input cannot override a task's
@@ -71,7 +76,7 @@ signals. In a monorepo, the owner selects the repository scope; baseline
71
76
  worktree digests and accepted dirty files are restricted to that scope.
72
77
 
73
78
  Classification and readiness are separate. Initialization immediately creates
74
- or resumes `.ultra/state.db`, installs only the required scaffold, records the
79
+ or resumes `.ultra/.runtime/state.db`, installs only the required scaffold, records the
75
80
  classification, and completes an `init` workflow after read-back verification. It
76
81
  does not create a research run. A later explicit `ultra-research` invocation selects
77
82
  `full` for greenfield or `adoption` for brownfield, records coverage dispositions, and
@@ -144,11 +149,12 @@ traceability, slice kind, public seam, exact verification command, acceptance
144
149
  checks, context references, resolved documentation impact, owner, and valid
145
150
  dependencies.
146
151
 
147
- A semantic Change update marks all derived tasks stale. Clearing that marker requires
148
- one complete execution-contract rebind through `task.update`; a marker-only clear is
149
- rejected. MCP validates the resulting contract and appends
150
- `task_contract_reconciled` with the current Change authority digest before a new plan
151
- may converge.
152
+ A semantic Change update traverses registered provenance and consumer edges and marks
153
+ only reachable task, workflow, and artifact consumers stale. Unrelated tasks remain
154
+ current. Clearing a task marker requires one complete execution-contract rebind through
155
+ `task.update`; a marker-only clear is rejected. MCP validates the resulting contract
156
+ and appends `task_contract_reconciled` with the current Change authority digest before
157
+ a new plan may converge.
152
158
 
153
159
  A session process and a task have different status machines. Process exit zero closes
154
160
  the execution transport but leaves the task `in_progress` until development,
@@ -221,14 +227,14 @@ stores the conversation transcript.
221
227
  | Stage | Authoritative writes | Completion verification | Available continuation |
222
228
  |---|---|---|---|
223
229
  | `init` | Baseline classification/scope, Git bootstrap state, completed init run, initialization event, projection job | DB/schema opens, scaffold/projection and read-back succeed; existing Git is preserved or missing local Git is initialized | Explicitly chosen `research`, `status`, or `doctor`; an already-ready project may also enter `change` |
224
- | `research` | Accepted coverage dispositions, selected step evidence, normalized material decisions, optional artifact checkpoints, typed semantic records with source digests, immutable reports, output paths and SHA-256 digests | Every included coverage area is dispositioned; only executed/verified/reused areas require work evidence; synthesis binds current specifications and distillate; all material decisions and digests are current | Baseline record/convergence for initial research; bounded change research returns to its owning change |
230
+ | `research` | Accepted coverage dispositions, selected step evidence, normalized material decisions, optional artifact checkpoints, typed semantic records with source digests, immutable reports, output paths and SHA-256 digests | Every included coverage area is dispositioned; only executed/verified/reused areas require work evidence; baseline synthesis binds current specifications and distillate, while Change synthesis stays in its overlay; all material decisions and digests are current | Baseline record/convergence for initial research; bounded Change research records its delta and returns to plan |
225
231
  | 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 |
226
- | `change` | Complete Change Contract, profile/risk rationale, research disposition, intent/delta artifacts, and a completed capture run; a decision thread only when material intent remains unresolved | Intent, acceptance, recovery, classification, and research disposition are complete and bound to the baseline | Model recommends; owner selects or delegates bounded `research`, `plan`, current `dev`, `think`, or `status` from allowed transitions |
227
- | `plan` | Accepted planning posture, selected planning evidence, complete change-owned task rows, acceptance coverage matrix, and change-bound `.ultra/execution-plan.json`; approval only for a material owner decision | Every Change acceptance id is owned by a task; trace targets, ownership, dependencies, topology, task contracts, and exported digest match current authority | Any dependency-ready task may enter `dev` |
232
+ | `change` | Complete Change Contract, profile/risk rationale, research disposition, isolated Change root, typed delta, and a completed capture run; a decision thread only when material intent remains unresolved | Intent, acceptance, recovery, classification, research disposition, and exact baseline anchors are current | Model recommends; owner selects bounded `research` or direct `plan`; both routes converge on plan before development |
233
+ | `plan` | Accepted planning posture, immutable `plan/planning` context, selected planning evidence, complete change-owned task rows, acceptance coverage matrix, and deterministic `<artifact_root>/plan.json` plus `plan.md`; approval only for a material owner decision | Every Change acceptance id is owned by a task; trace targets, ownership, dependencies, topology, task contracts, planning-context binding, and exported digests match current authority | Any dependency-ready task may enter `dev` |
228
234
  | `dev` | Task/session transitions, real worktree, step evidence, immutable implementation context, completion/review references | Task completed; no live session; task contract unchanged; starting context and review artifacts valid; completion commits are integrated and remain ancestors of current HEAD | Next task, then aggregate `test` |
229
- | `test` | Immutable checking context, an explicit risk-selected verification profile, and `.ultra/reports/tests/<workflow-id>.json` | Selected dimensions and acceptance pass; every excluded dimension has a rationale; report change/task set, HEAD, worktree, context, commands, blockers, and digests agree | `review` when passing, otherwise resume `dev`/`test` |
230
- | `review` | Immutable review context, `task`/`change`/`plan` mode, risk-selected worker provenance, two mandatory specialist axes, and coordinated summary | Current diff/HEAD and context match; the complete worker roster is selected or skipped with rationale; completed workers match specialist artifacts; both axes complete; findings and axis verdicts derive the final verdict | `deliver` only from a passing `change` review, otherwise resume implementation or checking |
231
- | `deliver` | Convergence context, verified learning resolutions, semantic reconciliation manifest, local change archive, and immutable delivery report | Tasks/test/change-review are current; baseline semantic updates are anchored; archive and baseline transaction agree; the report contains no release action | Report local completion or start the next `change`; publish/deploy/push are separate explicitly authorized operations |
235
+ | `test` | Immutable checking context, an explicit risk-selected verification profile, and `<artifact_root>/test/<workflow-id>/report.json` | Selected dimensions and exact Change acceptance ids pass; every excluded dimension has a rationale; report change/task set, HEAD, worktree, context, commands, blockers, and digests agree | `review` when passing, otherwise resume `dev`/`test` |
236
+ | `review` | Immutable review context plus risk-selected specialist and summary artifacts below `<artifact_root>/review/<workflow-id>/` | Current diff/HEAD and context match; the complete worker roster is selected or skipped with rationale; completed workers match specialist artifacts; both axes complete; findings and axis verdicts derive the final verdict | `deliver` only from a passing `change` review, otherwise resume implementation or checking |
237
+ | `deliver` | Convergence context, typed delta, documentation reconciliation, recoverable overlay transaction, self-contained archive, and immutable delivery report | Tasks/test/change-review and acceptance are current; every before/after digest and baseline anchor is current; the archive transaction is complete; the report contains no release action | Report local completion or start the next `change`; publish/deploy/push are separate explicitly authorized operations |
232
238
  | `status` | None | Reads doctor, baseline, workflows, breadcrumb, tasks, sessions, Git, and DB-referenced reports | Shows allowed transitions, any hard required transition, and a host-owned recommendation |
233
239
  | `doctor` | None by default; explicit repair writes only mechanical recovery state | Re-runs integrity, schema, workflow-output, projection, session, incident, archive, and install checks | Mechanically required recovery when unique; otherwise a set of safe recovery capabilities |
234
240
 
@@ -256,26 +262,31 @@ thread only for a new material decision cluster. Before asking, it must:
256
262
  6. normalize the owner response with `decision.resolve`, explicit reversible
257
263
  delegation with `decision.delegate`, or a consequence-bearing deferral with
258
264
  `decision.defer`;
259
- 7. call `decision.complete` when normalized state is settled, or prepare a compact
265
+ 7. apply accepted intent through the owning MCP operation or digest-bound artifact;
266
+ 8. read the resulting authority back and record typed `applied_refs` when another
267
+ authority changed;
268
+ 9. call `decision.complete` when normalized state is settled, or prepare a compact
260
269
  checkpoint only when a material decision cluster changes a durable contract and
261
270
  interruption recovery needs an artifact-bound boundary.
262
271
 
263
- The shared semantic flow is `inspect -> suggest -> ask if unresolved -> normalize ->
264
- persist`. Claude Code and Kimi Code use `AskUserQuestion`, Codex uses
272
+ The shared semantic flow is `inspect -> suggest -> host-native ask -> normalize ->
273
+ persist -> apply -> read back`. The ask is skipped only when current explicit intent
274
+ already resolves the material choice. Claude Code and Kimi Code use `AskUserQuestion`, Codex uses
265
275
  `request_user_input` when its current mode exposes it, and OpenCode uses `question`
266
276
  when permitted. Every host falls back to one concise direct question when normal
267
277
  conversation is still allowed. If the current host mode forbids interaction entirely,
268
278
  the choice remains unanswered and route-dependent writes stop. A dismissed question
269
279
  also remains unanswered. The DB trusts a normalized persisted result as current
270
280
  authority across sessions; it does not store or verify a UI receipt, raw prompt, or
271
- transcript.
281
+ transcript. Breadcrumb and decision reads recall completed normalized intent, so a new
282
+ host session can continue without replaying the dialogue.
272
283
 
273
284
  `ultra-think` owns this interaction protocol. `ultra-research`, `ultra-change`, and
274
285
  `ultra-plan` invoke it at their decision boundaries. `ultra-dev`, `ultra-test`,
275
286
  `ultra-review`, and `ultra-deliver` stop and route back to the same thread when new
276
287
  owner authority is required; they never answer on the owner's behalf. `ultra-status`
277
- shows only the current decision and transition set, while `ultra-doctor` diagnoses
278
- stale checkpoint artifacts and recovery state.
288
+ shows current unresolved decisions, relevant accepted intent, and the transition set,
289
+ while `ultra-doctor` diagnoses stale checkpoint artifacts and recovery state.
279
290
 
280
291
  When only a bound artifact changes, the same resolved thread is prepared and approved
281
292
  again against the current digest; this is checkpoint renewal, not a fabricated decision
@@ -289,37 +300,54 @@ opens one replacement.
289
300
  - the bound baseline/change/task identity;
290
301
  - a role and lifecycle gate;
291
302
  - DB-backed task outcome, public seam, verification command, and required refs;
303
+ - the selected task's bounded dependency/integration neighborhood rather than every
304
+ unrelated task;
305
+ - the task-context contract: purpose, why, constraints, non-goals, target seams/files,
306
+ pattern refs, acceptance, documentation impact, recovery, and drift definition;
292
307
  - local file hashes, current HEAD, branch, and worktree digest;
293
308
  - optional metadata-only external memory or graph references;
294
- - advisory file/token/context-share budgets;
309
+ - advisory file/token/context-share budgets, including inline Change/task authority;
295
310
  - the current Change and task authority digests;
296
311
  - readiness blockers, `allowed_transitions`, and a `required_transition` only for a
297
312
  unique hard-recovery path.
298
313
 
314
+ Compilation is pure with respect to accepted Change authority. Provider references are
315
+ updated only through `change.update`; they cannot be smuggled into `change.context`.
316
+ Context references retain `expected_digest`, `anchor`, `scope`, and
317
+ `freshness_policy`. `digest` requires byte-current content, `existence` allows an
318
+ implementation target to change while remaining present, and `advisory` reports drift
319
+ without blocking.
320
+
299
321
  Critical stage workflows bind a matching snapshot as a durable output:
300
322
 
301
323
  | Workflow | Required role | Required gate |
302
324
  |---|---|---|
325
+ | `plan` | `plan` | `planning` |
303
326
  | `dev` | `implement` | `implementation` |
304
327
  | `test` | `check` | `verification` |
305
328
  | `review` | `review` | `review` |
306
329
  | `deliver` | `check` | `convergence` |
307
330
 
308
- The snapshot path and digest are then carried into test, review, and delivery
309
- reports. This prevents a later Prompt from claiming evidence gathered under a
310
- different context. File and token budgets warn; they do not block legitimate
311
- work. Missing authority, required refs, task contracts, outputs, required owner
312
- decisions, or
313
- current gate evidence do block.
331
+ Every lookup is exact on `change_id + task_id + role + gate`. The snapshot path and
332
+ digest are carried into plan, test, review, and delivery artifacts. `plan.export`
333
+ writes only the owning Change's `plan.json` and deterministic `plan.md`, and binds the
334
+ exact planning snapshot and digest to both the artifact registry and `verify-plan`.
335
+ The legacy global `.ultra/execution-plan.json` is read-only migration compatibility,
336
+ not current authority. These rules prevent a later Prompt or a different role packet
337
+ from claiming evidence gathered under another context. Referenced file content remains
338
+ lazy; only metadata and bounded task authority are inline. File and token budgets warn;
339
+ they do not block legitimate work. Missing authority, required refs, stale
340
+ digest-bound refs, task contracts, outputs, required owner decisions, or current gate
341
+ evidence do block.
314
342
 
315
343
  ## 7. Invalidation rules
316
344
 
317
345
  - Changing a task contract invalidates contexts and gates that depended on the
318
346
  former contract.
319
347
  - Changing accepted Change intent, contract, classification, research disposition,
320
- provider evidence, or documentation impact marks all derived tasks stale and
321
- invalidates compiled contexts. Planning or task reconciliation must explicitly
322
- clear staleness before execution.
348
+ provider evidence, or documentation impact invalidates only transitive registered
349
+ consumers. Planning or task reconciliation must explicitly refresh affected
350
+ authority before execution.
323
351
  - Changing required source/spec content invalidates the recorded output or
324
352
  context digest.
325
353
  - Changing a research semantic source invalidates that workflow even when its immutable
@@ -333,10 +361,13 @@ current gate evidence do block.
333
361
  overwrite historical evidence.
334
362
  - `.ultra` workflow artifacts are excluded from application-worktree drift, but
335
363
  their own recorded SHA-256 digests are still authoritative.
336
- - Generated `tasks.json` and task-context headers are projections. Manual edits
364
+ - Generated `tasks.json` and complete task-context files are projections. Manual edits
337
365
  are overwritten and never repair DB authority.
338
366
  - A changed checkpoint artifact digest or superseded decision invalidates the linked
339
367
  decision checkpoint and blocks matching workflow advancement until reconfirmed.
368
+ - `workflow.revise` creates a candidate without deleting the completed prior run.
369
+ `workflow.supersede` promotes only a completed matching candidate, preserves both
370
+ histories, and invalidates only the prior run's transitive registered consumers.
340
371
 
341
372
  ## 8. Entry flows
342
373
 
@@ -347,7 +378,7 @@ ultra-init -> Git/scaffold/authority verification -> init completed
347
378
  -> explicit ultra-research -> suggested route -> owner-selected accepted coverage
348
379
  -> selected evidence work
349
380
  -> local checkpoint when Git is unborn -> baseline approval
350
- -> ultra-change -> optional bounded research/plan -> dev -> risk-selected test/review
381
+ -> ultra-change -> optional bounded research -> typed delta -> plan -> dev -> risk-selected test/review
351
382
  -> local deliver/archive
352
383
  ```
353
384
 
@@ -357,7 +388,7 @@ ultra-init -> Git/scaffold/authority verification -> init completed
357
388
  ultra-init -> preserve Git/classify brownfield -> init completed
358
389
  -> explicit adoption research -> suggested route -> owner-selected accepted coverage
359
390
  -> selected characterization/known-red evidence -> gap ledger -> owner approval
360
- -> first ultra-change -> optional bounded research/plan -> dev -> test/review -> deliver
391
+ -> first ultra-change -> optional bounded research -> typed delta -> plan -> dev -> test/review -> deliver
361
392
  ```
362
393
 
363
394
  Adoption records the system that exists. It does not force the owner to recreate
@@ -377,9 +408,9 @@ Migration preserves prior files and rows but never carries old approval forward.
377
408
 
378
409
  ```text
379
410
  ultra-status -> ultra-change intent capture
380
- -> model recommends; owner selects or delegates bounded research, plan, current task,
381
- think, or status
382
- -> dev -> risk-selected test -> review -> deliver -> reconciled baseline
411
+ -> model recommends; owner selects bounded research or direct plan
412
+ -> typed delta -> plan -> dev -> risk-selected test -> review
413
+ -> documentation reconciliation -> atomic deliver -> reconciled baseline
383
414
  ```
384
415
 
385
416
  Quick changes shorten the artifact burden only where the machine contract allows;
@@ -392,9 +423,9 @@ evidence rather than bypassing them.
392
423
  | Entry | Owns | Normal handoff |
393
424
  |---|---|---|
394
425
  | `ultra-init` | classification, scope, Git/scaffold bootstrap, and init verification | stops completed; exposes research/status/doctor and any already-valid change capability |
395
- | `ultra-research` | coverage disposition, selected evidence acquisition, synthesis, and material research decisions | baseline convergence or return to the owning change |
426
+ | `ultra-research` | coverage disposition, selected evidence acquisition, synthesis, and material research decisions | baseline convergence or Change delta then plan |
396
427
  | `ultra-think` | one material owner decision or read-only diagnosis | returns to the invoking capability after any needed checkpoint |
397
- | `ultra-change` | outcome capture, Change Contract, risk/research routing | model recommends; owner selects or delegates research, plan, current dev work, think, or status |
428
+ | `ultra-change` | outcome capture, Change Contract, isolated overlay, typed delta, and risk/research routing | model recommends; owner selects research or direct plan; development is never a direct handoff |
398
429
  | `ultra-plan` | task contracts, dependencies, acceptance coverage, optional material approval | any dependency-ready task through `ultra-dev` |
399
430
  | `ultra-dev` | one task/session vertical slice and focused review | next `ultra-dev` task or aggregate `ultra-test` |
400
431
  | `ultra-test` | risk-selected change acceptance and verification profile | `ultra-review` on pass, `ultra-dev`/`ultra-test` on failure |
package/hooks/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ultra Builder Pro workflow hooks
2
2
 
3
3
  These hooks observe only Ultra-owned state. Session health/context recognizes an initialized
4
- `.ultra/state.db` and active continuous changes. Direct projection writes remain blocked;
4
+ `.ultra/.runtime/state.db` and active continuous changes. Direct projection writes remain blocked;
5
5
  context, compact, stop, and subagent lifecycle behavior is recovery-oriented and does not turn
6
6
  advisory workflow conditions into refusal gates.
7
7
 
@@ -9,16 +9,17 @@ advisory workflow conditions into refusal gates.
9
9
  |---|---|---|
10
10
  | Session start | `health_check.py` | Read-only integrity, incident, projection, session, and change-artifact checks |
11
11
  | Shared helper | `context_spine.py` | Read the latest role/gate/readiness snapshot from state.db and derive one compact breadcrumb |
12
- | Session start | `workflow_context.py` | Inject the DB-derived breadcrumb without intent or provider payloads |
12
+ | Session start | `workflow_context.py` | Inject the DB-derived breadcrumb with bounded normalized accepted intent, never raw interaction or provider payloads |
13
13
  | Before edit | `active_task_context.py` | Protect `tasks.json` and restate the same DB-derived task breadcrumb |
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
17
  | Subagent lifecycle | `subagent_tracker.py` | Append minimal start/stop metadata to the authoritative DB event stream |
18
18
 
19
- The plugin deliberately does not capture prompts, tool observations, transcripts, summaries, or
20
- cross-session memory or code-graph content. Generic command blocking and post-edit policy are
21
- user/repository governance. Persistent Memory/graph content belongs to separately installed providers.
19
+ The plugin deliberately does not capture prompts, tool observations, transcripts, summaries, general
20
+ conversational memory, or code-graph payloads. It does retain project-local Ultra workflow memory in
21
+ `.ultra/`. Generic command blocking and post-edit policy are user/repository governance. General
22
+ memory and graph content belongs to separately installed providers.
22
23
 
23
24
  `subagent_tracker.py` passes only agent id/type and the host session id to the bundled
24
25
  `hook-event.cjs` helper. The helper binds the current change/task and appends
@@ -5,26 +5,72 @@ import json
5
5
  import sys
6
6
  from pathlib import Path
7
7
 
8
- from context_spine import ContextSpineError, find_root, read_breadcrumb, render_breadcrumb
8
+ from context_spine import (
9
+ ContextSpineError,
10
+ find_root,
11
+ read_breadcrumb,
12
+ render_breadcrumb,
13
+ )
9
14
 
10
15
  TASKS_PROJECTION = Path(".ultra/tasks/tasks.json")
11
16
 
12
17
 
13
- def targets_tasks_projection(data: dict, root: Path) -> bool:
18
+ def tool_paths(data: dict, start: Path):
19
+ """Yield absolute tool targets without assuming they belong to ``start``."""
14
20
  tool_input = data.get("tool_input")
15
21
  if not isinstance(tool_input, dict):
16
- return False
22
+ return
17
23
  for key in ("file_path", "filePath", "filepath", "path"):
18
24
  value = tool_input.get(key)
19
25
  if not isinstance(value, str) or not value.strip():
20
26
  continue
21
27
  candidate = Path(value)
22
- resolved = candidate.resolve() if candidate.is_absolute() else (root / candidate).resolve()
28
+ yield candidate.resolve() if candidate.is_absolute() else (start / candidate).resolve()
29
+
30
+
31
+ def projection_root_for_target(target: Path) -> Path | None:
32
+ """Return the project root only for the exact generated tasks projection."""
33
+ if (
34
+ target.name == "tasks.json"
35
+ and target.parent.name == "tasks"
36
+ and target.parent.parent.name == ".ultra"
37
+ ):
38
+ return target.parent.parent.parent
39
+ return None
40
+
41
+
42
+ def targets_tasks_projection(data: dict, root: Path) -> bool:
43
+ for resolved in tool_paths(data, root):
23
44
  if resolved == (root / TASKS_PROJECTION).resolve():
24
45
  return True
25
46
  return False
26
47
 
27
48
 
49
+ def projection_denial(reason: str) -> dict:
50
+ return {"hookSpecificOutput": {
51
+ "hookEventName": "PreToolUse",
52
+ "permissionDecision": "deny",
53
+ "permissionDecisionReason": reason,
54
+ }}
55
+
56
+
57
+ def conflicted_projection_root(data: dict, start: Path) -> Path | None:
58
+ for candidate in (start, *start.parents):
59
+ if not (candidate / ".ultra").exists():
60
+ continue
61
+ if targets_tasks_projection(data, candidate):
62
+ return candidate
63
+ return None
64
+
65
+
66
+ def projection_roots(data: dict, start: Path):
67
+ """Yield initialized project roots derived from every resolved tool target."""
68
+ for target in tool_paths(data, start):
69
+ root = projection_root_for_target(target)
70
+ if root is not None and (root / ".ultra").exists():
71
+ yield root
72
+
73
+
28
74
  def main() -> None:
29
75
  try:
30
76
  data = json.loads(sys.stdin.read() or "{}")
@@ -32,20 +78,59 @@ def main() -> None:
32
78
  except json.JSONDecodeError as exc:
33
79
  print(f"[active_task_context] invalid hook input: {exc}", file=sys.stderr)
34
80
  data = {}
35
- root = find_root(Path(data.get("cwd") or Path.cwd()).resolve())
81
+ start = Path(data.get("cwd") or Path.cwd()).resolve()
82
+ # An absolute tool target carries its own project boundary. Resolve and
83
+ # protect that authority before consulting cwd, because hooks may run from
84
+ # a host-owned temporary directory or another repository.
85
+ for target_root in projection_roots(data, start):
86
+ try:
87
+ target_authority = find_root(target_root)
88
+ except ContextSpineError as exc:
89
+ print(json.dumps(projection_denial(
90
+ "Refusing a direct write to an Ultra projection while the "
91
+ f"runtime authority is conflicted: {exc}. Resolve the "
92
+ "state.db conflict with ultra-doctor before editing generated "
93
+ "project artifacts."
94
+ )))
95
+ return
96
+ if target_authority is None:
97
+ continue
98
+ print(json.dumps(projection_denial(
99
+ "Refusing a direct write to the .ultra/tasks/tasks.json projection. "
100
+ ".ultra/.runtime/state.db is authoritative; use the Ultra MCP task tools and "
101
+ "run ultra-doctor when state or projection health is degraded."
102
+ )))
103
+ return
104
+
105
+ try:
106
+ root = find_root(start)
107
+ except ContextSpineError as exc:
108
+ print(
109
+ f"[active_task_context] cannot resolve Ultra root: {exc}",
110
+ file=sys.stderr,
111
+ )
112
+ # Ordinary context injection remains fail-open. A direct write to a
113
+ # known generated projection is different: an authority conflict
114
+ # means the hook cannot prove which DB would regenerate that file, so
115
+ # allowing the write would destroy the projection boundary.
116
+ if conflicted_projection_root(data, start) is not None:
117
+ print(json.dumps(projection_denial(
118
+ "Refusing a direct write to an Ultra projection while the "
119
+ f"runtime authority is conflicted: {exc}. Resolve the "
120
+ "state.db conflict with ultra-doctor before editing generated "
121
+ "project artifacts."
122
+ )))
123
+ return
124
+ root = None
36
125
  if root is None:
37
126
  print(json.dumps({}))
38
127
  return
39
128
  if targets_tasks_projection(data, root):
40
- print(json.dumps({"hookSpecificOutput": {
41
- "hookEventName": "PreToolUse",
42
- "permissionDecision": "deny",
43
- "permissionDecisionReason": (
129
+ print(json.dumps(projection_denial(
44
130
  "Refusing a direct write to the .ultra/tasks/tasks.json projection. "
45
- ".ultra/state.db is authoritative; use the Ultra MCP task tools and "
131
+ ".ultra/.runtime/state.db is authoritative; use the Ultra MCP task tools and "
46
132
  "run ultra-doctor when state or projection health is degraded."
47
- ),
48
- }}))
133
+ )))
49
134
  return
50
135
  try:
51
136
  breadcrumb = read_breadcrumb(root)
@@ -5,8 +5,11 @@ import json
5
5
  import os
6
6
  import shutil
7
7
  import subprocess
8
+ import sys
8
9
  from pathlib import Path
9
10
 
11
+ from runtime_paths import RuntimePathError, find_project_root
12
+
10
13
 
11
14
  class ContextSpineError(RuntimeError):
12
15
  """The canonical breadcrumb helper could not be executed or decoded."""
@@ -21,10 +24,19 @@ class Breadcrumb(dict):
21
24
 
22
25
 
23
26
  def find_root(start: Path):
24
- for root in (start, *start.parents):
25
- if (root / ".ultra" / "state.db").is_file():
26
- return root
27
- return None
27
+ try:
28
+ return find_project_root(start)
29
+ except RuntimePathError as exc:
30
+ raise ContextSpineError(str(exc)) from exc
31
+
32
+
33
+ def find_root_for_hook(start: Path, hook_name: str):
34
+ """Fail open at a hook boundary while preserving path-conflict evidence."""
35
+ try:
36
+ return find_root(start)
37
+ except ContextSpineError as exc:
38
+ print(f"[{hook_name}] cannot resolve Ultra root: {exc}", file=sys.stderr)
39
+ return None
28
40
 
29
41
 
30
42
  def _helper_path() -> Path: