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,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())
@@ -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())
@@ -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())