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.
- package/CHANGELOG.md +113 -3
- package/README.md +67 -478
- package/README.zh-CN.md +49 -422
- package/adapters/_shared/frontmatter.cjs +1 -18
- package/adapters/_shared/plugin-core.cjs +17 -83
- package/adapters/_shared/runtime-assets.cjs +6 -120
- package/adapters/claude.js +8 -24
- package/adapters/codex.js +16 -38
- package/bin/install.js +35 -57
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +13 -101
- package/docs/examples/drive/claude-reviewer.sh +53 -0
- package/docs/examples/drive/claude-writer.sh +43 -0
- package/engine/FORMATS.md +1051 -0
- package/engine/checks.cjs +139 -0
- package/engine/cli.cjs +148 -0
- package/engine/completion.cjs +48 -0
- package/engine/context.cjs +94 -0
- package/engine/drive.cjs +220 -0
- package/engine/expect-worker.cjs +5 -0
- package/engine/expect.cjs +33 -0
- package/engine/formats.cjs +189 -0
- package/engine/git-state.cjs +112 -0
- package/engine/ledger.cjs +205 -0
- package/engine/loading.cjs +28 -0
- package/engine/merge.cjs +273 -0
- package/engine/milestone.cjs +89 -0
- package/engine/plan.cjs +232 -0
- package/engine/project.cjs +5 -0
- package/engine/signals.cjs +3 -0
- package/engine/states.cjs +175 -0
- package/hooks/session-start.cjs +54 -0
- package/package.json +17 -52
- package/skills/ultra/SKILL.md +395 -0
- package/.ultra-template/changes/abandoned/.gitkeep +0 -0
- package/.ultra-template/changes/active/.gitkeep +0 -1
- package/.ultra-template/changes/archive/.gitkeep +0 -1
- package/.ultra-template/contexts/TEMPLATE.md +0 -135
- package/.ultra-template/decisions/.gitkeep +0 -1
- package/.ultra-template/evidence/.gitkeep +0 -1
- package/.ultra-template/north-star.md +0 -52
- package/.ultra-template/project-brief.md +0 -28
- package/.ultra-template/research/.gitkeep +0 -1
- package/.ultra-template/specs/architecture.md +0 -81
- package/.ultra-template/specs/discovery.md +0 -74
- package/.ultra-template/specs/product.md +0 -80
- package/.ultra-template/specs/research-distillate.md +0 -57
- package/.ultra-template/tasks.json +0 -4
- package/.ultra-template/test-report.json +0 -44
- package/adapters/_shared/host-profile.cjs +0 -5
- package/adapters/grok.js +0 -242
- package/adapters/kimi.js +0 -232
- package/adapters/opencode.js +0 -408
- package/adapters/zcode.js +0 -375
- package/bin/delegate-worker.cjs +0 -5
- package/bin/delegate.cjs +0 -5
- package/docs/ARCHITECTURE.md +0 -274
- package/docs/ARTIFACT-AUTHORITY.md +0 -305
- package/docs/DECISIONS.md +0 -229
- package/docs/PHILOSOPHY.md +0 -261
- package/docs/RUNTIME-COMPAT-MATRIX.md +0 -315
- package/docs/SKILL-AUTHORING.md +0 -113
- package/docs/WORKFLOW-LIFECYCLE.md +0 -350
- package/docs/evals/adversarial-review-2026-08-14.md +0 -84
- package/docs/evals/ultra-5-0-journey-results.md +0 -62
- package/docs/evals/ultra-5-0-live-acp-conformance.md +0 -93
- package/docs/evals/ultra-5-0-route-journeys.md +0 -145
- package/docs/evals/zcode-automation-2026-08-14.md +0 -103
- package/hooks/README.md +0 -52
- package/hooks/_common.py +0 -1779
- package/hooks/adapters/codex.py +0 -295
- package/hooks/adapters/grok.py +0 -134
- package/hooks/adapters/kimi.py +0 -156
- package/hooks/adapters/zcode.py +0 -227
- package/hooks/block_dangerous_commands.py +0 -201
- package/hooks/compact_context.py +0 -224
- package/hooks/mid_workflow_recall.py +0 -47
- package/hooks/post_edit_guard.py +0 -215
- package/hooks/session_context.py +0 -741
- package/skills/ultra-change/SKILL.md +0 -165
- package/skills/ultra-change/references/change-contract.md +0 -199
- package/skills/ultra-change/references/execution-grant.md +0 -109
- package/skills/ultra-change/references/primary-transfer.md +0 -273
- package/skills/ultra-change/scripts/validate_primary_transfer.cjs +0 -1928
- package/skills/ultra-delegate/SKILL.md +0 -195
- package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +0 -184
- package/skills/ultra-delegate/references/delegation-contract.md +0 -138
- package/skills/ultra-delegate/scripts/acp-conformance.cjs +0 -287
- package/skills/ultra-delegate/scripts/acp-driver.cjs +0 -762
- package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +0 -320
- package/skills/ultra-delegate/scripts/cli-driver.cjs +0 -61
- package/skills/ultra-delegate/scripts/delegate-profiles/kimi-read-only.md +0 -15
- package/skills/ultra-delegate/scripts/delegate-profiles/kimi-write.md +0 -17
- package/skills/ultra-delegate/scripts/delegate-worker.cjs +0 -328
- package/skills/ultra-delegate/scripts/delegate.cjs +0 -690
- package/skills/ultra-delegate/scripts/delegate_wait.py +0 -86
- package/skills/ultra-delegate/scripts/host-profile.cjs +0 -267
- package/skills/ultra-deliver/SKILL.md +0 -194
- package/skills/ultra-deliver/references/baseline-reconciliation.md +0 -41
- package/skills/ultra-dev/SKILL.md +0 -265
- package/skills/ultra-dev/references/debugging.md +0 -22
- package/skills/ultra-domain-modeling/SKILL.md +0 -79
- package/skills/ultra-grilling/SKILL.md +0 -121
- package/skills/ultra-grilling/references/reframing.md +0 -76
- package/skills/ultra-init/SKILL.md +0 -143
- package/skills/ultra-init/references/brownfield-adoption.md +0 -32
- package/skills/ultra-init/scripts/init_project.cjs +0 -1031
- package/skills/ultra-plan/SKILL.md +0 -160
- package/skills/ultra-plan/references/task-evidence-v2.md +0 -300
- package/skills/ultra-plan/scripts/validate_task_evidence.cjs +0 -859
- package/skills/ultra-research/SKILL.md +0 -167
- package/skills/ultra-research/references/00-problem-validation.md +0 -36
- package/skills/ultra-research/references/01-opportunity-discovery.md +0 -33
- package/skills/ultra-research/references/02-market-assessment.md +0 -31
- package/skills/ultra-research/references/03-alternatives.md +0 -31
- package/skills/ultra-research/references/04-product-strategy.md +0 -41
- package/skills/ultra-research/references/05-assumptions-validation.md +0 -31
- package/skills/ultra-research/references/10-user-personas.md +0 -30
- package/skills/ultra-research/references/11-user-scenarios.md +0 -28
- package/skills/ultra-research/references/20-user-stories.md +0 -31
- package/skills/ultra-research/references/21-features-scope.md +0 -38
- package/skills/ultra-research/references/22-success-metrics.md +0 -33
- package/skills/ultra-research/references/30-architecture-context.md +0 -30
- package/skills/ultra-research/references/31-solution-strategy.md +0 -28
- package/skills/ultra-research/references/32-building-blocks.md +0 -28
- package/skills/ultra-research/references/40-deployment.md +0 -29
- package/skills/ultra-research/references/41-quality-risks.md +0 -28
- package/skills/ultra-research/references/99-synthesis.md +0 -59
- package/skills/ultra-research/references/north-star-v2.md +0 -168
- package/skills/ultra-research/references/wayfinding.md +0 -50
- package/skills/ultra-research/scripts/validate_north_star.cjs +0 -1174
- package/skills/ultra-review/SKILL.md +0 -275
- package/skills/ultra-review/references/adversarial-evaluation.md +0 -31
- package/skills/ultra-review/references/code.md +0 -17
- package/skills/ultra-review/references/comments.md +0 -12
- package/skills/ultra-review/references/design.md +0 -12
- package/skills/ultra-review/references/errors.md +0 -13
- package/skills/ultra-review/references/review-modes.md +0 -29
- package/skills/ultra-review/references/spec.md +0 -18
- package/skills/ultra-review/references/tests.md +0 -12
- package/skills/ultra-review/references/unified-schema.md +0 -284
- package/skills/ultra-review/references/worker-packet.md +0 -183
- package/skills/ultra-review/scripts/review_wait.py +0 -2943
- package/skills/ultra-status/SKILL.md +0 -209
- package/skills/ultra-status/references/route-contract.md +0 -100
- package/skills/ultra-tdd/SKILL.md +0 -91
- package/skills/ultra-tdd/references/templates/README.md +0 -20
- package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +0 -79
- package/skills/ultra-tdd/references/templates/persistence-real.ts +0 -78
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +0 -78
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +0 -73
- package/skills/ultra-tdd/references/templates/vertical-slice.ts +0 -66
- package/skills/ultra-tdd/references/test-execution.md +0 -23
- package/skills/ultra-test/SKILL.md +0 -244
- package/skills/ultra-test/references/export-syntax.md +0 -18
- package/skills/ultra-test/scripts/validate_review_transport.cjs +0 -549
- package/skills/ultra-test/scripts/worktree_digest.cjs +0 -576
- package/skills/ultra-think/SKILL.md +0 -56
- package/skills/ultra-think/references/autonomy-boundary.md +0 -48
- package/skills/ultra-think/references/interaction-boundary.md +0 -42
package/hooks/adapters/codex.py
DELETED
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Codex wire adapter for Ultra Builder Pro's five file-first hooks.
|
|
3
|
-
|
|
4
|
-
Codex and Claude Code share the JSON-on-stdin command-hook shape, but differ
|
|
5
|
-
at three important boundaries:
|
|
6
|
-
|
|
7
|
-
- Codex emits ``apply_patch`` with the patch text in ``tool_input.command``;
|
|
8
|
-
the post-edit sensor expects one ``Edit`` event per ``file_path``.
|
|
9
|
-
- Codex rejects ``permissionDecision: ask`` in PreToolUse output. Ultra warning
|
|
10
|
-
decisions therefore become advisory ``systemMessage`` values.
|
|
11
|
-
- Each event has a strict output schema. This adapter removes foreign fields
|
|
12
|
-
while preserving real deny/block decisions and additional context.
|
|
13
|
-
|
|
14
|
-
The adapter executes only allowlisted sibling hook modules. It is both a CLI
|
|
15
|
-
entrypoint used by ``hooks/hooks.json`` and an importable normalization layer
|
|
16
|
-
for tests.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import copy
|
|
22
|
-
import json
|
|
23
|
-
import os
|
|
24
|
-
import re
|
|
25
|
-
import subprocess
|
|
26
|
-
import sys
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from typing import Any
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
HOOK_ROOT = Path(__file__).resolve().parent.parent
|
|
32
|
-
ALLOWED_FEATURES = {
|
|
33
|
-
"block_dangerous_commands.py",
|
|
34
|
-
"compact_context.py",
|
|
35
|
-
"mid_workflow_recall.py",
|
|
36
|
-
"post_edit_guard.py",
|
|
37
|
-
"session_context.py",
|
|
38
|
-
}
|
|
39
|
-
UNIVERSAL_OUTPUT_FIELDS = {"continue", "stopReason", "suppressOutput", "systemMessage"}
|
|
40
|
-
PATCH_FILE_RE = re.compile(r"^\*\*\* (?:Add|Update|Delete) File: (.+?)\s*$", re.MULTILINE)
|
|
41
|
-
PATCH_MOVE_RE = re.compile(r"^\*\*\* Move to: (.+?)\s*$", re.MULTILINE)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _patch_paths(tool_input: Any) -> list[str]:
|
|
45
|
-
if not isinstance(tool_input, dict):
|
|
46
|
-
return []
|
|
47
|
-
patch = tool_input.get("command") or tool_input.get("patch") or ""
|
|
48
|
-
if not isinstance(patch, str):
|
|
49
|
-
return []
|
|
50
|
-
paths: list[str] = []
|
|
51
|
-
for match in [*PATCH_FILE_RE.findall(patch), *PATCH_MOVE_RE.findall(patch)]:
|
|
52
|
-
candidate = match.strip()
|
|
53
|
-
if candidate and candidate not in paths:
|
|
54
|
-
paths.append(candidate)
|
|
55
|
-
return paths
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def normalize_inputs(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
|
59
|
-
"""Return file-first hook input(s) for one Codex hook payload."""
|
|
60
|
-
normalized = copy.deepcopy(payload)
|
|
61
|
-
if normalized.get("tool_response") is not None and normalized.get("tool_output") is None:
|
|
62
|
-
normalized["tool_output"] = normalized["tool_response"]
|
|
63
|
-
|
|
64
|
-
if normalized.get("tool_name") != "apply_patch":
|
|
65
|
-
return [normalized]
|
|
66
|
-
|
|
67
|
-
paths = _patch_paths(normalized.get("tool_input"))
|
|
68
|
-
if not paths:
|
|
69
|
-
normalized["tool_name"] = "Edit"
|
|
70
|
-
tool_input = normalized.get("tool_input")
|
|
71
|
-
normalized["tool_input"] = dict(tool_input) if isinstance(tool_input, dict) else {}
|
|
72
|
-
normalized["tool_input"].setdefault("file_path", "")
|
|
73
|
-
return [normalized]
|
|
74
|
-
|
|
75
|
-
outputs: list[dict[str, Any]] = []
|
|
76
|
-
for file_path in paths:
|
|
77
|
-
item = copy.deepcopy(normalized)
|
|
78
|
-
item["tool_name"] = "Edit"
|
|
79
|
-
tool_input = item.get("tool_input")
|
|
80
|
-
item["tool_input"] = dict(tool_input) if isinstance(tool_input, dict) else {}
|
|
81
|
-
item["tool_input"]["file_path"] = file_path
|
|
82
|
-
outputs.append(item)
|
|
83
|
-
return outputs
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _universal(output: dict[str, Any]) -> dict[str, Any]:
|
|
87
|
-
return {key: output[key] for key in UNIVERSAL_OUTPUT_FIELDS if key in output}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def adapt_output(output: dict[str, Any], event: str) -> dict[str, Any]:
|
|
91
|
-
"""Convert a shared hook result to the strict Codex event output schema."""
|
|
92
|
-
if not isinstance(output, dict):
|
|
93
|
-
return {}
|
|
94
|
-
result = _universal(output)
|
|
95
|
-
hook_specific = output.get("hookSpecificOutput")
|
|
96
|
-
hook_specific = dict(hook_specific) if isinstance(hook_specific, dict) else None
|
|
97
|
-
|
|
98
|
-
if event == "PreToolUse":
|
|
99
|
-
if hook_specific:
|
|
100
|
-
decision = hook_specific.get("permissionDecision")
|
|
101
|
-
reason = hook_specific.get("permissionDecisionReason")
|
|
102
|
-
if decision == "ask":
|
|
103
|
-
if reason:
|
|
104
|
-
result["systemMessage"] = str(reason)
|
|
105
|
-
additional = hook_specific.get("additionalContext")
|
|
106
|
-
if additional:
|
|
107
|
-
result["systemMessage"] = "\n".join(
|
|
108
|
-
part for part in [result.get("systemMessage"), str(additional)] if part
|
|
109
|
-
)
|
|
110
|
-
return result
|
|
111
|
-
allowed = {
|
|
112
|
-
"hookEventName": "PreToolUse",
|
|
113
|
-
**{key: value for key, value in hook_specific.items() if key in {
|
|
114
|
-
"permissionDecision", "permissionDecisionReason", "updatedInput", "additionalContext"
|
|
115
|
-
}},
|
|
116
|
-
}
|
|
117
|
-
result["hookSpecificOutput"] = allowed
|
|
118
|
-
if output.get("decision") in {"block"}:
|
|
119
|
-
result["decision"] = "block"
|
|
120
|
-
if output.get("reason"):
|
|
121
|
-
result["reason"] = output["reason"]
|
|
122
|
-
return result
|
|
123
|
-
|
|
124
|
-
if event == "PermissionRequest":
|
|
125
|
-
if hook_specific and hook_specific.get("permissionDecision") in {"allow", "deny"}:
|
|
126
|
-
result["hookSpecificOutput"] = {
|
|
127
|
-
"hookEventName": "PermissionRequest",
|
|
128
|
-
"decision": {
|
|
129
|
-
"behavior": hook_specific["permissionDecision"],
|
|
130
|
-
"message": hook_specific.get("permissionDecisionReason"),
|
|
131
|
-
},
|
|
132
|
-
}
|
|
133
|
-
return result
|
|
134
|
-
|
|
135
|
-
if event == "PostToolUse":
|
|
136
|
-
if output.get("decision") == "block":
|
|
137
|
-
result["decision"] = "block"
|
|
138
|
-
result["reason"] = str(output.get("reason") or "Ultra post-edit gate blocked continuation")
|
|
139
|
-
if hook_specific and hook_specific.get("additionalContext"):
|
|
140
|
-
result["hookSpecificOutput"] = {
|
|
141
|
-
"hookEventName": "PostToolUse",
|
|
142
|
-
"additionalContext": hook_specific["additionalContext"],
|
|
143
|
-
}
|
|
144
|
-
return result
|
|
145
|
-
|
|
146
|
-
if event == "SessionStart":
|
|
147
|
-
if hook_specific and hook_specific.get("additionalContext"):
|
|
148
|
-
result["hookSpecificOutput"] = {
|
|
149
|
-
"hookEventName": "SessionStart",
|
|
150
|
-
"additionalContext": hook_specific["additionalContext"],
|
|
151
|
-
}
|
|
152
|
-
return result
|
|
153
|
-
|
|
154
|
-
if event == "SubagentStart":
|
|
155
|
-
if hook_specific and hook_specific.get("additionalContext"):
|
|
156
|
-
result["hookSpecificOutput"] = {
|
|
157
|
-
"hookEventName": "SubagentStart",
|
|
158
|
-
"additionalContext": hook_specific["additionalContext"],
|
|
159
|
-
}
|
|
160
|
-
return result
|
|
161
|
-
|
|
162
|
-
if event == "UserPromptSubmit":
|
|
163
|
-
if output.get("decision") == "block":
|
|
164
|
-
result["decision"] = "block"
|
|
165
|
-
result["reason"] = str(output.get("reason") or "Ultra prompt gate blocked submission")
|
|
166
|
-
if hook_specific and hook_specific.get("additionalContext"):
|
|
167
|
-
result["hookSpecificOutput"] = {
|
|
168
|
-
"hookEventName": "UserPromptSubmit",
|
|
169
|
-
"additionalContext": hook_specific["additionalContext"],
|
|
170
|
-
}
|
|
171
|
-
return result
|
|
172
|
-
|
|
173
|
-
if event == "Stop":
|
|
174
|
-
if output.get("decision") == "block":
|
|
175
|
-
result["decision"] = "block"
|
|
176
|
-
result["reason"] = str(output.get("reason") or "Ultra hook blocked stop")
|
|
177
|
-
return result
|
|
178
|
-
|
|
179
|
-
if event in {"PreCompact", "PostCompact", "SubagentStop"}:
|
|
180
|
-
extra = output.get("additionalContext")
|
|
181
|
-
if not extra and hook_specific:
|
|
182
|
-
extra = hook_specific.get("additionalContext")
|
|
183
|
-
if extra:
|
|
184
|
-
result["systemMessage"] = str(extra)
|
|
185
|
-
return result
|
|
186
|
-
|
|
187
|
-
return result
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def _parse_output(stdout: str) -> dict[str, Any]:
|
|
191
|
-
text = stdout.strip()
|
|
192
|
-
if not text:
|
|
193
|
-
return {}
|
|
194
|
-
try:
|
|
195
|
-
value = json.loads(text)
|
|
196
|
-
except json.JSONDecodeError:
|
|
197
|
-
return {"systemMessage": f"Ultra hook returned non-JSON output: {text[:500]}"}
|
|
198
|
-
return value if isinstance(value, dict) else {}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def _merge_outputs(outputs: list[dict[str, Any]], event: str) -> dict[str, Any]:
|
|
202
|
-
if not outputs:
|
|
203
|
-
return {}
|
|
204
|
-
if len(outputs) == 1:
|
|
205
|
-
return outputs[0]
|
|
206
|
-
|
|
207
|
-
messages = [str(item.get("systemMessage")) for item in outputs if item.get("systemMessage")]
|
|
208
|
-
contexts: list[str] = []
|
|
209
|
-
blocking: dict[str, Any] | None = None
|
|
210
|
-
for item in outputs:
|
|
211
|
-
if item.get("decision") == "block":
|
|
212
|
-
blocking = item
|
|
213
|
-
break
|
|
214
|
-
specific = item.get("hookSpecificOutput")
|
|
215
|
-
if isinstance(specific, dict) and specific.get("permissionDecision") == "deny":
|
|
216
|
-
blocking = item
|
|
217
|
-
break
|
|
218
|
-
if isinstance(specific, dict) and specific.get("additionalContext"):
|
|
219
|
-
contexts.append(str(specific["additionalContext"]))
|
|
220
|
-
|
|
221
|
-
result = dict(blocking or {})
|
|
222
|
-
if messages:
|
|
223
|
-
result["systemMessage"] = "\n\n".join(messages)
|
|
224
|
-
if contexts and not blocking and event in {"PostToolUse", "SessionStart", "SubagentStart", "UserPromptSubmit"}:
|
|
225
|
-
result["hookSpecificOutput"] = {
|
|
226
|
-
"hookEventName": event,
|
|
227
|
-
"additionalContext": "\n\n".join(contexts),
|
|
228
|
-
}
|
|
229
|
-
return result
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def run_feature(feature: str, payload: dict[str, Any], feature_args: list[str]) -> dict[str, Any]:
|
|
233
|
-
if feature not in ALLOWED_FEATURES or Path(feature).name != feature:
|
|
234
|
-
return {"systemMessage": f"Ultra hook adapter refused unknown feature: {feature}"}
|
|
235
|
-
script = HOOK_ROOT / feature
|
|
236
|
-
if not script.is_file():
|
|
237
|
-
return {"systemMessage": f"Ultra hook feature is missing: {feature}"}
|
|
238
|
-
|
|
239
|
-
event = str(payload.get("hook_event_name") or "")
|
|
240
|
-
env = os.environ.copy()
|
|
241
|
-
env["UBP_HOOK_RUNTIME"] = "codex"
|
|
242
|
-
plugin_data = env.get("PLUGIN_DATA") or env.get("CLAUDE_PLUGIN_DATA")
|
|
243
|
-
if plugin_data:
|
|
244
|
-
env["UBP_HOOK_DATA"] = plugin_data
|
|
245
|
-
|
|
246
|
-
cwd = payload.get("cwd")
|
|
247
|
-
run_cwd = str(cwd) if isinstance(cwd, str) and Path(cwd).is_dir() else os.getcwd()
|
|
248
|
-
outputs: list[dict[str, Any]] = []
|
|
249
|
-
for normalized in normalize_inputs(payload):
|
|
250
|
-
proc = subprocess.run(
|
|
251
|
-
[sys.executable, str(script), *feature_args],
|
|
252
|
-
input=json.dumps(normalized),
|
|
253
|
-
text=True,
|
|
254
|
-
capture_output=True,
|
|
255
|
-
cwd=run_cwd,
|
|
256
|
-
env=env,
|
|
257
|
-
timeout=30,
|
|
258
|
-
check=False,
|
|
259
|
-
)
|
|
260
|
-
if proc.stderr:
|
|
261
|
-
print(proc.stderr.rstrip(), file=sys.stderr)
|
|
262
|
-
if proc.returncode != 0:
|
|
263
|
-
outputs.append({
|
|
264
|
-
"systemMessage": f"Ultra hook {feature} failed with exit {proc.returncode}: {proc.stderr.strip()[:500]}"
|
|
265
|
-
})
|
|
266
|
-
continue
|
|
267
|
-
outputs.append(adapt_output(_parse_output(proc.stdout), event))
|
|
268
|
-
return _merge_outputs(outputs, event)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def main() -> int:
|
|
272
|
-
if len(sys.argv) < 2:
|
|
273
|
-
print(json.dumps({"systemMessage": "Ultra Codex hook adapter requires a feature name"}))
|
|
274
|
-
return 0
|
|
275
|
-
feature = sys.argv[1]
|
|
276
|
-
feature_args = sys.argv[2:]
|
|
277
|
-
try:
|
|
278
|
-
payload = json.loads(sys.stdin.read() or "{}")
|
|
279
|
-
if not isinstance(payload, dict):
|
|
280
|
-
payload = {}
|
|
281
|
-
except json.JSONDecodeError as exc:
|
|
282
|
-
print(json.dumps({"systemMessage": f"Ultra hook input parse error: {exc}"}))
|
|
283
|
-
return 0
|
|
284
|
-
try:
|
|
285
|
-
output = run_feature(feature, payload, feature_args)
|
|
286
|
-
except subprocess.TimeoutExpired:
|
|
287
|
-
output = {"systemMessage": f"Ultra hook {feature} timed out"}
|
|
288
|
-
except Exception as exc: # adapter boundary must fail open with evidence
|
|
289
|
-
output = {"systemMessage": f"Ultra hook adapter error in {feature}: {exc}"}
|
|
290
|
-
print(json.dumps(output, ensure_ascii=False))
|
|
291
|
-
return 0
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if __name__ == "__main__":
|
|
295
|
-
raise SystemExit(main())
|
package/hooks/adapters/grok.py
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Grok Build wire adapter for Ultra observational lifecycle hooks."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import copy
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
import subprocess
|
|
10
|
-
import sys
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
HOOK_ROOT = Path(__file__).resolve().parent.parent
|
|
16
|
-
ALLOWED_FEATURES = {
|
|
17
|
-
"block_dangerous_commands.py",
|
|
18
|
-
"compact_context.py",
|
|
19
|
-
"mid_workflow_recall.py",
|
|
20
|
-
"post_edit_guard.py",
|
|
21
|
-
"session_context.py",
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def normalize_input(payload: dict[str, Any]) -> dict[str, Any]:
|
|
26
|
-
"""Translate Grok's camelCase wire names into the shared hook contract."""
|
|
27
|
-
normalized = copy.deepcopy(payload)
|
|
28
|
-
aliases = {
|
|
29
|
-
"hookEventName": "hook_event_name",
|
|
30
|
-
"sessionId": "session_id",
|
|
31
|
-
"toolName": "tool_name",
|
|
32
|
-
"toolInput": "tool_input",
|
|
33
|
-
"toolResult": "tool_output",
|
|
34
|
-
"stopHookActive": "stop_hook_active",
|
|
35
|
-
"agentId": "agent_id",
|
|
36
|
-
"agentType": "agent_type",
|
|
37
|
-
"workspaceRoot": "workspace_root",
|
|
38
|
-
}
|
|
39
|
-
for source, target in aliases.items():
|
|
40
|
-
if source in normalized and target not in normalized:
|
|
41
|
-
normalized[target] = normalized[source]
|
|
42
|
-
return normalized
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _parse_output(stdout: str) -> dict[str, Any]:
|
|
46
|
-
text = stdout.strip()
|
|
47
|
-
if not text:
|
|
48
|
-
return {}
|
|
49
|
-
try:
|
|
50
|
-
value = json.loads(text)
|
|
51
|
-
except json.JSONDecodeError:
|
|
52
|
-
return {}
|
|
53
|
-
return value if isinstance(value, dict) else {}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def adapt_output(output: dict[str, Any], event: str, reason: str | None) -> dict[str, Any]:
|
|
57
|
-
"""Return only output fields Grok actually consumes for this event."""
|
|
58
|
-
if event == "PreToolUse":
|
|
59
|
-
specific = output.get("hookSpecificOutput")
|
|
60
|
-
specific = specific if isinstance(specific, dict) else {}
|
|
61
|
-
if specific.get("permissionDecision") == "deny":
|
|
62
|
-
return {
|
|
63
|
-
"decision": "deny",
|
|
64
|
-
"reason": str(
|
|
65
|
-
specific.get("permissionDecisionReason")
|
|
66
|
-
or "Ultra refused a direct write to a managed authority file"
|
|
67
|
-
),
|
|
68
|
-
}
|
|
69
|
-
return {"decision": "allow"}
|
|
70
|
-
# Ultra Stop is advisory. Grok's session-end Stop is observe-only and must
|
|
71
|
-
# never be confused with a genuine end_turn gate.
|
|
72
|
-
if event == "Stop" and reason != "end_turn":
|
|
73
|
-
return {}
|
|
74
|
-
return {}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def run_feature(feature: str, payload: dict[str, Any], feature_args: list[str]) -> dict[str, Any]:
|
|
78
|
-
if feature not in ALLOWED_FEATURES or Path(feature).name != feature:
|
|
79
|
-
return {}
|
|
80
|
-
script = HOOK_ROOT / feature
|
|
81
|
-
if not script.is_file():
|
|
82
|
-
return {}
|
|
83
|
-
normalized = normalize_input(payload)
|
|
84
|
-
env = os.environ.copy()
|
|
85
|
-
env["UBP_HOOK_RUNTIME"] = "grok"
|
|
86
|
-
plugin_data = env.get("GROK_PLUGIN_DATA") or env.get("CLAUDE_PLUGIN_DATA")
|
|
87
|
-
if plugin_data:
|
|
88
|
-
env["UBP_HOOK_DATA"] = plugin_data
|
|
89
|
-
cwd = normalized.get("cwd")
|
|
90
|
-
run_cwd = str(cwd) if isinstance(cwd, str) and Path(cwd).is_dir() else os.getcwd()
|
|
91
|
-
proc = subprocess.run(
|
|
92
|
-
[sys.executable, str(script), *feature_args],
|
|
93
|
-
input=json.dumps(normalized),
|
|
94
|
-
text=True,
|
|
95
|
-
capture_output=True,
|
|
96
|
-
cwd=run_cwd,
|
|
97
|
-
env=env,
|
|
98
|
-
timeout=30,
|
|
99
|
-
check=False,
|
|
100
|
-
)
|
|
101
|
-
if proc.stderr:
|
|
102
|
-
print(proc.stderr.rstrip(), file=sys.stderr)
|
|
103
|
-
if proc.returncode != 0:
|
|
104
|
-
return {}
|
|
105
|
-
return adapt_output(
|
|
106
|
-
_parse_output(proc.stdout),
|
|
107
|
-
str(payload.get("hookEventName") or ""),
|
|
108
|
-
str(payload.get("reason") or "") or None,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def main() -> int:
|
|
113
|
-
if len(sys.argv) < 2:
|
|
114
|
-
print(json.dumps({}))
|
|
115
|
-
return 0
|
|
116
|
-
feature = sys.argv[1]
|
|
117
|
-
try:
|
|
118
|
-
payload = json.loads(sys.stdin.read() or "{}")
|
|
119
|
-
if not isinstance(payload, dict):
|
|
120
|
-
payload = {}
|
|
121
|
-
except json.JSONDecodeError as exc:
|
|
122
|
-
print(f"[grok hook] invalid input: {exc}", file=sys.stderr)
|
|
123
|
-
payload = {}
|
|
124
|
-
try:
|
|
125
|
-
output = run_feature(feature, payload, sys.argv[2:])
|
|
126
|
-
except Exception as exc:
|
|
127
|
-
print(f"[grok hook] {feature} failed open: {exc}", file=sys.stderr)
|
|
128
|
-
output = {}
|
|
129
|
-
print(json.dumps(output, ensure_ascii=False))
|
|
130
|
-
return 0
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if __name__ == "__main__":
|
|
134
|
-
raise SystemExit(main())
|
package/hooks/adapters/kimi.py
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Kimi Code wire adapter for Ultra Builder Pro's five file-first hooks.
|
|
3
|
-
|
|
4
|
-
Kimi uses snake_case JSON input compatible with the shared hooks, but
|
|
5
|
-
its output contract accepts only ``message`` plus an optional structured deny.
|
|
6
|
-
This adapter preserves explicit projection denials and optional Stop decisions, converts foreign context
|
|
7
|
-
fields into Kimi messages, and fails open with diagnostic evidence.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import copy
|
|
13
|
-
import json
|
|
14
|
-
import os
|
|
15
|
-
import subprocess
|
|
16
|
-
import sys
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from typing import Any
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
HOOK_ROOT = Path(__file__).resolve().parent.parent
|
|
22
|
-
ALLOWED_FEATURES = {
|
|
23
|
-
"block_dangerous_commands.py",
|
|
24
|
-
"compact_context.py",
|
|
25
|
-
"mid_workflow_recall.py",
|
|
26
|
-
"post_edit_guard.py",
|
|
27
|
-
"session_context.py",
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def normalize_input(payload: dict[str, Any]) -> dict[str, Any]:
|
|
32
|
-
"""Add only aliases required by the shared file-first hooks."""
|
|
33
|
-
normalized = copy.deepcopy(payload)
|
|
34
|
-
agent_name = normalized.get("agent_name")
|
|
35
|
-
if isinstance(agent_name, str) and agent_name:
|
|
36
|
-
normalized.setdefault("agent_id", agent_name)
|
|
37
|
-
normalized.setdefault("agent_type", agent_name)
|
|
38
|
-
if normalized.get("tool_response") is not None and normalized.get("tool_output") is None:
|
|
39
|
-
normalized["tool_output"] = normalized["tool_response"]
|
|
40
|
-
return normalized
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _text(value: Any) -> str:
|
|
44
|
-
return str(value).strip() if value is not None else ""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def adapt_output(output: dict[str, Any], event: str) -> dict[str, Any]:
|
|
48
|
-
"""Convert one legacy hook result to Kimi's exact structured schema."""
|
|
49
|
-
if not isinstance(output, dict):
|
|
50
|
-
return {}
|
|
51
|
-
specific = output.get("hookSpecificOutput")
|
|
52
|
-
specific = specific if isinstance(specific, dict) else {}
|
|
53
|
-
|
|
54
|
-
decision = specific.get("permissionDecision")
|
|
55
|
-
blocked = output.get("decision") == "block" or decision == "deny"
|
|
56
|
-
reason = (
|
|
57
|
-
_text(specific.get("permissionDecisionReason"))
|
|
58
|
-
or _text(output.get("reason"))
|
|
59
|
-
or f"Ultra {event} gate blocked continuation"
|
|
60
|
-
)
|
|
61
|
-
if blocked:
|
|
62
|
-
return {
|
|
63
|
-
"hookSpecificOutput": {
|
|
64
|
-
"message": reason,
|
|
65
|
-
"permissionDecision": "deny",
|
|
66
|
-
"permissionDecisionReason": reason,
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
context = (
|
|
71
|
-
_text(specific.get("additionalContext"))
|
|
72
|
-
or _text(output.get("additionalContext"))
|
|
73
|
-
or _text(output.get("systemMessage"))
|
|
74
|
-
or _text(specific.get("message"))
|
|
75
|
-
or _text(output.get("message"))
|
|
76
|
-
)
|
|
77
|
-
advisory = _text(specific.get("permissionDecisionReason")) if decision == "ask" else ""
|
|
78
|
-
message = "\n\n".join(part for part in [advisory, context] if part)
|
|
79
|
-
return {"message": message} if message else {}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _parse_output(stdout: str) -> dict[str, Any]:
|
|
83
|
-
text = stdout.strip()
|
|
84
|
-
if not text:
|
|
85
|
-
return {}
|
|
86
|
-
try:
|
|
87
|
-
value = json.loads(text)
|
|
88
|
-
except json.JSONDecodeError:
|
|
89
|
-
return {"message": f"Ultra hook returned non-JSON output: {text[:500]}"}
|
|
90
|
-
return value if isinstance(value, dict) else {}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def run_feature(feature: str, payload: dict[str, Any], feature_args: list[str]) -> dict[str, Any]:
|
|
94
|
-
if feature not in ALLOWED_FEATURES or Path(feature).name != feature:
|
|
95
|
-
return {"message": f"Ultra hook adapter refused unknown feature: {feature}"}
|
|
96
|
-
script = HOOK_ROOT / feature
|
|
97
|
-
if not script.is_file():
|
|
98
|
-
return {"message": f"Ultra hook feature is missing: {feature}"}
|
|
99
|
-
|
|
100
|
-
event = str(payload.get("hook_event_name") or "")
|
|
101
|
-
normalized = normalize_input(payload)
|
|
102
|
-
env = os.environ.copy()
|
|
103
|
-
env["UBP_HOOK_RUNTIME"] = "kimi"
|
|
104
|
-
plugin_data = env.get("PLUGIN_DATA") or env.get("KIMI_PLUGIN_ROOT")
|
|
105
|
-
if plugin_data:
|
|
106
|
-
env["UBP_HOOK_DATA"] = plugin_data
|
|
107
|
-
|
|
108
|
-
cwd = normalized.get("cwd")
|
|
109
|
-
run_cwd = str(cwd) if isinstance(cwd, str) and Path(cwd).is_dir() else os.getcwd()
|
|
110
|
-
proc = subprocess.run(
|
|
111
|
-
[sys.executable, str(script), *feature_args],
|
|
112
|
-
input=json.dumps(normalized),
|
|
113
|
-
text=True,
|
|
114
|
-
capture_output=True,
|
|
115
|
-
cwd=run_cwd,
|
|
116
|
-
env=env,
|
|
117
|
-
timeout=30,
|
|
118
|
-
check=False,
|
|
119
|
-
)
|
|
120
|
-
if proc.stderr:
|
|
121
|
-
print(proc.stderr.rstrip(), file=sys.stderr)
|
|
122
|
-
if proc.returncode != 0:
|
|
123
|
-
return {
|
|
124
|
-
"message": (
|
|
125
|
-
f"Ultra hook {feature} failed with exit {proc.returncode}: "
|
|
126
|
-
f"{proc.stderr.strip()[:500]}"
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
return adapt_output(_parse_output(proc.stdout), event)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def main() -> int:
|
|
133
|
-
if len(sys.argv) < 2:
|
|
134
|
-
print(json.dumps({"message": "Ultra Kimi hook adapter requires a feature name"}))
|
|
135
|
-
return 0
|
|
136
|
-
feature = sys.argv[1]
|
|
137
|
-
feature_args = sys.argv[2:]
|
|
138
|
-
try:
|
|
139
|
-
payload = json.loads(sys.stdin.read() or "{}")
|
|
140
|
-
if not isinstance(payload, dict):
|
|
141
|
-
payload = {}
|
|
142
|
-
except json.JSONDecodeError as exc:
|
|
143
|
-
print(json.dumps({"message": f"Ultra hook input parse error: {exc}"}))
|
|
144
|
-
return 0
|
|
145
|
-
try:
|
|
146
|
-
output = run_feature(feature, payload, feature_args)
|
|
147
|
-
except subprocess.TimeoutExpired:
|
|
148
|
-
output = {"message": f"Ultra hook {feature} timed out"}
|
|
149
|
-
except Exception as exc: # Kimi hooks are fail-open at this adapter boundary.
|
|
150
|
-
output = {"message": f"Ultra hook adapter error in {feature}: {exc}"}
|
|
151
|
-
print(json.dumps(output, ensure_ascii=False))
|
|
152
|
-
return 0
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if __name__ == "__main__":
|
|
156
|
-
raise SystemExit(main())
|