ultra-builder-pro-cli 0.25.1 → 0.26.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 (243) hide show
  1. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  2. package/.ultra-template/contexts/TEMPLATE.md +94 -0
  3. package/.ultra-template/decisions/.gitkeep +1 -0
  4. package/.ultra-template/evidence/.gitkeep +1 -0
  5. package/.ultra-template/north-star.md +46 -0
  6. package/.ultra-template/research/.gitkeep +1 -0
  7. package/.ultra-template/specs/discovery.md +2 -2
  8. package/.ultra-template/tasks.json +3 -0
  9. package/.ultra-template/test-report.json +27 -0
  10. package/CHANGELOG.md +107 -1
  11. package/README.md +217 -579
  12. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  13. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  14. package/adapters/_shared/file-ops.cjs +76 -2
  15. package/adapters/_shared/host-profile.cjs +97 -0
  16. package/adapters/_shared/plugin-core.cjs +204 -0
  17. package/adapters/_shared/runtime-assets.cjs +31 -96
  18. package/adapters/claude.js +92 -291
  19. package/adapters/codex.js +309 -842
  20. package/adapters/grok.js +143 -1469
  21. package/adapters/kimi.js +124 -1053
  22. package/adapters/opencode.js +230 -742
  23. package/bin/delegate-worker.cjs +328 -0
  24. package/bin/delegate.cjs +463 -0
  25. package/bin/install.js +38 -11
  26. package/docs/ARCHITECTURE.md +125 -216
  27. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  28. package/docs/DECISIONS.md +77 -87
  29. package/docs/PHILOSOPHY.md +208 -0
  30. package/docs/PLUGIN-ISOLATION-CONTRACT.md +65 -39
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +140 -155
  32. package/docs/SKILL-AUTHORING.md +94 -73
  33. package/docs/WORKFLOW-LIFECYCLE.md +97 -189
  34. package/hooks/README.md +19 -37
  35. package/hooks/_common.py +113 -0
  36. package/hooks/adapters/codex.py +9 -16
  37. package/hooks/adapters/grok.py +5 -5
  38. package/hooks/adapters/kimi.py +8 -14
  39. package/hooks/block_dangerous_commands.py +201 -0
  40. package/hooks/compact_context.py +65 -0
  41. package/hooks/mid_workflow_recall.py +25 -0
  42. package/hooks/post_edit_guard.py +105 -0
  43. package/hooks/session_context.py +30 -0
  44. package/package.json +17 -52
  45. package/skills/ultra-change/SKILL.md +115 -50
  46. package/skills/ultra-change/references/change-contract.md +68 -3
  47. package/skills/ultra-delegate/SKILL.md +89 -0
  48. package/skills/ultra-delegate/references/delegation-contract.md +59 -0
  49. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  50. package/skills/ultra-deliver/SKILL.md +98 -42
  51. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  52. package/skills/ultra-dev/SKILL.md +115 -62
  53. package/skills/ultra-dev/references/debugging.md +22 -0
  54. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  55. package/skills/ultra-grilling/SKILL.md +118 -0
  56. package/skills/ultra-grilling/references/reframing.md +72 -0
  57. package/skills/ultra-init/SKILL.md +102 -37
  58. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  59. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  60. package/skills/ultra-plan/SKILL.md +101 -68
  61. package/skills/ultra-research/SKILL.md +68 -48
  62. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  63. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  64. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  65. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  66. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  67. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  68. package/skills/ultra-research/references/10-user-personas.md +3 -2
  69. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  70. package/skills/ultra-research/references/20-user-stories.md +4 -3
  71. package/skills/ultra-research/references/21-features-scope.md +3 -2
  72. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  73. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  74. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  75. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  76. package/skills/ultra-research/references/40-deployment.md +3 -3
  77. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  78. package/skills/ultra-research/references/99-synthesis.md +7 -7
  79. package/skills/ultra-review/SKILL.md +84 -32
  80. package/skills/ultra-review/references/code.md +17 -0
  81. package/skills/ultra-review/references/comments.md +12 -0
  82. package/skills/ultra-review/references/design.md +12 -0
  83. package/skills/ultra-review/references/errors.md +13 -0
  84. package/skills/ultra-review/references/spec.md +13 -0
  85. package/skills/ultra-review/references/tests.md +12 -0
  86. package/skills/ultra-review/references/worker-packet.md +40 -0
  87. package/skills/ultra-review/scripts/review_wait.py +26 -15
  88. package/skills/ultra-status/SKILL.md +61 -36
  89. package/skills/ultra-tdd/SKILL.md +81 -0
  90. package/skills/ultra-tdd/references/templates/README.md +20 -0
  91. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  92. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  93. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  94. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  95. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  96. package/skills/ultra-tdd/references/test-execution.md +18 -0
  97. package/skills/ultra-test/SKILL.md +75 -30
  98. package/skills/ultra-test/references/export-syntax.md +18 -0
  99. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  100. package/skills/ultra-think/SKILL.md +49 -27
  101. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  102. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  103. package/.ultra-template/docs/research/README.md +0 -55
  104. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  105. package/.ultra-template/reports/templates/test-report.json +0 -18
  106. package/.ultra-template/tasks/tasks.json +0 -15
  107. package/.ultra-template/templates/task-context.md +0 -19
  108. package/adapters/_shared/codex-assets.cjs +0 -725
  109. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  110. package/adapters/_shared/interaction-contract.cjs +0 -129
  111. package/adapters/_shared/path-rewrite.cjs +0 -79
  112. package/adapters/_shared/runtime-native.cjs +0 -132
  113. package/adapters/_shared/settings-merge.cjs +0 -76
  114. package/agents/code-reviewer.md +0 -42
  115. package/agents/debugger.md +0 -40
  116. package/agents/review-code.md +0 -49
  117. package/agents/review-comments.md +0 -38
  118. package/agents/review-coordinator.md +0 -56
  119. package/agents/review-design.md +0 -40
  120. package/agents/review-errors.md +0 -43
  121. package/agents/review-spec.md +0 -39
  122. package/agents/review-tests.md +0 -40
  123. package/agents/tdd-runner.md +0 -38
  124. package/bin/orchestrator.js +0 -383
  125. package/commands/ultra-change.md +0 -10
  126. package/commands/ultra-deliver.md +0 -10
  127. package/commands/ultra-dev.md +0 -10
  128. package/commands/ultra-doctor.md +0 -10
  129. package/commands/ultra-init.md +0 -10
  130. package/commands/ultra-plan.md +0 -10
  131. package/commands/ultra-research.md +0 -10
  132. package/commands/ultra-review.md +0 -10
  133. package/commands/ultra-status.md +0 -10
  134. package/commands/ultra-test.md +0 -10
  135. package/commands/ultra-think.md +0 -10
  136. package/docs/AGENT-CONTEXT.md +0 -191
  137. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  138. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  139. package/docs/ROADMAP.md +0 -101
  140. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  141. package/hooks/active_task_context.py +0 -152
  142. package/hooks/context_envelope.py +0 -100
  143. package/hooks/health_check.py +0 -196
  144. package/hooks/pre_stop_check.py +0 -66
  145. package/hooks/runtime_paths.py +0 -396
  146. package/hooks/subagent_tracker.py +0 -88
  147. package/hooks/workflow_checkpoint.py +0 -76
  148. package/hooks/workflow_context.py +0 -48
  149. package/hooks/workflow_resume.py +0 -84
  150. package/mcp-server/hook-context.cjs +0 -38
  151. package/mcp-server/hook-event.cjs +0 -34
  152. package/mcp-server/lib/archive-journal.cjs +0 -1119
  153. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  154. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  155. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  156. package/mcp-server/lib/canonical-json.cjs +0 -19
  157. package/mcp-server/lib/change-authority.cjs +0 -26
  158. package/mcp-server/lib/change-packet.cjs +0 -790
  159. package/mcp-server/lib/change-workflow.cjs +0 -65
  160. package/mcp-server/lib/context-envelope.cjs +0 -581
  161. package/mcp-server/lib/context-paths.cjs +0 -142
  162. package/mcp-server/lib/decision-records.cjs +0 -423
  163. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  164. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  165. package/mcp-server/lib/doctor.cjs +0 -627
  166. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  167. package/mcp-server/lib/init-project.cjs +0 -1021
  168. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  169. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  170. package/mcp-server/lib/managed-file-write.cjs +0 -172
  171. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  172. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  173. package/mcp-server/lib/plan-store.cjs +0 -794
  174. package/mcp-server/lib/prd-parser.cjs +0 -83
  175. package/mcp-server/lib/pricing.cjs +0 -44
  176. package/mcp-server/lib/project-context.cjs +0 -178
  177. package/mcp-server/lib/projector.cjs +0 -386
  178. package/mcp-server/lib/provider-refs.cjs +0 -42
  179. package/mcp-server/lib/runtime-paths.cjs +0 -1714
  180. package/mcp-server/lib/runtime-state.cjs +0 -222
  181. package/mcp-server/lib/safe-project-file.cjs +0 -385
  182. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  183. package/mcp-server/lib/state-authority.cjs +0 -95
  184. package/mcp-server/lib/state-db.cjs +0 -1580
  185. package/mcp-server/lib/state-ops.cjs +0 -1428
  186. package/mcp-server/lib/task-contract.cjs +0 -404
  187. package/mcp-server/lib/task-expander.cjs +0 -105
  188. package/mcp-server/lib/task-ledger.cjs +0 -1964
  189. package/mcp-server/lib/telemetry.cjs +0 -85
  190. package/mcp-server/lib/topo.cjs +0 -131
  191. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  192. package/mcp-server/lib/worker-packet.cjs +0 -358
  193. package/mcp-server/server.cjs +0 -437
  194. package/orchestrator/auto-merge.cjs +0 -128
  195. package/orchestrator/daemon.cjs +0 -264
  196. package/orchestrator/dispatch-rules.cjs +0 -78
  197. package/orchestrator/parallel-orchestrator.cjs +0 -903
  198. package/orchestrator/planner/plan-builder.cjs +0 -125
  199. package/orchestrator/recovery.cjs +0 -188
  200. package/orchestrator/session-close-journal-worker.cjs +0 -5
  201. package/orchestrator/session-close-journal.cjs +0 -1014
  202. package/orchestrator/session-runner.cjs +0 -1153
  203. package/orchestrator/worktree-manager.cjs +0 -282
  204. package/settings.json +0 -7
  205. package/skills/cc-collab/SKILL.md +0 -42
  206. package/skills/code-review-expert/SKILL.md +0 -43
  207. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  208. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  209. package/skills/code-review-expert/references/removal-plan.md +0 -24
  210. package/skills/codex-collab/SKILL.md +0 -49
  211. package/skills/codex-collab/evals/evals.json +0 -35
  212. package/skills/integration-rules/SKILL.md +0 -32
  213. package/skills/security-rules/SKILL.md +0 -38
  214. package/skills/testing-rules/SKILL.md +0 -32
  215. package/skills/ultra-doctor/SKILL.md +0 -41
  216. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  217. package/skills/ultra-verify/SKILL.md +0 -46
  218. package/skills/ultra-verify/evals/evals.json +0 -46
  219. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  220. package/skills/ultra-verify/references/evidence-status.md +0 -21
  221. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  222. package/spec/cli-protocol.md +0 -102
  223. package/spec/mcp-tools.yaml +0 -312
  224. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  225. package/spec/schemas/change-delta.v1.schema.json +0 -153
  226. package/spec/schemas/command-manifest.schema.json +0 -29
  227. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  228. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  229. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  230. package/spec/schemas/mcp-tools.schema.json +0 -136
  231. package/spec/schemas/skill-manifest.schema.json +0 -24
  232. package/spec/schemas/state-db.sql +0 -641
  233. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  234. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  235. package/spec/schemas/test-report.v1.schema.json +0 -225
  236. package/ultra-tools/cli.cjs +0 -129
  237. package/ultra-tools/commands/db.cjs +0 -226
  238. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  239. package/ultra-tools/commands/migrate.cjs +0 -1386
  240. package/ultra-tools/commands/session.cjs +0 -213
  241. package/ultra-tools/commands/status.cjs +0 -559
  242. package/ultra-tools/commands/system.cjs +0 -498
  243. package/ultra-tools/commands/task.cjs +0 -105
@@ -1,396 +0,0 @@
1
- """Read-only canonical project and state-path resolution for Ultra hooks."""
2
-
3
- import json
4
- import os
5
- import sqlite3
6
- import subprocess
7
- from pathlib import Path
8
-
9
-
10
- class RuntimePathError(RuntimeError):
11
- """Ultra runtime paths are invalid or have competing authorities."""
12
-
13
- STATE_TOMBSTONE_MARKER = "MIGRATED_TO_RUNTIME.json"
14
- STATE_TOMBSTONE_KIND = "ultra-state-migration-tombstone"
15
-
16
-
17
- def _absolute(path: Path) -> Path:
18
- expanded = path.expanduser()
19
- return expanded if expanded.is_absolute() else Path.cwd() / expanded
20
-
21
-
22
- def _assert_root(root: Path, *, explicit: bool) -> Path:
23
- absolute = _absolute(root)
24
- try:
25
- stat = absolute.lstat()
26
- except FileNotFoundError as error:
27
- raise RuntimePathError(f"UBP_ROOT_DIR does not exist: {absolute}") from error
28
- if (explicit and absolute.is_symlink()) or not absolute.is_dir():
29
- raise RuntimePathError(
30
- f"UBP_ROOT_DIR must be a real directory, not a symlink or special entry: {absolute}"
31
- )
32
- return absolute.resolve()
33
-
34
-
35
- def _assert_directory(path: Path, label: str, *, allow_runtime_link_to: Path | None = None) -> None:
36
- try:
37
- path.lstat()
38
- except FileNotFoundError:
39
- return
40
- if path.is_symlink():
41
- if allow_runtime_link_to is not None:
42
- try:
43
- if (
44
- path.resolve(strict=True) == allow_runtime_link_to.parent.resolve(strict=True)
45
- and (path / "state.db").resolve(strict=True)
46
- == allow_runtime_link_to.resolve(strict=True)
47
- ):
48
- return
49
- except FileNotFoundError:
50
- pass
51
- raise RuntimePathError(f"{label} may not be a symlink: {path}")
52
- if not path.is_dir():
53
- raise RuntimePathError(f"{label} must be a directory: {path}")
54
-
55
-
56
- def _is_managed_legacy_tombstone(main: Path) -> bool:
57
- try:
58
- stat = main.lstat()
59
- except FileNotFoundError:
60
- return False
61
- if main.is_symlink():
62
- return False
63
- marker = main / STATE_TOMBSTONE_MARKER if main.is_dir() else main
64
- try:
65
- if main.is_dir():
66
- if [entry.name for entry in main.iterdir()] != [STATE_TOMBSTONE_MARKER]:
67
- return False
68
- elif not main.is_file():
69
- return False
70
- marker_stat = marker.lstat()
71
- if marker.is_symlink() or not marker_stat.st_mode:
72
- return False
73
- payload = json.loads(marker.read_text(encoding="utf-8"))
74
- except (FileNotFoundError, OSError, ValueError, json.JSONDecodeError):
75
- return False
76
- canonical = (main.parent / str(payload.get("canonical_state_db", ""))).resolve()
77
- expected = (main.parent / ".runtime" / "state.db").resolve()
78
- return (
79
- payload.get("version") == 1
80
- and payload.get("kind") == STATE_TOMBSTONE_KIND
81
- and marker.is_file()
82
- and canonical == expected
83
- )
84
-
85
-
86
- def _assert_state_set(
87
- main: Path, label: str, *, allow_managed_tombstone: bool = False
88
- ) -> None:
89
- present: dict[str, bool] = {}
90
- for suffix in ("", "-wal", "-shm"):
91
- candidate = Path(f"{main}{suffix}")
92
- try:
93
- candidate.lstat()
94
- except FileNotFoundError:
95
- present[suffix] = False
96
- continue
97
- tombstone = (
98
- suffix == ""
99
- and allow_managed_tombstone
100
- and _is_managed_legacy_tombstone(candidate)
101
- )
102
- if not tombstone and (candidate.is_symlink() or not candidate.is_file()):
103
- raise RuntimePathError(
104
- f"{label} state entry must be a regular file and may not be a symlink: {candidate}"
105
- )
106
- present[suffix] = not tombstone
107
- if not present[""] and (present["-wal"] or present["-shm"]):
108
- raise RuntimePathError(f"{label} SQLite sidecar exists without state.db authority")
109
-
110
-
111
- def _physical_state_paths(root: Path) -> tuple[Path, Path]:
112
- resolved = root.resolve()
113
- return (
114
- resolved / ".ultra" / ".runtime" / "state.db",
115
- resolved / ".ultra" / "state.db",
116
- )
117
-
118
-
119
- def _same_physical_authority(left: Path, right: Path) -> bool:
120
- if left == right:
121
- return True
122
- try:
123
- return left.resolve(strict=True) == right.resolve(strict=True)
124
- except FileNotFoundError:
125
- return False
126
-
127
-
128
- def _is_task_authority_link(
129
- runtime: Path, candidate: Path, physical_runtime: Path, runtime_state: Path
130
- ) -> bool:
131
- try:
132
- parts = candidate.relative_to(runtime).parts
133
- except ValueError:
134
- return False
135
- if (
136
- len(parts) != 4
137
- or parts[0] != "worktrees"
138
- or not parts[1]
139
- or len(parts[1]) > 128
140
- or not parts[1][0].isalnum()
141
- or any(not (char.isalnum() or char in "._-") for char in parts[1])
142
- or parts[2:] != (".ultra", ".runtime")
143
- ):
144
- return False
145
- try:
146
- return (
147
- candidate.resolve(strict=True) == physical_runtime
148
- and (candidate / "state.db").resolve(strict=True)
149
- == runtime_state.resolve(strict=True)
150
- )
151
- except FileNotFoundError:
152
- return False
153
-
154
-
155
- def _has_git_metadata(root: Path) -> bool:
156
- for candidate in (root, *root.parents):
157
- if (candidate / ".git").exists() or (candidate / ".git").is_symlink():
158
- return True
159
- return False
160
-
161
-
162
- def _registered_worktrees(root: Path) -> set[Path]:
163
- probe = subprocess.run(
164
- ["git", "rev-parse", "--is-inside-work-tree"],
165
- cwd=root,
166
- text=True,
167
- capture_output=True,
168
- check=False,
169
- )
170
- if probe.returncode != 0 or probe.stdout.strip() != "true":
171
- detail = (probe.stderr or probe.stdout or "unknown Git probe failure").strip()
172
- if not _has_git_metadata(root) and "not a git repository" in detail.lower():
173
- return set()
174
- raise RuntimePathError(f"cannot classify Git worktree authority: {detail}")
175
- result = subprocess.run(
176
- ["git", "worktree", "list", "--porcelain"],
177
- cwd=root,
178
- text=True,
179
- capture_output=True,
180
- check=False,
181
- )
182
- if result.returncode != 0:
183
- detail = (result.stderr or result.stdout or "unknown Git probe failure").strip()
184
- raise RuntimePathError(f"cannot inspect registered Git worktrees: {detail}")
185
- registered: set[Path] = set()
186
- for line in result.stdout.splitlines():
187
- if line.startswith("worktree "):
188
- registered.add(Path(line.removeprefix("worktree ")).resolve())
189
- return registered
190
-
191
-
192
- def _assert_registered_worktree_boundary(
193
- runtime: Path, candidate: Path, physical_runtime: Path
194
- ) -> None:
195
- sid = candidate.name
196
- if (
197
- not sid
198
- or len(sid) > 128
199
- or not sid[0].isalnum()
200
- or any(not (char.isalnum() or char in "._-") for char in sid)
201
- ):
202
- raise RuntimePathError(f"registered worktree has an unsafe session id: {candidate}")
203
- authority_link = candidate / ".ultra" / ".runtime"
204
- if (
205
- not authority_link.is_symlink()
206
- or not _is_task_authority_link(
207
- runtime, authority_link, physical_runtime, runtime / "state.db"
208
- )
209
- ):
210
- raise RuntimePathError(
211
- "registered worktree must contain the exact central Ultra "
212
- f"authority link: {authority_link}"
213
- )
214
-
215
-
216
- def _assert_runtime_tree(root: Path, runtime: Path) -> None:
217
- try:
218
- runtime.lstat()
219
- except FileNotFoundError:
220
- return
221
- try:
222
- physical_runtime = runtime.resolve(strict=True)
223
- except FileNotFoundError as error:
224
- raise RuntimePathError(
225
- f"canonical runtime root cannot be resolved: {runtime}"
226
- ) from error
227
- pending = list(runtime.iterdir())
228
- runtime_state = runtime / "state.db"
229
- registered: set[Path] | None = None
230
- while pending:
231
- candidate = pending.pop()
232
- try:
233
- candidate.lstat()
234
- except FileNotFoundError:
235
- continue
236
- if candidate.is_symlink():
237
- if _is_task_authority_link(
238
- runtime, candidate, physical_runtime, runtime_state
239
- ):
240
- continue
241
- raise RuntimePathError(
242
- f"canonical runtime entry may not be a symlink: {candidate}"
243
- )
244
- if not candidate.is_dir() and not candidate.is_file():
245
- raise RuntimePathError(
246
- "canonical runtime entry must be a regular file or real "
247
- f"directory: {candidate}"
248
- )
249
- try:
250
- physical_candidate = candidate.resolve(strict=True)
251
- physical_candidate.relative_to(physical_runtime)
252
- except (FileNotFoundError, ValueError) as error:
253
- raise RuntimePathError(
254
- f"canonical runtime entry escapes its physical root: {candidate}"
255
- ) from error
256
- try:
257
- relative_parts = candidate.relative_to(runtime).parts
258
- except ValueError:
259
- relative_parts = ()
260
- if (
261
- candidate.is_dir()
262
- and len(relative_parts) == 2
263
- and relative_parts[0] == "worktrees"
264
- ):
265
- if registered is None:
266
- registered = _registered_worktrees(root)
267
- if physical_candidate in registered:
268
- _assert_registered_worktree_boundary(
269
- runtime, candidate, physical_runtime
270
- )
271
- continue
272
- if candidate.is_dir():
273
- pending.extend(candidate.iterdir())
274
-
275
-
276
- def _project_root_from_state_db(configured: Path) -> Path:
277
- if (
278
- configured.name == "state.db"
279
- and configured.parent.name == ".runtime"
280
- and configured.parent.parent.name == ".ultra"
281
- ):
282
- return configured.parent.parent.parent
283
- raise RuntimePathError(
284
- f"configured DB is not a canonical project runtime authority: {configured}"
285
- )
286
-
287
-
288
- def _assert_task_authority_binding(root: Path, configured: Path) -> None:
289
- try:
290
- authority_root = _project_root_from_state_db(configured).resolve(strict=True)
291
- canonical_db = authority_root / ".ultra" / ".runtime" / "state.db"
292
- if configured.resolve(strict=True) != canonical_db.resolve(strict=True):
293
- raise RuntimePathError("configured DB is not the canonical authority")
294
- task_root = root.resolve(strict=True)
295
- worktrees_root = (
296
- authority_root / ".ultra" / ".runtime" / "worktrees"
297
- ).resolve(strict=True)
298
- relative = task_root.relative_to(worktrees_root)
299
- if len(relative.parts) != 1:
300
- raise RuntimePathError("task root is not a direct managed worktree child")
301
- sid = relative.parts[0]
302
- if task_root not in _registered_worktrees(authority_root):
303
- raise RuntimePathError("task root is not a registered Git worktree")
304
- runtime_link = root / ".ultra" / ".runtime"
305
- if (
306
- not runtime_link.is_symlink()
307
- or runtime_link.resolve(strict=True) != canonical_db.parent.resolve(strict=True)
308
- ):
309
- raise RuntimePathError("task authority link does not resolve to the configured DB")
310
- uri = f"{configured.resolve(strict=True).as_uri()}?mode=ro"
311
- with sqlite3.connect(uri, uri=True) as connection:
312
- row = connection.execute(
313
- "SELECT worktree_path FROM sessions WHERE sid = ?", (sid,)
314
- ).fetchone()
315
- if row is None:
316
- raise RuntimePathError(f"authority has no session binding for {sid}")
317
- if Path(row[0]).resolve(strict=True) != task_root:
318
- raise RuntimePathError(f"session {sid} is bound to a different worktree")
319
- except (FileNotFoundError, ValueError, sqlite3.Error) as error:
320
- raise RuntimePathError(
321
- f"task root is not bound to the configured Ultra authority: {error}"
322
- ) from error
323
-
324
-
325
- def validate_project_layout(
326
- root: Path, *, validate_runtime_tree: bool = False
327
- ) -> tuple[Path, Path, Path | None]:
328
- runtime, legacy = _physical_state_paths(root)
329
- configured_value = os.environ.get("UBP_DB_PATH", "").strip()
330
- configured = _absolute(Path(configured_value)) if configured_value else None
331
- if configured is not None:
332
- _assert_state_set(configured, "configured")
333
- _assert_directory(root / ".ultra", ".ultra")
334
- _assert_directory(
335
- root / ".ultra" / ".runtime",
336
- ".ultra/.runtime",
337
- allow_runtime_link_to=configured,
338
- )
339
- _assert_state_set(runtime, "runtime")
340
- _assert_state_set(legacy, "legacy", allow_managed_tombstone=True)
341
- if validate_runtime_tree:
342
- _assert_runtime_tree(root, root.resolve() / ".ultra" / ".runtime")
343
- return runtime, legacy, configured
344
-
345
-
346
- def _assert_no_competing_state(root: Path) -> tuple[Path, Path]:
347
- runtime, legacy, configured = validate_project_layout(root)
348
- legacy_active = legacy.is_file() and not _is_managed_legacy_tombstone(legacy)
349
- if runtime.is_file() and legacy_active:
350
- raise RuntimePathError(
351
- "both legacy .ultra/state.db and runtime "
352
- ".ultra/.runtime/state.db exist; refusing to choose either authority"
353
- )
354
- if configured is not None:
355
- active = runtime if runtime.is_file() else (legacy if legacy_active else None)
356
- allowed = (
357
- _same_physical_authority(configured, active)
358
- if active is not None
359
- else configured == runtime
360
- )
361
- if not allowed:
362
- raise RuntimePathError(
363
- "UBP_DB_PATH does not name this project's canonical or "
364
- f"task-linked authority: {configured}"
365
- )
366
- if (root / ".ultra" / ".runtime").is_symlink():
367
- _assert_task_authority_binding(root, configured)
368
- return runtime, legacy
369
-
370
-
371
- def state_db_path(root: Path) -> Path:
372
- """Return configured, runtime, or legacy state without mutating the project."""
373
- runtime, legacy = _assert_no_competing_state(root)
374
- configured = os.environ.get("UBP_DB_PATH", "").strip()
375
- if configured:
376
- return _absolute(Path(configured)).resolve()
377
- if runtime.is_file():
378
- return runtime
379
- if legacy.is_file() and not _is_managed_legacy_tombstone(legacy):
380
- return legacy
381
- return runtime
382
-
383
-
384
- def find_project_root(start: Path) -> Path | None:
385
- """Resolve one project root without letting UBP_DB_PATH invent a root."""
386
- configured_root = os.environ.get("UBP_ROOT_DIR", "").strip()
387
- if configured_root:
388
- root = _assert_root(Path(configured_root), explicit=True)
389
- return root if state_db_path(root).is_file() else None
390
-
391
- current = _assert_root(start, explicit=False)
392
- for root in (current, *current.parents):
393
- runtime, legacy = _assert_no_competing_state(root)
394
- if runtime.is_file() or legacy.is_file():
395
- return root
396
- return None
@@ -1,88 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Append minimal Ultra subagent lifecycle metadata to state.db events.
3
-
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.
7
- """
8
-
9
- import json
10
- import os
11
- import shutil
12
- import subprocess
13
- import sys
14
- from pathlib import Path
15
-
16
- from context_envelope import find_root_for_hook
17
-
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")
28
-
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")
38
-
39
-
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 ""
61
- if action not in ("start", "stop"):
62
- print(json.dumps({}))
63
- return
64
-
65
- try:
66
- raw = sys.stdin.read()
67
- hook_input = json.loads(raw) if raw.strip() else {}
68
- if not isinstance(hook_input, dict):
69
- hook_input = {}
70
- except (json.JSONDecodeError, EOFError) as exc:
71
- print(f"[subagent_tracker] invalid hook input: {exc}", file=sys.stderr)
72
- hook_input = {}
73
-
74
- root = find_root_for_hook(
75
- Path(hook_input.get("cwd") or Path.cwd()).resolve(),
76
- "subagent_tracker",
77
- )
78
- if root is not None:
79
- try:
80
- append_event(root, action, hook_input)
81
- except (OSError, RuntimeError, subprocess.TimeoutExpired) as exc:
82
- print(f"[subagent_tracker] lifecycle event unavailable: {exc}", file=sys.stderr)
83
-
84
- print(json.dumps({}))
85
-
86
-
87
- if __name__ == "__main__":
88
- main()
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Write an advisory recovery copy of the live Context Envelope before compaction."""
3
-
4
- import json
5
- import os
6
- import sys
7
- import tempfile
8
- from datetime import datetime, timezone
9
- from pathlib import Path
10
-
11
- from context_envelope import (
12
- ContextEnvelopeError,
13
- find_root_for_hook,
14
- read_context_envelope,
15
- render_context_envelope,
16
- )
17
- from runtime_paths import RuntimePathError, validate_project_layout
18
-
19
-
20
- def main() -> None:
21
- try:
22
- data = json.loads(sys.stdin.read() or "{}")
23
- data = data if isinstance(data, dict) else {}
24
- except json.JSONDecodeError as exc:
25
- print(f"[workflow_checkpoint] invalid hook input: {exc}", file=sys.stderr)
26
- data = {}
27
- root = find_root_for_hook(
28
- Path(data.get("cwd") or Path.cwd()).resolve(),
29
- "workflow_checkpoint",
30
- )
31
- if root is None:
32
- print(json.dumps({}))
33
- return
34
- try:
35
- envelope = read_context_envelope(root)
36
- except ContextEnvelopeError as exc:
37
- print(f"[workflow_checkpoint] cannot inspect Context Envelope: {exc}", file=sys.stderr)
38
- print(json.dumps({}))
39
- return
40
- if not envelope:
41
- print(json.dumps({}))
42
- return
43
-
44
- try:
45
- validate_project_layout(root, validate_runtime_tree=True)
46
- except RuntimePathError as exc:
47
- print(
48
- f"[workflow_checkpoint] unsafe Ultra runtime: {exc}",
49
- file=sys.stderr,
50
- )
51
- print(json.dumps({}))
52
- return
53
- runtime_dir = root / ".ultra" / ".runtime"
54
- runtime_dir.mkdir(parents=True, exist_ok=True)
55
- checkpoint = {
56
- "schema": 3,
57
- "captured_at": datetime.now(timezone.utc).isoformat(),
58
- "session_id": data.get("session_id", ""),
59
- "context_digest": envelope.get("digest"),
60
- "context": dict(envelope),
61
- "rendered": render_context_envelope(root, envelope),
62
- }
63
- fd, temp_name = tempfile.mkstemp(prefix="checkpoint.", suffix=".json", dir=runtime_dir)
64
- try:
65
- with os.fdopen(fd, "w", encoding="utf-8") as handle:
66
- json.dump(checkpoint, handle, ensure_ascii=False, indent=2)
67
- handle.write("\n")
68
- os.replace(temp_name, runtime_dir / "checkpoint.json")
69
- finally:
70
- if os.path.exists(temp_name):
71
- os.unlink(temp_name)
72
- print(json.dumps({"systemMessage": "Ultra Context Envelope checkpoint saved."}))
73
-
74
-
75
- if __name__ == "__main__":
76
- main()
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Inject one compact, read-only Ultra Context Envelope."""
3
-
4
- import json
5
- import sys
6
- from pathlib import Path
7
-
8
- from context_envelope import (
9
- ContextEnvelopeError,
10
- find_root_for_hook,
11
- read_context_envelope,
12
- render_context_envelope,
13
- )
14
-
15
-
16
- def payload() -> dict:
17
- try:
18
- value = json.loads(sys.stdin.read() or "{}")
19
- return value if isinstance(value, dict) else {}
20
- except json.JSONDecodeError as exc:
21
- print(f"[workflow_context] invalid hook input: {exc}", file=sys.stderr)
22
- return {}
23
-
24
-
25
- def main() -> None:
26
- data = payload()
27
- root = find_root_for_hook(
28
- Path(data.get("cwd") or Path.cwd()).resolve(),
29
- "workflow_context",
30
- )
31
- if root is None:
32
- print(json.dumps({}))
33
- return
34
- try:
35
- envelope = read_context_envelope(root)
36
- except ContextEnvelopeError as exc:
37
- print(f"[workflow_context] cannot inspect Context Envelope: {exc}", file=sys.stderr)
38
- envelope = None
39
- if not envelope:
40
- print(json.dumps({}))
41
- return
42
- context = render_context_envelope(root, envelope)
43
- print(json.dumps({"hookSpecificOutput": {
44
- "hookEventName": "SessionStart",
45
- "additionalContext": context,
46
- }}))
47
- if __name__ == "__main__":
48
- main()
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Re-inject the live Context Envelope after compaction; checkpoints are advisory."""
3
-
4
- import json
5
- import sys
6
- from datetime import datetime
7
- from pathlib import Path
8
-
9
- from context_envelope import (
10
- ContextEnvelopeError,
11
- find_root_for_hook,
12
- read_context_envelope,
13
- render_context_envelope,
14
- )
15
-
16
-
17
- def _hook_output(event: str, text: str) -> dict:
18
- if event == "SessionStart":
19
- return {"hookSpecificOutput": {
20
- "hookEventName": "SessionStart",
21
- "additionalContext": text,
22
- }}
23
- return {"additionalContext": text}
24
-
25
-
26
- def _checkpoint_advisory(root: Path):
27
- file = root / ".ultra" / ".runtime" / "checkpoint.json"
28
- if not file.is_file():
29
- return None
30
- try:
31
- value = json.loads(file.read_text(encoding="utf-8"))
32
- datetime.fromisoformat(str(value.get("captured_at", "")).replace("Z", "+00:00"))
33
- except (OSError, json.JSONDecodeError, ValueError, AttributeError) as exc:
34
- print(f"[workflow_resume] cannot read checkpoint {file}: {exc}", file=sys.stderr)
35
- return None
36
- if (
37
- not isinstance(value, dict)
38
- or value.get("schema") != 3
39
- or not isinstance(value.get("context"), dict)
40
- or not isinstance(value.get("rendered"), str)
41
- or not value["rendered"].strip()
42
- or value.get("context_digest") != value["context"].get("digest")
43
- ):
44
- print(f"[workflow_resume] invalid checkpoint schema in {file}", file=sys.stderr)
45
- return None
46
- return "\n".join([
47
- "[Ultra checkpoint advisory]",
48
- "Live .ultra/.runtime/state.db could not be read; this checkpoint is not authority.",
49
- value["rendered"],
50
- "Run ultra.doctor before mutation.",
51
- ])
52
-
53
-
54
- def main() -> None:
55
- try:
56
- data = json.loads(sys.stdin.read() or "{}")
57
- data = data if isinstance(data, dict) else {}
58
- except json.JSONDecodeError as exc:
59
- print(f"[workflow_resume] invalid hook input: {exc}", file=sys.stderr)
60
- data = {}
61
- root = find_root_for_hook(
62
- Path(data.get("cwd") or Path.cwd()).resolve(),
63
- "workflow_resume",
64
- )
65
- if root is None:
66
- print(json.dumps({}))
67
- return
68
- text = None
69
- try:
70
- envelope = read_context_envelope(root)
71
- if envelope:
72
- text = render_context_envelope(root, envelope)
73
- except ContextEnvelopeError as exc:
74
- print(f"[workflow_resume] cannot inspect Context Envelope: {exc}", file=sys.stderr)
75
- text = _checkpoint_advisory(root)
76
- if not text:
77
- print(json.dumps({}))
78
- return
79
- event = data.get("hook_event_name", "SessionStart")
80
- print(json.dumps(_hook_output(event, text)))
81
-
82
-
83
- if __name__ == "__main__":
84
- main()