ultra-builder-pro-cli 0.20.0 → 0.22.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 (109) 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 +10 -3
  4. package/.ultra-template/templates/task-context.md +19 -0
  5. package/CHANGELOG.md +106 -0
  6. package/README.md +215 -41
  7. package/adapters/_shared/codex-assets.cjs +69 -11
  8. package/adapters/_shared/interaction-contract.cjs +36 -3
  9. package/adapters/_shared/runtime-assets.cjs +41 -1
  10. package/adapters/claude.js +7 -5
  11. package/adapters/codex.js +1 -1
  12. package/adapters/kimi.js +2 -8
  13. package/adapters/opencode.js +43 -30
  14. package/bin/orchestrator.js +64 -24
  15. package/docs/AGENT-CONTEXT.md +89 -49
  16. package/docs/ARCHITECTURE.md +112 -45
  17. package/docs/ARTIFACT-AUTHORITY.md +274 -0
  18. package/docs/COMMIT-HASH-BACKFILL.md +33 -90
  19. package/docs/DECISIONS.md +14 -6
  20. package/docs/LEGACY-CLI-CROSSWALK.md +16 -6
  21. package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -4
  22. package/docs/ROADMAP.md +20 -6
  23. package/docs/RUNTIME-COMPAT-MATRIX.md +29 -16
  24. package/docs/STATE-DB-ACCESS-POLICY.md +43 -13
  25. package/docs/WORKFLOW-LIFECYCLE.md +105 -50
  26. package/hooks/README.md +10 -8
  27. package/hooks/active_task_context.py +105 -18
  28. package/hooks/context_spine.py +16 -4
  29. package/hooks/health_check.py +5 -4
  30. package/hooks/pre_stop_check.py +5 -2
  31. package/hooks/runtime_paths.py +396 -0
  32. package/hooks/subagent_tracker.py +5 -2
  33. package/hooks/workflow_checkpoint.py +16 -3
  34. package/hooks/workflow_context.py +11 -3
  35. package/hooks/workflow_resume.py +7 -4
  36. package/mcp-server/breadcrumb.cjs +20 -2
  37. package/mcp-server/lib/archive-journal.cjs +1026 -111
  38. package/mcp-server/lib/archive-mutation-worker.py +365 -0
  39. package/mcp-server/lib/artifact-registry.cjs +1127 -0
  40. package/mcp-server/lib/baseline-workflow.cjs +69 -28
  41. package/mcp-server/lib/change-packet.cjs +818 -0
  42. package/mcp-server/lib/change-workflow.cjs +932 -176
  43. package/mcp-server/lib/context-paths.cjs +142 -0
  44. package/mcp-server/lib/context-spine.cjs +511 -45
  45. package/mcp-server/lib/decision-dialogue.cjs +345 -3
  46. package/mcp-server/lib/delivery-transaction.cjs +471 -0
  47. package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
  48. package/mcp-server/lib/doctor.cjs +401 -11
  49. package/mcp-server/lib/git-bootstrap.cjs +458 -47
  50. package/mcp-server/lib/init-project.cjs +240 -95
  51. package/mcp-server/lib/plan-publication-worker.cjs +830 -0
  52. package/mcp-server/lib/plan-store.cjs +700 -5
  53. package/mcp-server/lib/project-breadcrumb.cjs +23 -4
  54. package/mcp-server/lib/projector.cjs +211 -42
  55. package/mcp-server/lib/runtime-paths.cjs +1687 -0
  56. package/mcp-server/lib/safe-project-file.cjs +385 -0
  57. package/mcp-server/lib/spec-learning.cjs +28 -12
  58. package/mcp-server/lib/state-authority.cjs +19 -3
  59. package/mcp-server/lib/state-db.cjs +263 -9
  60. package/mcp-server/lib/state-ops.cjs +254 -50
  61. package/mcp-server/lib/task-ledger.cjs +1342 -0
  62. package/mcp-server/lib/telemetry.cjs +12 -3
  63. package/mcp-server/lib/workflow-state.cjs +610 -58
  64. package/mcp-server/server.cjs +423 -42
  65. package/orchestrator/daemon.cjs +2 -10
  66. package/orchestrator/parallel-orchestrator.cjs +314 -50
  67. package/orchestrator/planner/plan-builder.cjs +1 -1
  68. package/orchestrator/recovery.cjs +125 -7
  69. package/orchestrator/session-close-journal-worker.cjs +5 -0
  70. package/orchestrator/session-close-journal.cjs +1014 -0
  71. package/orchestrator/session-runner.cjs +679 -69
  72. package/orchestrator/worktree-manager.cjs +186 -35
  73. package/package.json +2 -3
  74. package/skills/cc-collab/SKILL.md +4 -1
  75. package/skills/codex-collab/SKILL.md +5 -2
  76. package/skills/ultra-change/SKILL.md +34 -8
  77. package/skills/ultra-deliver/SKILL.md +26 -11
  78. package/skills/ultra-dev/SKILL.md +52 -10
  79. package/skills/ultra-doctor/SKILL.md +16 -3
  80. package/skills/ultra-init/SKILL.md +22 -11
  81. package/skills/ultra-plan/SKILL.md +58 -18
  82. package/skills/ultra-research/SKILL.md +37 -35
  83. package/skills/ultra-review/SKILL.md +14 -8
  84. package/skills/ultra-status/SKILL.md +18 -9
  85. package/skills/ultra-test/SKILL.md +11 -5
  86. package/skills/ultra-think/SKILL.md +6 -2
  87. package/skills/ultra-think/references/decision-dialogue.md +17 -6
  88. package/skills/ultra-verify/SKILL.md +4 -1
  89. package/spec/cli-protocol.md +21 -7
  90. package/spec/mcp-tools.yaml +629 -44
  91. package/spec/schemas/change-delta.v1.schema.json +153 -0
  92. package/spec/schemas/context-file.v4.5.schema.json +1 -1
  93. package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
  94. package/spec/schemas/mcp-tools.schema.json +1 -1
  95. package/spec/schemas/state-db.sql +51 -14
  96. package/spec/schemas/task-ledger.v1.schema.json +217 -0
  97. package/spec/schemas/tasks.v4.5.schema.json +1 -1
  98. package/ultra-tools/cli.cjs +3 -2
  99. package/ultra-tools/commands/db.cjs +76 -25
  100. package/ultra-tools/commands/migrate.cjs +1000 -82
  101. package/ultra-tools/commands/session.cjs +34 -8
  102. package/ultra-tools/commands/status.cjs +32 -6
  103. package/ultra-tools/commands/system.cjs +129 -22
  104. package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -19
  105. package/commands/learn.md +0 -10
  106. package/orchestrator/skill-miner.cjs +0 -133
  107. package/output-styles/coding-vibes.md +0 -67
  108. package/output-styles/structural-thinking.md +0 -61
  109. package/skills/learn/SKILL.md +0 -58
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env python3
2
+ """Perform bounded archive mutations relative to inherited directory fds."""
3
+
4
+ import base64
5
+ import json
6
+ import os
7
+ import stat
8
+ import sys
9
+
10
+
11
+ MAX_REQUEST_BYTES = 16 * 1024 * 1024
12
+ MAX_DATA_BYTES = 8 * 1024 * 1024
13
+ MAX_BASENAME_BYTES = 255
14
+ ARCHIVE_FILES = frozenset((
15
+ ".archive-intent.json",
16
+ ".archive-rebind.json",
17
+ "archive-summary.md",
18
+ ))
19
+
20
+ OPERATION_FIELDS = {
21
+ "mkdir_dir": frozenset((
22
+ "operation", "directory_fd", "directory_identity", "name",
23
+ )),
24
+ "write_atomic": frozenset((
25
+ "operation", "directory_fd", "directory_identity", "name",
26
+ "temp_name", "replace", "data_base64",
27
+ )),
28
+ "write_rebind_atomic": frozenset((
29
+ "operation", "directory_fd", "directory_identity", "name",
30
+ "temp_name", "replace", "data_base64",
31
+ )),
32
+ "unlink_regular": frozenset((
33
+ "operation", "directory_fd", "directory_identity", "name",
34
+ "allow_missing",
35
+ )),
36
+ "rename_dir": frozenset((
37
+ "operation", "source_parent_fd", "destination_parent_fd",
38
+ "guard_parent_fd", "source_parent_identity",
39
+ "destination_parent_identity", "guard_parent_identity",
40
+ "source_parent_name", "destination_parent_name", "source_name",
41
+ "destination_name", "source_identity",
42
+ )),
43
+ }
44
+
45
+
46
+ class MutationError(Exception):
47
+ code = "ARCHIVE_PATH_UNSAFE"
48
+
49
+
50
+ class ProtocolError(MutationError):
51
+ code = "ARCHIVE_MUTATION_INVALID"
52
+
53
+
54
+ class RuntimePrerequisiteError(MutationError):
55
+ code = "ARCHIVE_RUNTIME_UNAVAILABLE"
56
+
57
+
58
+ def identity(value):
59
+ return {
60
+ "dev": str(value.st_dev),
61
+ "ino": str(value.st_ino),
62
+ "mode": str(value.st_mode),
63
+ }
64
+
65
+
66
+ def require_identity_shape(value, label):
67
+ if (
68
+ not isinstance(value, dict)
69
+ or set(value) != {"dev", "ino", "mode"}
70
+ or any(
71
+ not isinstance(value[field], str) or not value[field].isdigit()
72
+ for field in ("dev", "ino", "mode")
73
+ )
74
+ ):
75
+ raise ProtocolError(f"{label} has an invalid identity")
76
+
77
+
78
+ def require_identity(actual, expected, label):
79
+ if identity(actual) != expected:
80
+ raise MutationError(f"{label} changed identity")
81
+
82
+
83
+ def require_fd(value, label):
84
+ if isinstance(value, bool) or not isinstance(value, int) or value < 3 or value > 5:
85
+ raise ProtocolError(f"{label} is outside the inherited descriptor boundary")
86
+
87
+
88
+ def require_basename(value, label):
89
+ if (
90
+ not isinstance(value, str)
91
+ or not value
92
+ or value in (".", "..")
93
+ or "/" in value
94
+ or "\\" in value
95
+ or "\x00" in value
96
+ or len(os.fsencode(value)) > MAX_BASENAME_BYTES
97
+ ):
98
+ raise ProtocolError(f"{label} must be one bounded basename")
99
+
100
+
101
+ def require_platform_support():
102
+ if os.name != "posix":
103
+ raise RuntimePrerequisiteError("archive mutations require a POSIX Python runtime")
104
+ required_dir_fd = (os.open, os.stat, os.rename, os.unlink, os.mkdir)
105
+ if any(operation not in os.supports_dir_fd for operation in required_dir_fd):
106
+ raise RuntimePrerequisiteError(
107
+ "Python dir_fd operations required by archive mutations are unavailable"
108
+ )
109
+ if os.stat not in os.supports_follow_symlinks or not hasattr(os, "O_NOFOLLOW"):
110
+ raise RuntimePrerequisiteError(
111
+ "Python no-follow filesystem operations required by archive mutations are unavailable"
112
+ )
113
+
114
+
115
+ def validate_request(request):
116
+ if not isinstance(request, dict):
117
+ raise ProtocolError("archive mutation request must be an object")
118
+ operation = request.get("operation")
119
+ expected = OPERATION_FIELDS.get(operation)
120
+ if expected is None or set(request) != expected:
121
+ raise ProtocolError("archive mutation operation or fields are not allowlisted")
122
+
123
+ for field in (name for name in expected if name.endswith("_fd")):
124
+ require_fd(request[field], field)
125
+ for field in (name for name in expected if name.endswith("_identity")):
126
+ require_identity_shape(request[field], field)
127
+ for field in (
128
+ name for name in expected
129
+ if name == "name" or name.endswith("_name")
130
+ ):
131
+ require_basename(request[field], field)
132
+
133
+ if operation == "mkdir_dir" and request["name"] != "archive":
134
+ raise ProtocolError("archive mkdir can create only the archive root")
135
+ if operation in ("write_atomic", "unlink_regular") and request["name"] not in ARCHIVE_FILES:
136
+ raise ProtocolError("archive file mutation target is not allowlisted")
137
+ if operation in ("write_atomic", "write_rebind_atomic"):
138
+ if type(request["replace"]) is not bool:
139
+ raise ProtocolError("replace must be a boolean")
140
+ if request["temp_name"] == request["name"]:
141
+ raise ProtocolError("archive publication temp name must be distinct")
142
+ encoded = request["data_base64"]
143
+ if not isinstance(encoded, str):
144
+ raise ProtocolError("archive publication data must be base64 text")
145
+ try:
146
+ data = base64.b64decode(encoded, validate=True)
147
+ except (ValueError, TypeError) as error:
148
+ raise ProtocolError("archive publication data is invalid base64") from error
149
+ if len(data) > MAX_DATA_BYTES:
150
+ raise ProtocolError("archive publication data exceeds the protocol limit")
151
+ request["_data"] = data
152
+ if operation == "unlink_regular" and type(request["allow_missing"]) is not bool:
153
+ raise ProtocolError("allow_missing must be a boolean")
154
+ if operation == "rename_dir":
155
+ parent_names = {
156
+ request["source_parent_name"],
157
+ request["destination_parent_name"],
158
+ }
159
+ if parent_names != {"active", "archive"}:
160
+ raise ProtocolError("archive transition parents must be active and archive")
161
+ return operation
162
+
163
+
164
+ def require_directory_fd(fd, expected, label):
165
+ current = os.fstat(fd)
166
+ if not stat.S_ISDIR(current.st_mode):
167
+ raise MutationError(f"{label} is not a directory")
168
+ require_identity(current, expected, label)
169
+
170
+
171
+ def lstat_at(fd, name):
172
+ try:
173
+ return os.stat(name, dir_fd=fd, follow_symlinks=False)
174
+ except FileNotFoundError:
175
+ return None
176
+
177
+
178
+ def require_canonical_directory(fd, name, expected, label):
179
+ current = lstat_at(fd, name)
180
+ if current is None or not stat.S_ISDIR(current.st_mode):
181
+ raise MutationError(f"{label} is missing or unsafe")
182
+ require_identity(current, expected, label)
183
+
184
+
185
+ def write_all(fd, data):
186
+ offset = 0
187
+ while offset < len(data):
188
+ written = os.write(fd, data[offset:])
189
+ if written <= 0:
190
+ raise MutationError("short archive publication write")
191
+ offset += written
192
+
193
+
194
+ def write_atomic(request):
195
+ fd = request["directory_fd"]
196
+ expected = request["directory_identity"]
197
+ name = request["name"]
198
+ temp_name = request["temp_name"]
199
+ replace = request["replace"]
200
+ require_directory_fd(fd, expected, "archive write directory")
201
+ current = lstat_at(fd, name)
202
+ if current is not None:
203
+ if not replace or not stat.S_ISREG(current.st_mode):
204
+ raise MutationError("archive publication target is not safely replaceable")
205
+ flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW
206
+ temp_fd = None
207
+ published = False
208
+ try:
209
+ temp_fd = os.open(temp_name, flags, 0o600, dir_fd=fd)
210
+ write_all(temp_fd, request["_data"])
211
+ os.fsync(temp_fd)
212
+ os.close(temp_fd)
213
+ temp_fd = None
214
+ require_directory_fd(fd, expected, "archive write directory")
215
+ os.rename(temp_name, name, src_dir_fd=fd, dst_dir_fd=fd)
216
+ published = True
217
+ require_directory_fd(fd, expected, "archive write directory")
218
+ final = lstat_at(fd, name)
219
+ if final is None or not stat.S_ISREG(final.st_mode):
220
+ raise MutationError("archive publication did not produce a regular file")
221
+ finally:
222
+ if temp_fd is not None:
223
+ os.close(temp_fd)
224
+ if not published:
225
+ try:
226
+ os.unlink(temp_name, dir_fd=fd)
227
+ except FileNotFoundError:
228
+ pass
229
+
230
+
231
+ def unlink_regular(request):
232
+ fd = request["directory_fd"]
233
+ expected = request["directory_identity"]
234
+ require_directory_fd(fd, expected, "archive cleanup directory")
235
+ current = lstat_at(fd, request["name"])
236
+ if current is None:
237
+ if request["allow_missing"]:
238
+ return
239
+ raise MutationError("archive cleanup target is missing")
240
+ if not stat.S_ISREG(current.st_mode):
241
+ raise MutationError("archive cleanup target is not a regular file")
242
+ os.unlink(request["name"], dir_fd=fd)
243
+ require_directory_fd(fd, expected, "archive cleanup directory")
244
+ if lstat_at(fd, request["name"]) is not None:
245
+ raise MutationError("archive cleanup target still exists")
246
+
247
+
248
+ def rename_dir(request):
249
+ source_parent_fd = request["source_parent_fd"]
250
+ destination_parent_fd = request["destination_parent_fd"]
251
+ guard_parent_fd = request["guard_parent_fd"]
252
+ require_directory_fd(
253
+ source_parent_fd,
254
+ request["source_parent_identity"],
255
+ "archive source parent",
256
+ )
257
+ require_directory_fd(
258
+ destination_parent_fd,
259
+ request["destination_parent_identity"],
260
+ "archive destination parent",
261
+ )
262
+ require_directory_fd(
263
+ guard_parent_fd,
264
+ request["guard_parent_identity"],
265
+ "archive guard parent",
266
+ )
267
+ require_canonical_directory(
268
+ guard_parent_fd,
269
+ request["source_parent_name"],
270
+ request["source_parent_identity"],
271
+ "canonical archive source parent",
272
+ )
273
+ require_canonical_directory(
274
+ guard_parent_fd,
275
+ request["destination_parent_name"],
276
+ request["destination_parent_identity"],
277
+ "canonical archive destination parent",
278
+ )
279
+ source = lstat_at(source_parent_fd, request["source_name"])
280
+ if source is None or not stat.S_ISDIR(source.st_mode):
281
+ raise MutationError("archive source directory is missing or unsafe")
282
+ require_identity(source, request["source_identity"], "archive source directory")
283
+ if lstat_at(destination_parent_fd, request["destination_name"]) is not None:
284
+ raise MutationError("archive destination already exists")
285
+ os.rename(
286
+ request["source_name"],
287
+ request["destination_name"],
288
+ src_dir_fd=source_parent_fd,
289
+ dst_dir_fd=destination_parent_fd,
290
+ )
291
+ require_directory_fd(
292
+ source_parent_fd,
293
+ request["source_parent_identity"],
294
+ "archive source parent",
295
+ )
296
+ require_directory_fd(
297
+ destination_parent_fd,
298
+ request["destination_parent_identity"],
299
+ "archive destination parent",
300
+ )
301
+ if lstat_at(source_parent_fd, request["source_name"]) is not None:
302
+ raise MutationError("archive source still exists after transition")
303
+ destination = lstat_at(destination_parent_fd, request["destination_name"])
304
+ if destination is None or not stat.S_ISDIR(destination.st_mode):
305
+ raise MutationError("archive destination is missing after transition")
306
+ require_identity(destination, request["source_identity"], "archive destination directory")
307
+
308
+
309
+ def mkdir_dir(request):
310
+ fd = request["directory_fd"]
311
+ expected = request["directory_identity"]
312
+ require_directory_fd(fd, expected, "archive mkdir parent")
313
+ current = lstat_at(fd, request["name"])
314
+ if current is None:
315
+ os.mkdir(request["name"], mode=0o700, dir_fd=fd)
316
+ current = lstat_at(fd, request["name"])
317
+ if current is None or not stat.S_ISDIR(current.st_mode):
318
+ raise MutationError("archive directory cannot be created safely")
319
+ require_directory_fd(fd, expected, "archive mkdir parent")
320
+
321
+
322
+ OPERATIONS = {
323
+ "write_atomic": write_atomic,
324
+ "write_rebind_atomic": write_atomic,
325
+ "unlink_regular": unlink_regular,
326
+ "rename_dir": rename_dir,
327
+ "mkdir_dir": mkdir_dir,
328
+ }
329
+
330
+
331
+ def read_request():
332
+ raw = sys.stdin.buffer.read(MAX_REQUEST_BYTES + 1)
333
+ if len(raw) > MAX_REQUEST_BYTES:
334
+ raise ProtocolError("archive mutation request exceeds the protocol limit")
335
+ try:
336
+ return json.loads(raw.decode("utf-8"))
337
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
338
+ raise ProtocolError("archive mutation request is not valid UTF-8 JSON") from error
339
+
340
+
341
+ def main():
342
+ request = read_request()
343
+ operation_name = validate_request(request)
344
+ require_platform_support()
345
+ OPERATIONS[operation_name](request)
346
+ json.dump({"ok": True}, sys.stdout)
347
+ sys.stdout.write("\n")
348
+
349
+
350
+ if __name__ == "__main__":
351
+ try:
352
+ main()
353
+ except Exception as error: # fail closed at the process boundary
354
+ code = error.code if isinstance(error, MutationError) else "ARCHIVE_PATH_UNSAFE"
355
+ json.dump(
356
+ {
357
+ "ok": False,
358
+ "code": code,
359
+ "error": type(error).__name__,
360
+ "message": str(error),
361
+ },
362
+ sys.stderr,
363
+ )
364
+ sys.stderr.write("\n")
365
+ sys.exit(3 if code == "ARCHIVE_RUNTIME_UNAVAILABLE" else 2)