ultra-builder-pro-cli 0.26.2 → 5.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.
- package/.ultra-template/contexts/TEMPLATE.md +70 -29
- package/.ultra-template/north-star.md +35 -18
- package/.ultra-template/tasks.json +1 -0
- package/.ultra-template/test-report.json +17 -1
- package/CHANGELOG.md +235 -1
- package/README.md +210 -50
- package/README.zh-CN.md +440 -0
- package/adapters/_shared/host-profile.cjs +3 -95
- package/adapters/_shared/plugin-core.cjs +61 -5
- package/adapters/_shared/runtime-assets.cjs +66 -5
- package/adapters/zcode.js +375 -0
- package/bin/delegate-worker.cjs +2 -325
- package/bin/delegate.cjs +3 -461
- package/bin/install.js +11 -6
- package/docs/ARCHITECTURE.md +133 -31
- package/docs/ARTIFACT-AUTHORITY.md +185 -24
- package/docs/DECISIONS.md +153 -18
- package/docs/PHILOSOPHY.md +58 -7
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +34 -12
- package/docs/RUNTIME-COMPAT-MATRIX.md +195 -23
- package/docs/SKILL-AUTHORING.md +19 -6
- package/docs/WORKFLOW-LIFECYCLE.md +196 -44
- package/docs/evals/adversarial-review-2026-08-14.md +84 -0
- package/docs/evals/ultra-5-0-journey-results.md +62 -0
- package/docs/evals/ultra-5-0-live-acp-conformance.md +93 -0
- package/docs/evals/ultra-5-0-route-journeys.md +145 -0
- package/docs/evals/zcode-automation-2026-08-14.md +103 -0
- package/hooks/README.md +27 -4
- package/hooks/_common.py +1678 -61
- package/hooks/adapters/zcode.py +227 -0
- package/hooks/compact_context.py +176 -17
- package/hooks/mid_workflow_recall.py +27 -5
- package/hooks/post_edit_guard.py +133 -23
- package/hooks/session_context.py +707 -22
- package/package.json +7 -3
- package/skills/ultra-change/SKILL.md +69 -12
- package/skills/ultra-change/references/change-contract.md +93 -27
- package/skills/ultra-change/references/execution-grant.md +109 -0
- package/skills/ultra-change/references/primary-transfer.md +273 -0
- package/skills/ultra-change/scripts/validate_primary_transfer.cjs +1928 -0
- package/skills/ultra-delegate/SKILL.md +103 -8
- package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +184 -0
- package/skills/ultra-delegate/references/delegation-contract.md +80 -1
- package/skills/ultra-delegate/scripts/acp-conformance.cjs +287 -0
- package/skills/ultra-delegate/scripts/acp-driver.cjs +762 -0
- package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +320 -0
- package/skills/ultra-delegate/scripts/cli-driver.cjs +61 -0
- package/skills/ultra-delegate/scripts/delegate-worker.cjs +328 -0
- package/skills/ultra-delegate/scripts/delegate.cjs +690 -0
- package/skills/ultra-delegate/scripts/host-profile.cjs +267 -0
- package/skills/ultra-deliver/SKILL.md +98 -11
- package/skills/ultra-dev/SKILL.md +188 -42
- package/skills/ultra-grilling/SKILL.md +10 -7
- package/skills/ultra-grilling/references/reframing.md +7 -3
- package/skills/ultra-init/SKILL.md +56 -7
- package/skills/ultra-init/scripts/init_project.cjs +968 -21
- package/skills/ultra-plan/SKILL.md +91 -47
- package/skills/ultra-plan/references/task-evidence-v2.md +300 -0
- package/skills/ultra-plan/scripts/validate_task_evidence.cjs +859 -0
- package/skills/ultra-research/SKILL.md +70 -14
- package/skills/ultra-research/references/04-product-strategy.md +11 -2
- package/skills/ultra-research/references/21-features-scope.md +8 -0
- package/skills/ultra-research/references/22-success-metrics.md +4 -3
- package/skills/ultra-research/references/99-synthesis.md +19 -2
- package/skills/ultra-research/references/north-star-v2.md +168 -0
- package/skills/ultra-research/references/wayfinding.md +8 -0
- package/skills/ultra-research/scripts/validate_north_star.cjs +1174 -0
- package/skills/ultra-review/SKILL.md +201 -27
- package/skills/ultra-review/references/adversarial-evaluation.md +31 -0
- package/skills/ultra-review/references/spec.md +5 -0
- package/skills/ultra-review/references/unified-schema.md +146 -11
- package/skills/ultra-review/references/worker-packet.md +151 -8
- package/skills/ultra-review/scripts/review_wait.py +2653 -63
- package/skills/ultra-status/SKILL.md +129 -15
- package/skills/ultra-status/references/route-contract.md +100 -0
- package/skills/ultra-tdd/SKILL.md +11 -4
- package/skills/ultra-tdd/references/test-execution.md +6 -1
- package/skills/ultra-test/SKILL.md +161 -18
- package/skills/ultra-test/scripts/validate_review_transport.cjs +549 -0
- package/skills/ultra-test/scripts/worktree_digest.cjs +518 -30
- /package/{adapters/_shared → skills/ultra-delegate/scripts}/delegate-profiles/kimi-read-only.md +0 -0
- /package/{adapters/_shared → skills/ultra-delegate/scripts}/delegate-profiles/kimi-write.md +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
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())
|
package/hooks/compact_context.py
CHANGED
|
@@ -6,24 +6,140 @@ from __future__ import annotations
|
|
|
6
6
|
import subprocess
|
|
7
7
|
from datetime import datetime, timezone
|
|
8
8
|
|
|
9
|
-
from _common import
|
|
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
|
|
10
21
|
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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"",
|
|
15
69
|
)
|
|
16
|
-
|
|
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
|
|
17
115
|
|
|
18
116
|
|
|
19
|
-
def save_snapshot(root)
|
|
20
|
-
|
|
21
|
-
|
|
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)
|
|
22
138
|
text = [
|
|
23
139
|
"# Ultra Compact Snapshot",
|
|
24
140
|
"",
|
|
25
141
|
f"Generated: {datetime.now(timezone.utc).isoformat()}",
|
|
26
|
-
f"HEAD: {
|
|
142
|
+
f"HEAD: {head}",
|
|
27
143
|
f"Task: {task.get('id') if task else 'none'}",
|
|
28
144
|
"",
|
|
29
145
|
"## Acceptance Criteria",
|
|
@@ -32,15 +148,30 @@ def save_snapshot(root) -> None:
|
|
|
32
148
|
"## Resume Note",
|
|
33
149
|
sections["resume"] or "_(none)_",
|
|
34
150
|
"",
|
|
151
|
+
RESUME_NAVIGATION_LIMITATION,
|
|
152
|
+
"",
|
|
153
|
+
"## Task Diagnostics",
|
|
154
|
+
diagnostics or "_(none)_",
|
|
155
|
+
"",
|
|
35
156
|
"## Worktree",
|
|
36
157
|
"```text",
|
|
37
|
-
|
|
158
|
+
worktree,
|
|
38
159
|
"```",
|
|
39
160
|
"",
|
|
40
161
|
]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
44
175
|
|
|
45
176
|
|
|
46
177
|
def main() -> int:
|
|
@@ -50,14 +181,42 @@ def main() -> int:
|
|
|
50
181
|
return 0
|
|
51
182
|
event = str(payload.get("hook_event_name") or payload.get("hookEventName") or "")
|
|
52
183
|
if event == "PreCompact":
|
|
53
|
-
save_snapshot(root)
|
|
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
|
+
)
|
|
54
191
|
return 0
|
|
55
192
|
if event in {"PostCompact", "SessionStart"} and (
|
|
56
193
|
event == "PostCompact" or payload.get("source") == "compact"
|
|
57
194
|
):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
)
|
|
61
220
|
return 0
|
|
62
221
|
|
|
63
222
|
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Recall current acceptance before a source-reading or editing tool call."""
|
|
3
3
|
|
|
4
|
-
from _common import
|
|
4
|
+
from _common import (
|
|
5
|
+
current_task_selection,
|
|
6
|
+
emit_context,
|
|
7
|
+
project_root,
|
|
8
|
+
read_payload,
|
|
9
|
+
render_task_diagnostics,
|
|
10
|
+
task_sections,
|
|
11
|
+
)
|
|
5
12
|
|
|
6
13
|
|
|
7
14
|
def main() -> int:
|
|
@@ -9,14 +16,29 @@ def main() -> int:
|
|
|
9
16
|
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
10
17
|
if root is None:
|
|
11
18
|
return 0
|
|
12
|
-
|
|
19
|
+
selection = current_task_selection(root)
|
|
20
|
+
task = selection["task"]
|
|
13
21
|
if not task:
|
|
22
|
+
diagnostics = render_task_diagnostics(selection["diagnostics"])
|
|
23
|
+
if diagnostics:
|
|
24
|
+
emit_context(
|
|
25
|
+
"PreToolUse",
|
|
26
|
+
f"Ultra task ledger diagnostics:\n{diagnostics}",
|
|
27
|
+
)
|
|
14
28
|
return 0
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
sections = task_sections(root, task)
|
|
30
|
+
parts = []
|
|
31
|
+
if sections["acceptance"]:
|
|
32
|
+
parts.append(
|
|
33
|
+
f"Ultra task {task.get('id')} acceptance reminder:\n{sections['acceptance']}"
|
|
34
|
+
)
|
|
35
|
+
diagnostics = render_task_diagnostics(sections["diagnostics"])
|
|
36
|
+
if diagnostics:
|
|
37
|
+
parts.append(f"Ultra task {task.get('id')} diagnostics:\n{diagnostics}")
|
|
38
|
+
if parts:
|
|
17
39
|
emit_context(
|
|
18
40
|
"PreToolUse",
|
|
19
|
-
|
|
41
|
+
"\n\n".join(parts),
|
|
20
42
|
)
|
|
21
43
|
return 0
|
|
22
44
|
|