ultra-builder-pro-cli 5.0.0 → 6.0.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 (159) hide show
  1. package/CHANGELOG.md +113 -3
  2. package/README.md +67 -478
  3. package/README.zh-CN.md +49 -422
  4. package/adapters/_shared/frontmatter.cjs +1 -18
  5. package/adapters/_shared/plugin-core.cjs +17 -83
  6. package/adapters/_shared/runtime-assets.cjs +6 -120
  7. package/adapters/claude.js +8 -24
  8. package/adapters/codex.js +16 -38
  9. package/bin/install.js +35 -57
  10. package/docs/PLUGIN-ISOLATION-CONTRACT.md +13 -101
  11. package/docs/examples/drive/claude-reviewer.sh +53 -0
  12. package/docs/examples/drive/claude-writer.sh +43 -0
  13. package/engine/FORMATS.md +1051 -0
  14. package/engine/checks.cjs +139 -0
  15. package/engine/cli.cjs +148 -0
  16. package/engine/completion.cjs +48 -0
  17. package/engine/context.cjs +94 -0
  18. package/engine/drive.cjs +220 -0
  19. package/engine/expect-worker.cjs +5 -0
  20. package/engine/expect.cjs +33 -0
  21. package/engine/formats.cjs +189 -0
  22. package/engine/git-state.cjs +112 -0
  23. package/engine/ledger.cjs +205 -0
  24. package/engine/loading.cjs +28 -0
  25. package/engine/merge.cjs +273 -0
  26. package/engine/milestone.cjs +89 -0
  27. package/engine/plan.cjs +232 -0
  28. package/engine/project.cjs +5 -0
  29. package/engine/signals.cjs +3 -0
  30. package/engine/states.cjs +175 -0
  31. package/hooks/session-start.cjs +54 -0
  32. package/package.json +17 -52
  33. package/skills/ultra/SKILL.md +395 -0
  34. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  35. package/.ultra-template/changes/active/.gitkeep +0 -1
  36. package/.ultra-template/changes/archive/.gitkeep +0 -1
  37. package/.ultra-template/contexts/TEMPLATE.md +0 -135
  38. package/.ultra-template/decisions/.gitkeep +0 -1
  39. package/.ultra-template/evidence/.gitkeep +0 -1
  40. package/.ultra-template/north-star.md +0 -52
  41. package/.ultra-template/project-brief.md +0 -28
  42. package/.ultra-template/research/.gitkeep +0 -1
  43. package/.ultra-template/specs/architecture.md +0 -81
  44. package/.ultra-template/specs/discovery.md +0 -74
  45. package/.ultra-template/specs/product.md +0 -80
  46. package/.ultra-template/specs/research-distillate.md +0 -57
  47. package/.ultra-template/tasks.json +0 -4
  48. package/.ultra-template/test-report.json +0 -44
  49. package/adapters/_shared/host-profile.cjs +0 -5
  50. package/adapters/grok.js +0 -242
  51. package/adapters/kimi.js +0 -232
  52. package/adapters/opencode.js +0 -408
  53. package/adapters/zcode.js +0 -375
  54. package/bin/delegate-worker.cjs +0 -5
  55. package/bin/delegate.cjs +0 -5
  56. package/docs/ARCHITECTURE.md +0 -274
  57. package/docs/ARTIFACT-AUTHORITY.md +0 -305
  58. package/docs/DECISIONS.md +0 -229
  59. package/docs/PHILOSOPHY.md +0 -261
  60. package/docs/RUNTIME-COMPAT-MATRIX.md +0 -315
  61. package/docs/SKILL-AUTHORING.md +0 -113
  62. package/docs/WORKFLOW-LIFECYCLE.md +0 -350
  63. package/docs/evals/adversarial-review-2026-08-14.md +0 -84
  64. package/docs/evals/ultra-5-0-journey-results.md +0 -62
  65. package/docs/evals/ultra-5-0-live-acp-conformance.md +0 -93
  66. package/docs/evals/ultra-5-0-route-journeys.md +0 -145
  67. package/docs/evals/zcode-automation-2026-08-14.md +0 -103
  68. package/hooks/README.md +0 -52
  69. package/hooks/_common.py +0 -1779
  70. package/hooks/adapters/codex.py +0 -295
  71. package/hooks/adapters/grok.py +0 -134
  72. package/hooks/adapters/kimi.py +0 -156
  73. package/hooks/adapters/zcode.py +0 -227
  74. package/hooks/block_dangerous_commands.py +0 -201
  75. package/hooks/compact_context.py +0 -224
  76. package/hooks/mid_workflow_recall.py +0 -47
  77. package/hooks/post_edit_guard.py +0 -215
  78. package/hooks/session_context.py +0 -741
  79. package/skills/ultra-change/SKILL.md +0 -165
  80. package/skills/ultra-change/references/change-contract.md +0 -199
  81. package/skills/ultra-change/references/execution-grant.md +0 -109
  82. package/skills/ultra-change/references/primary-transfer.md +0 -273
  83. package/skills/ultra-change/scripts/validate_primary_transfer.cjs +0 -1928
  84. package/skills/ultra-delegate/SKILL.md +0 -195
  85. package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +0 -184
  86. package/skills/ultra-delegate/references/delegation-contract.md +0 -138
  87. package/skills/ultra-delegate/scripts/acp-conformance.cjs +0 -287
  88. package/skills/ultra-delegate/scripts/acp-driver.cjs +0 -762
  89. package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +0 -320
  90. package/skills/ultra-delegate/scripts/cli-driver.cjs +0 -61
  91. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-read-only.md +0 -15
  92. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-write.md +0 -17
  93. package/skills/ultra-delegate/scripts/delegate-worker.cjs +0 -328
  94. package/skills/ultra-delegate/scripts/delegate.cjs +0 -690
  95. package/skills/ultra-delegate/scripts/delegate_wait.py +0 -86
  96. package/skills/ultra-delegate/scripts/host-profile.cjs +0 -267
  97. package/skills/ultra-deliver/SKILL.md +0 -194
  98. package/skills/ultra-deliver/references/baseline-reconciliation.md +0 -41
  99. package/skills/ultra-dev/SKILL.md +0 -265
  100. package/skills/ultra-dev/references/debugging.md +0 -22
  101. package/skills/ultra-domain-modeling/SKILL.md +0 -79
  102. package/skills/ultra-grilling/SKILL.md +0 -121
  103. package/skills/ultra-grilling/references/reframing.md +0 -76
  104. package/skills/ultra-init/SKILL.md +0 -143
  105. package/skills/ultra-init/references/brownfield-adoption.md +0 -32
  106. package/skills/ultra-init/scripts/init_project.cjs +0 -1031
  107. package/skills/ultra-plan/SKILL.md +0 -160
  108. package/skills/ultra-plan/references/task-evidence-v2.md +0 -300
  109. package/skills/ultra-plan/scripts/validate_task_evidence.cjs +0 -859
  110. package/skills/ultra-research/SKILL.md +0 -167
  111. package/skills/ultra-research/references/00-problem-validation.md +0 -36
  112. package/skills/ultra-research/references/01-opportunity-discovery.md +0 -33
  113. package/skills/ultra-research/references/02-market-assessment.md +0 -31
  114. package/skills/ultra-research/references/03-alternatives.md +0 -31
  115. package/skills/ultra-research/references/04-product-strategy.md +0 -41
  116. package/skills/ultra-research/references/05-assumptions-validation.md +0 -31
  117. package/skills/ultra-research/references/10-user-personas.md +0 -30
  118. package/skills/ultra-research/references/11-user-scenarios.md +0 -28
  119. package/skills/ultra-research/references/20-user-stories.md +0 -31
  120. package/skills/ultra-research/references/21-features-scope.md +0 -38
  121. package/skills/ultra-research/references/22-success-metrics.md +0 -33
  122. package/skills/ultra-research/references/30-architecture-context.md +0 -30
  123. package/skills/ultra-research/references/31-solution-strategy.md +0 -28
  124. package/skills/ultra-research/references/32-building-blocks.md +0 -28
  125. package/skills/ultra-research/references/40-deployment.md +0 -29
  126. package/skills/ultra-research/references/41-quality-risks.md +0 -28
  127. package/skills/ultra-research/references/99-synthesis.md +0 -59
  128. package/skills/ultra-research/references/north-star-v2.md +0 -168
  129. package/skills/ultra-research/references/wayfinding.md +0 -50
  130. package/skills/ultra-research/scripts/validate_north_star.cjs +0 -1174
  131. package/skills/ultra-review/SKILL.md +0 -275
  132. package/skills/ultra-review/references/adversarial-evaluation.md +0 -31
  133. package/skills/ultra-review/references/code.md +0 -17
  134. package/skills/ultra-review/references/comments.md +0 -12
  135. package/skills/ultra-review/references/design.md +0 -12
  136. package/skills/ultra-review/references/errors.md +0 -13
  137. package/skills/ultra-review/references/review-modes.md +0 -29
  138. package/skills/ultra-review/references/spec.md +0 -18
  139. package/skills/ultra-review/references/tests.md +0 -12
  140. package/skills/ultra-review/references/unified-schema.md +0 -284
  141. package/skills/ultra-review/references/worker-packet.md +0 -183
  142. package/skills/ultra-review/scripts/review_wait.py +0 -2943
  143. package/skills/ultra-status/SKILL.md +0 -209
  144. package/skills/ultra-status/references/route-contract.md +0 -100
  145. package/skills/ultra-tdd/SKILL.md +0 -91
  146. package/skills/ultra-tdd/references/templates/README.md +0 -20
  147. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +0 -79
  148. package/skills/ultra-tdd/references/templates/persistence-real.ts +0 -78
  149. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +0 -78
  150. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +0 -73
  151. package/skills/ultra-tdd/references/templates/vertical-slice.ts +0 -66
  152. package/skills/ultra-tdd/references/test-execution.md +0 -23
  153. package/skills/ultra-test/SKILL.md +0 -244
  154. package/skills/ultra-test/references/export-syntax.md +0 -18
  155. package/skills/ultra-test/scripts/validate_review_transport.cjs +0 -549
  156. package/skills/ultra-test/scripts/worktree_digest.cjs +0 -576
  157. package/skills/ultra-think/SKILL.md +0 -56
  158. package/skills/ultra-think/references/autonomy-boundary.md +0 -48
  159. package/skills/ultra-think/references/interaction-boundary.md +0 -42
@@ -1,227 +0,0 @@
1
- #!/usr/bin/env python3
2
- """ZCode wire adapter for Ultra's five file-first lifecycle hooks."""
3
-
4
- from __future__ import annotations
5
-
6
- import copy
7
- import json
8
- import os
9
- import re
10
- import subprocess
11
- import sys
12
- from pathlib import Path
13
- from typing import Any
14
-
15
-
16
- HOOK_ROOT = Path(__file__).resolve().parent.parent
17
- ALLOWED_FEATURES = {
18
- "block_dangerous_commands.py",
19
- "compact_context.py",
20
- "mid_workflow_recall.py",
21
- "post_edit_guard.py",
22
- "session_context.py",
23
- }
24
- PATCH_FILE_RE = re.compile(r"^\*\*\* (?:Add|Update|Delete) File: (.+?)\s*$", re.MULTILINE)
25
- PATCH_MOVE_RE = re.compile(r"^\*\*\* Move to: (.+?)\s*$", re.MULTILINE)
26
-
27
-
28
- def normalize_input(payload: dict[str, Any]) -> dict[str, Any]:
29
- """Translate ZCode's camelCase hook wire names to the shared contract."""
30
- normalized = copy.deepcopy(payload)
31
- aliases = {
32
- "hookEventName": "hook_event_name",
33
- "sessionId": "session_id",
34
- "toolName": "tool_name",
35
- "toolInput": "tool_input",
36
- "toolResult": "tool_output",
37
- "toolResponse": "tool_output",
38
- "workspaceRoot": "workspace_root",
39
- }
40
- for source, target in aliases.items():
41
- if source in normalized and target not in normalized:
42
- normalized[target] = normalized[source]
43
- if "cwd" not in normalized and isinstance(normalized.get("workspace_root"), str):
44
- normalized["cwd"] = normalized["workspace_root"]
45
- return normalized
46
-
47
-
48
- def _patch_paths(tool_input: Any) -> list[str]:
49
- if not isinstance(tool_input, dict):
50
- return []
51
- patch = tool_input.get("command") or tool_input.get("patch") or ""
52
- if not isinstance(patch, str):
53
- return []
54
- paths: list[str] = []
55
- for match in [*PATCH_FILE_RE.findall(patch), *PATCH_MOVE_RE.findall(patch)]:
56
- candidate = match.strip()
57
- if candidate and candidate not in paths:
58
- paths.append(candidate)
59
- return paths
60
-
61
-
62
- def normalize_inputs(payload: dict[str, Any]) -> list[dict[str, Any]]:
63
- normalized = normalize_input(payload)
64
- if normalized.get("tool_name") != "ApplyPatch":
65
- return [normalized]
66
- paths = _patch_paths(normalized.get("tool_input"))
67
- if not paths:
68
- normalized["tool_name"] = "Edit"
69
- return [normalized]
70
- results: list[dict[str, Any]] = []
71
- for file_path in paths:
72
- item = copy.deepcopy(normalized)
73
- item["tool_name"] = "Edit"
74
- tool_input = item.get("tool_input")
75
- item["tool_input"] = dict(tool_input) if isinstance(tool_input, dict) else {}
76
- item["tool_input"]["file_path"] = file_path
77
- results.append(item)
78
- return results
79
-
80
-
81
- def _parse_output(stdout: str) -> dict[str, Any]:
82
- text = stdout.strip()
83
- if not text:
84
- return {}
85
- try:
86
- value = json.loads(text)
87
- except json.JSONDecodeError:
88
- return {"systemMessage": f"Ultra hook returned non-JSON output: {text[:500]}"}
89
- return value if isinstance(value, dict) else {}
90
-
91
-
92
- def adapt_output(output: dict[str, Any], event: str) -> dict[str, Any]:
93
- """Project shared output onto ZCode's strict event-specific schema."""
94
- if not isinstance(output, dict):
95
- return {}
96
- specific = output.get("hookSpecificOutput")
97
- specific = specific if isinstance(specific, dict) else {}
98
- context = specific.get("additionalContext") or output.get("additionalContext")
99
-
100
- if event == "PreToolUse":
101
- decision = specific.get("permissionDecision")
102
- if decision in {"allow", "ask", "deny"}:
103
- projected: dict[str, Any] = {
104
- "hookEventName": "PreToolUse",
105
- "permissionDecision": decision,
106
- }
107
- reason = specific.get("permissionDecisionReason") or output.get("reason")
108
- if reason:
109
- projected["permissionDecisionReason"] = str(reason)
110
- if context:
111
- projected["additionalContext"] = str(context)
112
- return {"hookSpecificOutput": projected}
113
- if output.get("decision") == "block":
114
- reason = str(output.get("reason") or "Ultra blocked this tool call")
115
- return {
116
- "hookSpecificOutput": {
117
- "hookEventName": "PreToolUse",
118
- "permissionDecision": "deny",
119
- "permissionDecisionReason": reason,
120
- }
121
- }
122
- return {}
123
-
124
- if event in {
125
- "SessionStart", "UserPromptSubmit", "PostToolUse",
126
- "PostToolUseFailure", "Stop",
127
- } and context:
128
- return {
129
- "hookSpecificOutput": {
130
- "hookEventName": event,
131
- "additionalContext": str(context),
132
- }
133
- }
134
- return {}
135
-
136
-
137
- def _merge_outputs(outputs: list[dict[str, Any]], event: str) -> dict[str, Any]:
138
- if not outputs:
139
- return {}
140
- for output in outputs:
141
- specific = output.get("hookSpecificOutput")
142
- if isinstance(specific, dict) and specific.get("permissionDecision") == "deny":
143
- return output
144
- contexts = []
145
- for output in outputs:
146
- specific = output.get("hookSpecificOutput")
147
- if isinstance(specific, dict) and specific.get("additionalContext"):
148
- contexts.append(str(specific["additionalContext"]))
149
- if contexts:
150
- return {
151
- "hookSpecificOutput": {
152
- "hookEventName": event,
153
- "additionalContext": "\n\n".join(contexts),
154
- }
155
- }
156
- return outputs[-1]
157
-
158
-
159
- def run_feature(feature: str, payload: dict[str, Any], feature_args: list[str]) -> dict[str, Any]:
160
- if feature not in ALLOWED_FEATURES or Path(feature).name != feature:
161
- return {"systemMessage": f"Ultra hook adapter refused unknown feature: {feature}"}
162
- script = HOOK_ROOT / feature
163
- if not script.is_file():
164
- return {"systemMessage": f"Ultra hook feature is missing: {feature}"}
165
-
166
- event = str(payload.get("hookEventName") or payload.get("hook_event_name") or "")
167
- normalized_items = normalize_inputs(payload)
168
- env = os.environ.copy()
169
- env["UBP_HOOK_RUNTIME"] = "zcode"
170
- plugin_data = env.get("ZCODE_PLUGIN_DATA") or env.get("CLAUDE_PLUGIN_DATA")
171
- if plugin_data:
172
- env["UBP_HOOK_DATA"] = plugin_data
173
- cwd = normalized_items[0].get("cwd") if normalized_items else None
174
- run_cwd = str(cwd) if isinstance(cwd, str) and Path(cwd).is_dir() else os.getcwd()
175
-
176
- outputs: list[dict[str, Any]] = []
177
- for normalized in normalized_items:
178
- proc = subprocess.run(
179
- [sys.executable, str(script), *feature_args],
180
- input=json.dumps(normalized),
181
- text=True,
182
- capture_output=True,
183
- cwd=run_cwd,
184
- env=env,
185
- timeout=30,
186
- check=False,
187
- )
188
- if proc.stderr:
189
- print(proc.stderr.rstrip(), file=sys.stderr)
190
- if proc.returncode != 0:
191
- outputs.append({
192
- "systemMessage": (
193
- f"Ultra hook {feature} failed with exit {proc.returncode}: "
194
- f"{proc.stderr.strip()[:500]}"
195
- )
196
- })
197
- continue
198
- outputs.append(adapt_output(_parse_output(proc.stdout), event))
199
- return _merge_outputs(outputs, event)
200
-
201
-
202
- def main() -> int:
203
- if len(sys.argv) < 2:
204
- print(json.dumps({"systemMessage": "Ultra ZCode hook adapter requires a feature name"}))
205
- return 0
206
- try:
207
- payload = json.loads(sys.stdin.read() or "{}")
208
- if not isinstance(payload, dict):
209
- payload = {}
210
- except json.JSONDecodeError as exc:
211
- print(json.dumps({"systemMessage": f"Ultra hook input parse error: {exc}"}))
212
- return 0
213
- try:
214
- output = run_feature(sys.argv[1], payload, sys.argv[2:])
215
- except subprocess.TimeoutExpired:
216
- output = {"systemMessage": f"Ultra hook {sys.argv[1]} timed out"}
217
- except Exception as exc:
218
- output = {"systemMessage": f"Ultra hook adapter error in {sys.argv[1]}: {exc}"}
219
- # ZCode's hook wire schema is strict. Its official contract explicitly accepts an
220
- # empty stdout stream as a successful no-op, so do not serialize an empty object.
221
- if output:
222
- print(json.dumps(output, ensure_ascii=False))
223
- return 0
224
-
225
-
226
- if __name__ == "__main__":
227
- raise SystemExit(main())
@@ -1,201 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Block a narrow set of externally destructive shell effects."""
3
-
4
- from __future__ import annotations
5
-
6
- import hashlib
7
- import json
8
- import os
9
- import re
10
- import shlex
11
- import subprocess
12
-
13
- from _common import emit_context, project_root, read_payload
14
-
15
-
16
- APPROVAL_VAR = "UBP_DANGEROUS_COMMAND_APPROVED"
17
- APPROVAL_PREFIX = re.compile(rf"^\s*{APPROVAL_VAR}=\S*\s+")
18
-
19
- HEREDOC = re.compile(
20
- r"<<-?\s*(['\"]?)(?P<tag>[A-Za-z_]\w*)\1(?P<rest>[^\n]*)\n(?P<body>.*?)^\s*(?P=tag)\s*$",
21
- re.DOTALL | re.MULTILINE,
22
- )
23
- # Commands whose heredoc is a payload rather than a script. Deliberately short: an
24
- # unlisted sink keeps its body in scope, so the cost of an omission is a false positive,
25
- # never a missed effect.
26
- DATA_SINK = re.compile(r"\bgit\s+(?:commit|tag|notes)\b", re.IGNORECASE)
27
- SEARCH_COMMANDS = {"egrep", "fgrep", "grep", "rg"}
28
- SHELL_CONTROL = {";", ";;", "&", "&&", "|", "||", "(", ")", "\n"}
29
-
30
-
31
- def strip_data_heredocs(command: str) -> str:
32
- """Drop heredoc bodies that are data for a known sink.
33
-
34
- Describing an effect inside a commit message is not performing it, and a message
35
- that mentions one used to be blocked as though it did.
36
-
37
- Bodies are kept whenever they might be executed: an interpreter reading a heredoc
38
- runs it, so stripping there would make quoting style a way around this gate. The
39
- body survives unless the delimiter is quoted, the segment introducing it is a
40
- listed data sink, and nothing on the heredoc's own line pipes it somewhere else.
41
- An unquoted delimiter performs parameter, command and arithmetic expansion before
42
- the sink receives the data, so its body remains executable shell input.
43
- """
44
- def resolve(match: re.Match) -> str:
45
- segment = re.split(r"\|\||&&|[;&|\n]", command[:match.start()])[-1]
46
- if (
47
- not match.group(1)
48
- or "|" in match.group("rest")
49
- or not DATA_SINK.search(segment)
50
- ):
51
- return match.group(0)
52
- tag = match.group("tag")
53
- return f"<<{tag}\n{tag}"
54
-
55
- return HEREDOC.sub(resolve, command)
56
-
57
-
58
- def effect_of(command: str) -> str:
59
- """The command reduced to what it actually does.
60
-
61
- Strips an inline approval assignment, then any heredoc body that is data for a
62
- known sink. What remains is the effect: the part worth classifying and hashing.
63
-
64
- The digest must identify the *effect*, not the spelling of the retry. Without this,
65
- prepending the variable changes the command text, which changes the digest, which
66
- means the quoted digest is never the one that matches -- the repair path is a loop.
67
-
68
- The assigned value is discarded on purpose. Only the process environment authorizes,
69
- because the command string is composed by the model and honouring an inline value
70
- would let it approve itself.
71
- """
72
- return strip_data_heredocs(APPROVAL_PREFIX.sub("", command, count=1))
73
-
74
-
75
- def is_inert_search(command: str) -> bool:
76
- """Return true when a search query is data, not executable shell input."""
77
- if any(marker in command for marker in ("$(", "<(", ">(", "`")):
78
- return False
79
- try:
80
- lexer = shlex.shlex(command, posix=True, punctuation_chars=";&|()\n")
81
- lexer.whitespace = " \t\r"
82
- lexer.whitespace_split = True
83
- lexer.commenters = ""
84
- tokens = list(lexer)
85
- except ValueError:
86
- return False
87
- if not tokens or any(token in SHELL_CONTROL for token in tokens):
88
- return False
89
- return os.path.basename(tokens[0]) in SEARCH_COMMANDS
90
-
91
-
92
- def protected_push(command: str, root) -> bool:
93
- if not re.search(r"\bgit\s+push\b", command, re.IGNORECASE):
94
- return False
95
- if re.search(r"\b(?:main|master|production|prod)\b", command, re.IGNORECASE):
96
- return True
97
- result = subprocess.run(
98
- ["git", "branch", "--show-current"], cwd=root, text=True,
99
- capture_output=True, check=False, timeout=3,
100
- )
101
- return result.stdout.strip() in {"main", "master", "production", "prod"}
102
-
103
-
104
- def destructive_protected_push(command: str, root) -> bool:
105
- """Detect history rewrites or deletion, not ordinary additive publication."""
106
- if not protected_push(command, root):
107
- return False
108
- match = re.search(r"\bgit\s+push\b", command, re.IGNORECASE)
109
- if match is None:
110
- return False
111
- segment = re.split(r"\|\||&&|[;&|\n]", command[match.end():], maxsplit=1)[0]
112
- force_or_delete = re.search(
113
- r"(?:^|\s)(?:-f|--force(?:-with-lease(?:=\S+)?|-if-includes)?|--delete)(?=\s|$)",
114
- segment,
115
- re.IGNORECASE,
116
- )
117
- forced_refspec = re.search(r"(?:^|\s)(?:\+\S+|:\S+)(?=\s|$)", segment)
118
- return force_or_delete is not None or forced_refspec is not None
119
-
120
-
121
- def classify(command: str, root) -> str | None:
122
- if destructive_protected_push(command, root):
123
- return "protected branch history rewrite or deletion"
124
- if re.search(r"\b(?:drop\s+(?:table|database|schema)|truncate\s+(?:table\s+)?)\b", command, re.IGNORECASE):
125
- return "destructive database operation"
126
- if re.search(r"\b(?:cast\s+send|solana\s+transfer|bitcoin-cli\s+send|near\s+send|aptos\s+move\s+run)\b", command, re.IGNORECASE):
127
- return "funds or on-chain transaction"
128
- if re.search(
129
- r"\b(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|SECRET|PASSWORD)\s*=\s*['\"]?(?!\$|\$\{)[^\s'\"]{8,}",
130
- command,
131
- re.IGNORECASE,
132
- ):
133
- return "hard-coded credential"
134
- if re.search(r"\beval\s+['\"]?\$|\bexec\s*\([^)]*\binput\s*\(", command, re.IGNORECASE):
135
- return "user-controlled code execution"
136
- return None
137
-
138
-
139
- def advisory(command: str, root) -> str | None:
140
- if protected_push(command, root):
141
- return (
142
- "Ultra observed a protected branch push. This portable hook cannot receive "
143
- "the host's trusted owner-approval receipt, so it does not replace the host's "
144
- "native authority flow. Proceed only after explicit owner authorization and "
145
- "verify the remote result."
146
- )
147
- if re.search(
148
- r"\b(?:alembic\s+upgrade|prisma\s+migrate|knex\s+migrate|sequelize\s+db:migrate|rails\s+db:migrate|manage\.py\s+migrate)\b",
149
- command,
150
- re.IGNORECASE,
151
- ):
152
- return (
153
- "Ultra observed a database migration command. Confirm the target environment, "
154
- "backup or rollback path, and owner authorization before any irreversible effect."
155
- )
156
- return None
157
-
158
-
159
- def main() -> int:
160
- payload = read_payload()
161
- root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
162
- if root is None:
163
- return 0
164
- tool_input = payload.get("tool_input")
165
- tool_input = tool_input if isinstance(tool_input, dict) else {}
166
- command = tool_input.get("command")
167
- if not isinstance(command, str) or not command.strip():
168
- return 0
169
- effect = effect_of(command)
170
- if is_inert_search(effect):
171
- return 0
172
- threat = classify(effect, root)
173
- if threat is None:
174
- note = advisory(effect, root)
175
- if note:
176
- emit_context("PreToolUse", note)
177
- return 0
178
- digest = hashlib.sha256(effect.encode()).hexdigest()
179
- if os.environ.get(APPROVAL_VAR) == digest:
180
- return 0
181
- reason = (
182
- f"Ultra blocked {threat}. Protected effect: the exact shell command, "
183
- f"digest {digest}. Authorization comes from the owner's environment, never from "
184
- f"the command text. Either run it yourself, or export "
185
- f"{APPROVAL_VAR}={digest} in the environment that launches the agent and ask "
186
- f"again. Prefixing the assignment onto this command does not authorize it."
187
- )
188
- print(json.dumps({
189
- "decision": "block",
190
- "reason": reason,
191
- "hookSpecificOutput": {
192
- "hookEventName": "PreToolUse",
193
- "permissionDecision": "deny",
194
- "permissionDecisionReason": reason,
195
- },
196
- }))
197
- return 0
198
-
199
-
200
- if __name__ == "__main__":
201
- raise SystemExit(main())
@@ -1,224 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Save and restore a disposable compact snapshot from canonical files."""
3
-
4
- from __future__ import annotations
5
-
6
- import subprocess
7
- from datetime import datetime, timezone
8
-
9
- from _common import (
10
- RESUME_NAVIGATION_LIMITATION,
11
- current_task_selection,
12
- emit_context,
13
- project_root,
14
- read_derived_project_file_snapshot,
15
- read_payload,
16
- render_task_diagnostics,
17
- task_sections,
18
- write_derived_project_file_atomic,
19
- )
20
- from session_context import _read_bounded_process_output
21
-
22
-
23
- COMPACT_SNAPSHOT_RELATIVE = ".ultra/.runtime/compact-snapshot.md"
24
- COMPACT_SNAPSHOT_MAX_BYTES = 1024 * 1024
25
- COMPACT_GIT_TIMEOUT_SECONDS = 5
26
- COMPACT_GIT_MAX_OUTPUT_BYTES = 256 * 1024
27
-
28
-
29
- def git_output(root, *args: str):
30
- command = ["git", *args]
31
- rendered = " ".join(command)
32
- try:
33
- process = subprocess.Popen(
34
- command,
35
- cwd=root,
36
- stdin=subprocess.PIPE,
37
- stdout=subprocess.PIPE,
38
- stderr=subprocess.PIPE,
39
- shell=False,
40
- )
41
- except FileNotFoundError:
42
- return "unavailable", {
43
- "code": "compact_git_missing",
44
- "message": f"Compact snapshot could not run `{rendered}` because Git is unavailable.",
45
- "path": str(root),
46
- "command": command,
47
- "repair": (
48
- "Restore a working Git executable and retry PreCompact; canonical task "
49
- "Acceptance Criteria and the navigational Resume Note remain recoverable."
50
- ),
51
- }
52
- except OSError as error:
53
- return "unavailable", {
54
- "code": "compact_git_execution_error",
55
- "message": f"Compact snapshot could not execute `{rendered}`: {error}",
56
- "path": str(root),
57
- "command": command,
58
- "repair": (
59
- "Repair local Git execution and retry PreCompact; canonical task Acceptance "
60
- "Criteria and the navigational Resume Note remain recoverable."
61
- ),
62
- }
63
-
64
- stdout, _stderr, failure = _read_bounded_process_output(
65
- process,
66
- COMPACT_GIT_TIMEOUT_SECONDS,
67
- COMPACT_GIT_MAX_OUTPUT_BYTES,
68
- b"",
69
- )
70
- if failure == "timeout":
71
- return "unavailable", {
72
- "code": "compact_git_timeout",
73
- "message": (
74
- f"Compact snapshot stopped `{rendered}` after the "
75
- f"{COMPACT_GIT_TIMEOUT_SECONDS}-second execution ceiling."
76
- ),
77
- "path": str(root),
78
- "command": command,
79
- "repair": (
80
- "Retry PreCompact after Git becomes responsive; canonical task Acceptance "
81
- "Criteria and the navigational Resume Note remain recoverable."
82
- ),
83
- }
84
- if failure == "output_too_large":
85
- return "unavailable", {
86
- "code": "compact_git_output_too_large",
87
- "message": (
88
- f"Compact snapshot stopped `{rendered}` after combined stdout and stderr "
89
- f"exceeded the {COMPACT_GIT_MAX_OUTPUT_BYTES}-byte ceiling."
90
- ),
91
- "path": str(root),
92
- "command": command,
93
- "repair": (
94
- f"Retry PreCompact after reducing Git command output below the "
95
- f"{COMPACT_GIT_MAX_OUTPUT_BYTES}-byte combined stdout/stderr ceiling; "
96
- "canonical task Acceptance Criteria and the navigational Resume Note remain recoverable."
97
- ),
98
- }
99
- if process.returncode != 0:
100
- return "unavailable", {
101
- "code": "compact_git_nonzero",
102
- "message": (
103
- f"Compact snapshot observed `{rendered}` exit with code "
104
- f"{process.returncode}."
105
- ),
106
- "path": str(root),
107
- "command": command,
108
- "returncode": process.returncode,
109
- "repair": (
110
- "Repair the local Git worktree or repository state and retry PreCompact; "
111
- "canonical task Acceptance Criteria and the navigational Resume Note remain recoverable."
112
- ),
113
- }
114
- return stdout.decode("utf-8", errors="replace").strip(), None
115
-
116
-
117
- def save_snapshot(root):
118
- selection = current_task_selection(root)
119
- task = selection["task"]
120
- sections = (
121
- task_sections(root, task)
122
- if task
123
- else {
124
- "acceptance": "",
125
- "resume": "",
126
- "diagnostics": selection["diagnostics"],
127
- }
128
- )
129
- head, head_failure = git_output(root, "rev-parse", "HEAD")
130
- worktree, worktree_failure = git_output(root, "status", "--short")
131
- git_diagnostics = [
132
- failure
133
- for failure in (head_failure, worktree_failure)
134
- if failure is not None
135
- ]
136
- snapshot_diagnostics = [*sections["diagnostics"], *git_diagnostics]
137
- diagnostics = render_task_diagnostics(snapshot_diagnostics)
138
- text = [
139
- "# Ultra Compact Snapshot",
140
- "",
141
- f"Generated: {datetime.now(timezone.utc).isoformat()}",
142
- f"HEAD: {head}",
143
- f"Task: {task.get('id') if task else 'none'}",
144
- "",
145
- "## Acceptance Criteria",
146
- sections["acceptance"] or "_(none)_",
147
- "",
148
- "## Resume Note",
149
- sections["resume"] or "_(none)_",
150
- "",
151
- RESUME_NAVIGATION_LIMITATION,
152
- "",
153
- "## Task Diagnostics",
154
- diagnostics or "_(none)_",
155
- "",
156
- "## Worktree",
157
- "```text",
158
- worktree,
159
- "```",
160
- "",
161
- ]
162
- write_failure = write_derived_project_file_atomic(
163
- root,
164
- COMPACT_SNAPSHOT_RELATIVE,
165
- "\n".join(text).encode("utf-8"),
166
- max_bytes=COMPACT_SNAPSHOT_MAX_BYTES,
167
- code_prefix="compact_snapshot_write",
168
- label="Ultra compact snapshot",
169
- )
170
- published_diagnostics = list(sections["diagnostics"])
171
- published_diagnostics.extend(git_diagnostics)
172
- if write_failure is not None:
173
- published_diagnostics.append(write_failure)
174
- return published_diagnostics
175
-
176
-
177
- def main() -> int:
178
- payload = read_payload()
179
- root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
180
- if root is None:
181
- return 0
182
- event = str(payload.get("hook_event_name") or payload.get("hookEventName") or "")
183
- if event == "PreCompact":
184
- diagnostics = save_snapshot(root)
185
- if diagnostics:
186
- emit_context(
187
- "PreCompact",
188
- "Ultra compact snapshot diagnostic:\n"
189
- + render_task_diagnostics(diagnostics),
190
- )
191
- return 0
192
- if event in {"PostCompact", "SessionStart"} and (
193
- event == "PostCompact" or payload.get("source") == "compact"
194
- ):
195
- snapshot, failure = read_derived_project_file_snapshot(
196
- root,
197
- COMPACT_SNAPSHOT_RELATIVE,
198
- max_bytes=COMPACT_SNAPSHOT_MAX_BYTES,
199
- code_prefix="compact_snapshot_read",
200
- label="Ultra compact snapshot",
201
- )
202
- if snapshot is not None:
203
- try:
204
- text = snapshot["bytes"].decode("utf-8")
205
- except UnicodeDecodeError:
206
- failure = {
207
- "code": "compact_snapshot_read_invalid_utf8",
208
- "message": "Ultra compact snapshot must be valid UTF-8 Markdown.",
209
- "path": snapshot["path"],
210
- }
211
- else:
212
- emit_context("SessionStart", text)
213
- return 0
214
- if failure is not None and not failure.get("code", "").endswith("_missing"):
215
- emit_context(
216
- "SessionStart",
217
- "Ultra compact snapshot diagnostic:\n"
218
- + render_task_diagnostics([failure]),
219
- )
220
- return 0
221
-
222
-
223
- if __name__ == "__main__":
224
- raise SystemExit(main())