ultra-builder-pro-cli 0.25.1 → 0.26.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 +87 -0
- package/.ultra-template/decisions/.gitkeep +1 -0
- package/.ultra-template/evidence/.gitkeep +1 -0
- package/.ultra-template/north-star.md +46 -0
- package/.ultra-template/research/.gitkeep +1 -0
- package/.ultra-template/specs/discovery.md +2 -2
- package/.ultra-template/tasks.json +3 -0
- package/.ultra-template/test-report.json +27 -0
- package/CHANGELOG.md +40 -0
- package/README.md +217 -579
- package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
- package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
- package/adapters/_shared/file-ops.cjs +76 -2
- package/adapters/_shared/host-profile.cjs +97 -0
- package/adapters/_shared/plugin-core.cjs +204 -0
- package/adapters/_shared/runtime-assets.cjs +31 -96
- package/adapters/claude.js +92 -291
- package/adapters/codex.js +309 -842
- package/adapters/grok.js +143 -1469
- package/adapters/kimi.js +124 -1053
- package/adapters/opencode.js +230 -742
- package/bin/delegate-worker.cjs +323 -0
- package/bin/delegate.cjs +456 -0
- package/bin/install.js +38 -11
- package/docs/ARCHITECTURE.md +122 -216
- package/docs/ARTIFACT-AUTHORITY.md +90 -268
- package/docs/DECISIONS.md +75 -87
- package/docs/PHILOSOPHY.md +177 -0
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +62 -39
- package/docs/RUNTIME-COMPAT-MATRIX.md +99 -156
- package/docs/SKILL-AUTHORING.md +94 -73
- package/docs/WORKFLOW-LIFECYCLE.md +93 -189
- package/hooks/README.md +17 -37
- package/hooks/_common.py +113 -0
- package/hooks/adapters/codex.py +9 -16
- package/hooks/adapters/grok.py +5 -5
- package/hooks/adapters/kimi.py +8 -14
- package/hooks/block_dangerous_commands.py +95 -0
- package/hooks/compact_context.py +65 -0
- package/hooks/mid_workflow_recall.py +25 -0
- package/hooks/post_edit_guard.py +105 -0
- package/hooks/session_context.py +30 -0
- package/package.json +17 -52
- package/skills/ultra-change/SKILL.md +97 -50
- package/skills/ultra-change/references/change-contract.md +40 -1
- package/skills/ultra-delegate/SKILL.md +82 -0
- package/skills/ultra-delegate/references/delegation-contract.md +51 -0
- package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
- package/skills/ultra-deliver/SKILL.md +61 -42
- package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
- package/skills/ultra-dev/SKILL.md +96 -62
- package/skills/ultra-dev/references/debugging.md +22 -0
- package/skills/ultra-domain-modeling/SKILL.md +79 -0
- package/skills/ultra-grilling/SKILL.md +79 -0
- package/skills/ultra-init/SKILL.md +94 -37
- package/skills/ultra-init/references/brownfield-adoption.md +33 -83
- package/skills/ultra-init/scripts/init_project.cjs +84 -0
- package/skills/ultra-plan/SKILL.md +88 -68
- package/skills/ultra-research/SKILL.md +62 -48
- package/skills/ultra-research/references/00-problem-validation.md +5 -5
- package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
- package/skills/ultra-research/references/02-market-assessment.md +5 -4
- package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
- package/skills/ultra-research/references/04-product-strategy.md +3 -3
- package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
- package/skills/ultra-research/references/10-user-personas.md +3 -2
- package/skills/ultra-research/references/11-user-scenarios.md +3 -3
- package/skills/ultra-research/references/20-user-stories.md +4 -3
- package/skills/ultra-research/references/21-features-scope.md +3 -2
- package/skills/ultra-research/references/22-success-metrics.md +3 -3
- package/skills/ultra-research/references/30-architecture-context.md +3 -3
- package/skills/ultra-research/references/31-solution-strategy.md +3 -3
- package/skills/ultra-research/references/32-building-blocks.md +3 -3
- package/skills/ultra-research/references/40-deployment.md +3 -3
- package/skills/ultra-research/references/41-quality-risks.md +3 -3
- package/skills/ultra-research/references/99-synthesis.md +7 -7
- package/skills/ultra-review/SKILL.md +59 -32
- package/skills/ultra-review/references/code.md +17 -0
- package/skills/ultra-review/references/comments.md +12 -0
- package/skills/ultra-review/references/design.md +12 -0
- package/skills/ultra-review/references/errors.md +13 -0
- package/skills/ultra-review/references/spec.md +13 -0
- package/skills/ultra-review/references/tests.md +12 -0
- package/skills/ultra-review/references/worker-packet.md +40 -0
- package/skills/ultra-review/scripts/review_wait.py +26 -15
- package/skills/ultra-status/SKILL.md +61 -36
- package/skills/ultra-tdd/SKILL.md +81 -0
- package/skills/ultra-tdd/references/templates/README.md +20 -0
- package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
- package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
- package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
- package/skills/ultra-tdd/references/test-execution.md +18 -0
- package/skills/ultra-test/SKILL.md +70 -30
- package/skills/ultra-test/references/export-syntax.md +18 -0
- package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
- package/skills/ultra-think/SKILL.md +49 -27
- package/skills/ultra-think/references/autonomy-boundary.md +48 -0
- package/skills/ultra-think/references/interaction-boundary.md +18 -33
- package/.ultra-template/docs/research/README.md +0 -55
- package/.ultra-template/reports/templates/delivery-report.json +0 -17
- package/.ultra-template/reports/templates/test-report.json +0 -18
- package/.ultra-template/tasks/tasks.json +0 -15
- package/.ultra-template/templates/task-context.md +0 -19
- package/adapters/_shared/codex-assets.cjs +0 -725
- package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
- package/adapters/_shared/interaction-contract.cjs +0 -129
- package/adapters/_shared/path-rewrite.cjs +0 -79
- package/adapters/_shared/runtime-native.cjs +0 -132
- package/adapters/_shared/settings-merge.cjs +0 -76
- package/agents/code-reviewer.md +0 -42
- package/agents/debugger.md +0 -40
- package/agents/review-code.md +0 -49
- package/agents/review-comments.md +0 -38
- package/agents/review-coordinator.md +0 -56
- package/agents/review-design.md +0 -40
- package/agents/review-errors.md +0 -43
- package/agents/review-spec.md +0 -39
- package/agents/review-tests.md +0 -40
- package/agents/tdd-runner.md +0 -38
- package/bin/orchestrator.js +0 -383
- package/commands/ultra-change.md +0 -10
- package/commands/ultra-deliver.md +0 -10
- package/commands/ultra-dev.md +0 -10
- package/commands/ultra-doctor.md +0 -10
- package/commands/ultra-init.md +0 -10
- package/commands/ultra-plan.md +0 -10
- package/commands/ultra-research.md +0 -10
- package/commands/ultra-review.md +0 -10
- package/commands/ultra-status.md +0 -10
- package/commands/ultra-test.md +0 -10
- package/commands/ultra-think.md +0 -10
- package/docs/AGENT-CONTEXT.md +0 -191
- package/docs/COMMIT-HASH-BACKFILL.md +0 -51
- package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
- package/docs/ROADMAP.md +0 -101
- package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
- package/hooks/active_task_context.py +0 -152
- package/hooks/context_envelope.py +0 -100
- package/hooks/health_check.py +0 -196
- package/hooks/pre_stop_check.py +0 -66
- package/hooks/runtime_paths.py +0 -396
- package/hooks/subagent_tracker.py +0 -88
- package/hooks/workflow_checkpoint.py +0 -76
- package/hooks/workflow_context.py +0 -48
- package/hooks/workflow_resume.py +0 -84
- package/mcp-server/hook-context.cjs +0 -38
- package/mcp-server/hook-event.cjs +0 -34
- package/mcp-server/lib/archive-journal.cjs +0 -1119
- package/mcp-server/lib/archive-mutation-worker.py +0 -365
- package/mcp-server/lib/artifact-registry.cjs +0 -1232
- package/mcp-server/lib/baseline-workflow.cjs +0 -1193
- package/mcp-server/lib/canonical-json.cjs +0 -19
- package/mcp-server/lib/change-authority.cjs +0 -26
- package/mcp-server/lib/change-packet.cjs +0 -790
- package/mcp-server/lib/change-workflow.cjs +0 -65
- package/mcp-server/lib/context-envelope.cjs +0 -581
- package/mcp-server/lib/context-paths.cjs +0 -142
- package/mcp-server/lib/decision-records.cjs +0 -423
- package/mcp-server/lib/delivery-transaction.cjs +0 -471
- package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
- package/mcp-server/lib/doctor.cjs +0 -627
- package/mcp-server/lib/git-bootstrap.cjs +0 -651
- package/mcp-server/lib/init-project.cjs +0 -1021
- package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
- package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
- package/mcp-server/lib/managed-file-write.cjs +0 -172
- package/mcp-server/lib/plan-checkpoint.cjs +0 -177
- package/mcp-server/lib/plan-publication-worker.cjs +0 -830
- package/mcp-server/lib/plan-store.cjs +0 -794
- package/mcp-server/lib/prd-parser.cjs +0 -83
- package/mcp-server/lib/pricing.cjs +0 -44
- package/mcp-server/lib/project-context.cjs +0 -178
- package/mcp-server/lib/projector.cjs +0 -386
- package/mcp-server/lib/provider-refs.cjs +0 -42
- package/mcp-server/lib/runtime-paths.cjs +0 -1714
- package/mcp-server/lib/runtime-state.cjs +0 -222
- package/mcp-server/lib/safe-project-file.cjs +0 -385
- package/mcp-server/lib/stage-checkpoints.cjs +0 -463
- package/mcp-server/lib/state-authority.cjs +0 -95
- package/mcp-server/lib/state-db.cjs +0 -1580
- package/mcp-server/lib/state-ops.cjs +0 -1428
- package/mcp-server/lib/task-contract.cjs +0 -404
- package/mcp-server/lib/task-expander.cjs +0 -105
- package/mcp-server/lib/task-ledger.cjs +0 -1964
- package/mcp-server/lib/telemetry.cjs +0 -85
- package/mcp-server/lib/topo.cjs +0 -131
- package/mcp-server/lib/ultra-facade.cjs +0 -2684
- package/mcp-server/lib/worker-packet.cjs +0 -358
- package/mcp-server/server.cjs +0 -437
- package/orchestrator/auto-merge.cjs +0 -128
- package/orchestrator/daemon.cjs +0 -264
- package/orchestrator/dispatch-rules.cjs +0 -78
- package/orchestrator/parallel-orchestrator.cjs +0 -903
- package/orchestrator/planner/plan-builder.cjs +0 -125
- package/orchestrator/recovery.cjs +0 -188
- package/orchestrator/session-close-journal-worker.cjs +0 -5
- package/orchestrator/session-close-journal.cjs +0 -1014
- package/orchestrator/session-runner.cjs +0 -1153
- package/orchestrator/worktree-manager.cjs +0 -282
- package/settings.json +0 -7
- package/skills/cc-collab/SKILL.md +0 -42
- package/skills/code-review-expert/SKILL.md +0 -43
- package/skills/code-review-expert/references/correctness-reliability.md +0 -30
- package/skills/code-review-expert/references/design-boundaries.md +0 -26
- package/skills/code-review-expert/references/removal-plan.md +0 -24
- package/skills/codex-collab/SKILL.md +0 -49
- package/skills/codex-collab/evals/evals.json +0 -35
- package/skills/integration-rules/SKILL.md +0 -32
- package/skills/security-rules/SKILL.md +0 -38
- package/skills/testing-rules/SKILL.md +0 -32
- package/skills/ultra-doctor/SKILL.md +0 -41
- package/skills/ultra-plan/references/semantic-preflight.md +0 -20
- package/skills/ultra-verify/SKILL.md +0 -46
- package/skills/ultra-verify/evals/evals.json +0 -46
- package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
- package/skills/ultra-verify/references/evidence-status.md +0 -21
- package/skills/ultra-verify/scripts/verify_wait.py +0 -94
- package/spec/cli-protocol.md +0 -102
- package/spec/mcp-tools.yaml +0 -312
- package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
- package/spec/schemas/change-delta.v1.schema.json +0 -153
- package/spec/schemas/command-manifest.schema.json +0 -29
- package/spec/schemas/context-file.v4.5.schema.json +0 -54
- package/spec/schemas/delivery-report.v1.schema.json +0 -66
- package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
- package/spec/schemas/mcp-tools.schema.json +0 -136
- package/spec/schemas/skill-manifest.schema.json +0 -24
- package/spec/schemas/state-db.sql +0 -641
- package/spec/schemas/task-ledger.v2.schema.json +0 -296
- package/spec/schemas/tasks.v4.5.schema.json +0 -96
- package/spec/schemas/test-report.v1.schema.json +0 -225
- package/ultra-tools/cli.cjs +0 -129
- package/ultra-tools/commands/db.cjs +0 -226
- package/ultra-tools/commands/legacy-memory.cjs +0 -159
- package/ultra-tools/commands/migrate.cjs +0 -1386
- package/ultra-tools/commands/session.cjs +0 -213
- package/ultra-tools/commands/status.cjs +0 -559
- package/ultra-tools/commands/system.cjs +0 -498
- package/ultra-tools/commands/task.cjs +0 -105
package/hooks/_common.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Small file-first helpers shared by Ultra hooks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def read_payload() -> dict[str, Any]:
|
|
14
|
+
try:
|
|
15
|
+
value = json.loads(sys.stdin.read() or "{}")
|
|
16
|
+
except json.JSONDecodeError:
|
|
17
|
+
return {}
|
|
18
|
+
return value if isinstance(value, dict) else {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def project_root(payload: dict[str, Any]) -> Path | None:
|
|
22
|
+
candidate = payload.get("cwd") or payload.get("workspace_root") or os.getcwd()
|
|
23
|
+
path = Path(str(candidate)).resolve()
|
|
24
|
+
if path.is_file():
|
|
25
|
+
path = path.parent
|
|
26
|
+
for current in (path, *path.parents):
|
|
27
|
+
if (current / ".ultra").is_dir():
|
|
28
|
+
return current
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def markdown_section(text: str, heading: str) -> str:
|
|
33
|
+
pattern = re.compile(
|
|
34
|
+
rf"^##\s+{re.escape(heading)}\s*$\n(?P<body>.*?)(?=^##\s+|\Z)",
|
|
35
|
+
re.MULTILINE | re.DOTALL | re.IGNORECASE,
|
|
36
|
+
)
|
|
37
|
+
match = pattern.search(text)
|
|
38
|
+
return match.group("body").strip() if match else ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def read_tasks(root: Path) -> list[dict[str, Any]]:
|
|
42
|
+
try:
|
|
43
|
+
value = json.loads((root / ".ultra" / "tasks.json").read_text(encoding="utf-8"))
|
|
44
|
+
except (OSError, json.JSONDecodeError):
|
|
45
|
+
return []
|
|
46
|
+
tasks = value.get("tasks", []) if isinstance(value, dict) else value
|
|
47
|
+
return [item for item in tasks if isinstance(item, dict)] if isinstance(tasks, list) else []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def current_task(root: Path) -> dict[str, Any] | None:
|
|
51
|
+
tasks = read_tasks(root)
|
|
52
|
+
for status in ("in_progress", "pending"):
|
|
53
|
+
for task in tasks:
|
|
54
|
+
if task.get("status") == status:
|
|
55
|
+
return task
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def project_file(root: Path, value: str, *, ultra_default: bool = False) -> Path | None:
|
|
60
|
+
relative = Path(value)
|
|
61
|
+
if relative.is_absolute():
|
|
62
|
+
return None
|
|
63
|
+
ultra_shorthands = {"changes", "contexts", "decisions", "evidence", "progress", "reviews", "specs"}
|
|
64
|
+
first = relative.parts[0] if relative.parts else ""
|
|
65
|
+
if first == ".ultra":
|
|
66
|
+
base = root
|
|
67
|
+
elif ultra_default and first in ultra_shorthands:
|
|
68
|
+
base = root / ".ultra"
|
|
69
|
+
else:
|
|
70
|
+
base = root
|
|
71
|
+
candidate = (base / relative).resolve()
|
|
72
|
+
try:
|
|
73
|
+
candidate.relative_to(root.resolve())
|
|
74
|
+
except ValueError:
|
|
75
|
+
return None
|
|
76
|
+
return candidate
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def context_path(root: Path, task: dict[str, Any]) -> Path | None:
|
|
80
|
+
value = task.get("context_file")
|
|
81
|
+
if not isinstance(value, str) or not value:
|
|
82
|
+
return None
|
|
83
|
+
return project_file(root, value, ultra_default=True)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def task_sections(root: Path, task: dict[str, Any]) -> dict[str, str]:
|
|
87
|
+
file = context_path(root, task)
|
|
88
|
+
try:
|
|
89
|
+
text = file.read_text(encoding="utf-8") if file else ""
|
|
90
|
+
except OSError:
|
|
91
|
+
text = ""
|
|
92
|
+
return {
|
|
93
|
+
"acceptance": markdown_section(text, "Acceptance Criteria"),
|
|
94
|
+
"resume": markdown_section(text, "Resume Note"),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def emit_context(event: str, text: str) -> None:
|
|
99
|
+
if not text.strip():
|
|
100
|
+
return
|
|
101
|
+
print(json.dumps({
|
|
102
|
+
"hookSpecificOutput": {
|
|
103
|
+
"hookEventName": event,
|
|
104
|
+
"additionalContext": text.strip(),
|
|
105
|
+
}
|
|
106
|
+
}))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def write_json_atomic(file: Path, payload: dict[str, Any]) -> None:
|
|
110
|
+
file.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
temporary = file.with_suffix(f"{file.suffix}.tmp-{os.getpid()}")
|
|
112
|
+
temporary.write_text(f"{json.dumps(payload, indent=2)}\n", encoding="utf-8")
|
|
113
|
+
temporary.replace(file)
|
package/hooks/adapters/codex.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Codex wire adapter for Ultra Builder Pro hooks.
|
|
2
|
+
"""Codex wire adapter for Ultra Builder Pro's five file-first hooks.
|
|
3
3
|
|
|
4
4
|
Codex and Claude Code share the JSON-on-stdin command-hook shape, but differ
|
|
5
5
|
at three important boundaries:
|
|
6
6
|
|
|
7
7
|
- Codex emits ``apply_patch`` with the patch text in ``tool_input.command``;
|
|
8
|
-
|
|
8
|
+
the post-edit sensor expects one ``Edit`` event per ``file_path``.
|
|
9
9
|
- Codex rejects ``permissionDecision: ask`` in PreToolUse output. Ultra warning
|
|
10
10
|
decisions therefore become advisory ``systemMessage`` values.
|
|
11
11
|
- Each event has a strict output schema. This adapter removes foreign fields
|
|
@@ -30,13 +30,11 @@ from typing import Any
|
|
|
30
30
|
|
|
31
31
|
HOOK_ROOT = Path(__file__).resolve().parent.parent
|
|
32
32
|
ALLOWED_FEATURES = {
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"workflow_context.py",
|
|
39
|
-
"workflow_resume.py",
|
|
33
|
+
"block_dangerous_commands.py",
|
|
34
|
+
"compact_context.py",
|
|
35
|
+
"mid_workflow_recall.py",
|
|
36
|
+
"post_edit_guard.py",
|
|
37
|
+
"session_context.py",
|
|
40
38
|
}
|
|
41
39
|
UNIVERSAL_OUTPUT_FIELDS = {"continue", "stopReason", "suppressOutput", "systemMessage"}
|
|
42
40
|
PATCH_FILE_RE = re.compile(r"^\*\*\* (?:Add|Update|Delete) File: (.+?)\s*$", re.MULTILINE)
|
|
@@ -58,7 +56,7 @@ def _patch_paths(tool_input: Any) -> list[str]:
|
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
def normalize_inputs(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
|
61
|
-
"""Return
|
|
59
|
+
"""Return file-first hook input(s) for one Codex hook payload."""
|
|
62
60
|
normalized = copy.deepcopy(payload)
|
|
63
61
|
if normalized.get("tool_response") is not None and normalized.get("tool_output") is None:
|
|
64
62
|
normalized["tool_output"] = normalized["tool_response"]
|
|
@@ -90,7 +88,7 @@ def _universal(output: dict[str, Any]) -> dict[str, Any]:
|
|
|
90
88
|
|
|
91
89
|
|
|
92
90
|
def adapt_output(output: dict[str, Any], event: str) -> dict[str, Any]:
|
|
93
|
-
"""Convert a
|
|
91
|
+
"""Convert a shared hook result to the strict Codex event output schema."""
|
|
94
92
|
if not isinstance(output, dict):
|
|
95
93
|
return {}
|
|
96
94
|
result = _universal(output)
|
|
@@ -239,11 +237,6 @@ def run_feature(feature: str, payload: dict[str, Any], feature_args: list[str])
|
|
|
239
237
|
return {"systemMessage": f"Ultra hook feature is missing: {feature}"}
|
|
240
238
|
|
|
241
239
|
event = str(payload.get("hook_event_name") or "")
|
|
242
|
-
if feature == "workflow_resume.py" and event == "PostCompact":
|
|
243
|
-
payload = dict(payload)
|
|
244
|
-
payload["hook_event_name"] = "SessionStart"
|
|
245
|
-
payload["source"] = "compact"
|
|
246
|
-
|
|
247
240
|
env = os.environ.copy()
|
|
248
241
|
env["UBP_HOOK_RUNTIME"] = "codex"
|
|
249
242
|
plugin_data = env.get("PLUGIN_DATA") or env.get("CLAUDE_PLUGIN_DATA")
|
package/hooks/adapters/grok.py
CHANGED
|
@@ -14,11 +14,11 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
HOOK_ROOT = Path(__file__).resolve().parent.parent
|
|
16
16
|
ALLOWED_FEATURES = {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
17
|
+
"block_dangerous_commands.py",
|
|
18
|
+
"compact_context.py",
|
|
19
|
+
"mid_workflow_recall.py",
|
|
20
|
+
"post_edit_guard.py",
|
|
21
|
+
"session_context.py",
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
|
package/hooks/adapters/kimi.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Kimi Code wire adapter for Ultra Builder Pro
|
|
2
|
+
"""Kimi Code wire adapter for Ultra Builder Pro's five file-first hooks.
|
|
3
3
|
|
|
4
|
-
Kimi
|
|
4
|
+
Kimi uses snake_case JSON input compatible with the shared hooks, but
|
|
5
5
|
its output contract accepts only ``message`` plus an optional structured deny.
|
|
6
6
|
This adapter preserves explicit projection denials and optional Stop decisions, converts foreign context
|
|
7
7
|
fields into Kimi messages, and fails open with diagnostic evidence.
|
|
@@ -20,18 +20,16 @@ from typing import Any
|
|
|
20
20
|
|
|
21
21
|
HOOK_ROOT = Path(__file__).resolve().parent.parent
|
|
22
22
|
ALLOWED_FEATURES = {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"workflow_context.py",
|
|
29
|
-
"workflow_resume.py",
|
|
23
|
+
"block_dangerous_commands.py",
|
|
24
|
+
"compact_context.py",
|
|
25
|
+
"mid_workflow_recall.py",
|
|
26
|
+
"post_edit_guard.py",
|
|
27
|
+
"session_context.py",
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
def normalize_input(payload: dict[str, Any]) -> dict[str, Any]:
|
|
34
|
-
"""Add only
|
|
32
|
+
"""Add only aliases required by the shared file-first hooks."""
|
|
35
33
|
normalized = copy.deepcopy(payload)
|
|
36
34
|
agent_name = normalized.get("agent_name")
|
|
37
35
|
if isinstance(agent_name, str) and agent_name:
|
|
@@ -101,10 +99,6 @@ def run_feature(feature: str, payload: dict[str, Any], feature_args: list[str])
|
|
|
101
99
|
|
|
102
100
|
event = str(payload.get("hook_event_name") or "")
|
|
103
101
|
normalized = normalize_input(payload)
|
|
104
|
-
if feature == "workflow_resume.py" and event == "PostCompact":
|
|
105
|
-
normalized["hook_event_name"] = "SessionStart"
|
|
106
|
-
normalized["source"] = "compact"
|
|
107
|
-
|
|
108
102
|
env = os.environ.copy()
|
|
109
103
|
env["UBP_HOOK_RUNTIME"] = "kimi"
|
|
110
104
|
plugin_data = env.get("PLUGIN_DATA") or env.get("KIMI_PLUGIN_ROOT")
|
|
@@ -0,0 +1,95 @@
|
|
|
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 subprocess
|
|
11
|
+
|
|
12
|
+
from _common import emit_context, project_root, read_payload
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def protected_push(command: str, root) -> bool:
|
|
16
|
+
if not re.search(r"\bgit\s+push\b", command, re.IGNORECASE):
|
|
17
|
+
return False
|
|
18
|
+
if re.search(r"\b(?:main|master|production|prod)\b", command, re.IGNORECASE):
|
|
19
|
+
return True
|
|
20
|
+
result = subprocess.run(
|
|
21
|
+
["git", "branch", "--show-current"], cwd=root, text=True,
|
|
22
|
+
capture_output=True, check=False, timeout=3,
|
|
23
|
+
)
|
|
24
|
+
return result.stdout.strip() in {"main", "master", "production", "prod"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def classify(command: str, root) -> str | None:
|
|
28
|
+
if protected_push(command, root):
|
|
29
|
+
return "protected branch push"
|
|
30
|
+
if re.search(r"\b(?:drop\s+(?:table|database|schema)|truncate\s+(?:table\s+)?)\b", command, re.IGNORECASE):
|
|
31
|
+
return "destructive database operation"
|
|
32
|
+
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):
|
|
33
|
+
return "funds or on-chain transaction"
|
|
34
|
+
if re.search(
|
|
35
|
+
r"\b(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|SECRET|PASSWORD)\s*=\s*['\"]?(?!\$|\$\{)[^\s'\"]{8,}",
|
|
36
|
+
command,
|
|
37
|
+
re.IGNORECASE,
|
|
38
|
+
):
|
|
39
|
+
return "hard-coded credential"
|
|
40
|
+
if re.search(r"\beval\s+['\"]?\$|\bexec\s*\([^)]*\binput\s*\(", command, re.IGNORECASE):
|
|
41
|
+
return "user-controlled code execution"
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def advisory(command: str) -> str | None:
|
|
46
|
+
if re.search(
|
|
47
|
+
r"\b(?:alembic\s+upgrade|prisma\s+migrate|knex\s+migrate|sequelize\s+db:migrate|rails\s+db:migrate|manage\.py\s+migrate)\b",
|
|
48
|
+
command,
|
|
49
|
+
re.IGNORECASE,
|
|
50
|
+
):
|
|
51
|
+
return (
|
|
52
|
+
"Ultra observed a database migration command. Confirm the target environment, "
|
|
53
|
+
"backup or rollback path, and owner authorization before any irreversible effect."
|
|
54
|
+
)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> int:
|
|
59
|
+
payload = read_payload()
|
|
60
|
+
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
61
|
+
if root is None:
|
|
62
|
+
return 0
|
|
63
|
+
tool_input = payload.get("tool_input")
|
|
64
|
+
tool_input = tool_input if isinstance(tool_input, dict) else {}
|
|
65
|
+
command = tool_input.get("command")
|
|
66
|
+
if not isinstance(command, str) or not command.strip():
|
|
67
|
+
return 0
|
|
68
|
+
threat = classify(command, root)
|
|
69
|
+
if threat is None:
|
|
70
|
+
note = advisory(command)
|
|
71
|
+
if note:
|
|
72
|
+
emit_context("PreToolUse", note)
|
|
73
|
+
return 0
|
|
74
|
+
digest = hashlib.sha256(command.encode()).hexdigest()
|
|
75
|
+
if os.environ.get("UBP_DANGEROUS_COMMAND_APPROVED") == digest:
|
|
76
|
+
return 0
|
|
77
|
+
reason = (
|
|
78
|
+
f"Ultra blocked {threat}. Protected effect: the exact shell command. "
|
|
79
|
+
f"After explicit owner authorization, rerun with "
|
|
80
|
+
f"UBP_DANGEROUS_COMMAND_APPROVED={digest}."
|
|
81
|
+
)
|
|
82
|
+
print(json.dumps({
|
|
83
|
+
"decision": "block",
|
|
84
|
+
"reason": reason,
|
|
85
|
+
"hookSpecificOutput": {
|
|
86
|
+
"hookEventName": "PreToolUse",
|
|
87
|
+
"permissionDecision": "deny",
|
|
88
|
+
"permissionDecisionReason": reason,
|
|
89
|
+
},
|
|
90
|
+
}))
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,65 @@
|
|
|
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 current_task, emit_context, project_root, read_payload, task_sections
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def git_output(root, *args: str) -> str:
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
["git", *args], cwd=root, text=True, capture_output=True, check=False, timeout=5
|
|
15
|
+
)
|
|
16
|
+
return result.stdout.strip() if result.returncode == 0 else "unavailable"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def save_snapshot(root) -> None:
|
|
20
|
+
task = current_task(root)
|
|
21
|
+
sections = task_sections(root, task) if task else {"acceptance": "", "resume": ""}
|
|
22
|
+
text = [
|
|
23
|
+
"# Ultra Compact Snapshot",
|
|
24
|
+
"",
|
|
25
|
+
f"Generated: {datetime.now(timezone.utc).isoformat()}",
|
|
26
|
+
f"HEAD: {git_output(root, 'rev-parse', 'HEAD')}",
|
|
27
|
+
f"Task: {task.get('id') if task else 'none'}",
|
|
28
|
+
"",
|
|
29
|
+
"## Acceptance Criteria",
|
|
30
|
+
sections["acceptance"] or "_(none)_",
|
|
31
|
+
"",
|
|
32
|
+
"## Resume Note",
|
|
33
|
+
sections["resume"] or "_(none)_",
|
|
34
|
+
"",
|
|
35
|
+
"## Worktree",
|
|
36
|
+
"```text",
|
|
37
|
+
git_output(root, "status", "--short"),
|
|
38
|
+
"```",
|
|
39
|
+
"",
|
|
40
|
+
]
|
|
41
|
+
file = root / ".ultra" / ".runtime" / "compact-snapshot.md"
|
|
42
|
+
file.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
file.write_text("\n".join(text), encoding="utf-8")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main() -> int:
|
|
47
|
+
payload = read_payload()
|
|
48
|
+
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
49
|
+
if root is None:
|
|
50
|
+
return 0
|
|
51
|
+
event = str(payload.get("hook_event_name") or payload.get("hookEventName") or "")
|
|
52
|
+
if event == "PreCompact":
|
|
53
|
+
save_snapshot(root)
|
|
54
|
+
return 0
|
|
55
|
+
if event in {"PostCompact", "SessionStart"} and (
|
|
56
|
+
event == "PostCompact" or payload.get("source") == "compact"
|
|
57
|
+
):
|
|
58
|
+
file = root / ".ultra" / ".runtime" / "compact-snapshot.md"
|
|
59
|
+
if file.is_file():
|
|
60
|
+
emit_context("SessionStart", file.read_text(encoding="utf-8"))
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Recall current acceptance before a source-reading or editing tool call."""
|
|
3
|
+
|
|
4
|
+
from _common import current_task, emit_context, project_root, read_payload, task_sections
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> int:
|
|
8
|
+
payload = read_payload()
|
|
9
|
+
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
10
|
+
if root is None:
|
|
11
|
+
return 0
|
|
12
|
+
task = current_task(root)
|
|
13
|
+
if not task:
|
|
14
|
+
return 0
|
|
15
|
+
acceptance = task_sections(root, task)["acceptance"]
|
|
16
|
+
if acceptance:
|
|
17
|
+
emit_context(
|
|
18
|
+
"PreToolUse",
|
|
19
|
+
f"Ultra task {task.get('id')} acceptance reminder:\n{acceptance}",
|
|
20
|
+
)
|
|
21
|
+
return 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Record mechanical task evidence observations after an edit."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from _common import (
|
|
12
|
+
current_task,
|
|
13
|
+
emit_context,
|
|
14
|
+
project_file,
|
|
15
|
+
project_root,
|
|
16
|
+
read_payload,
|
|
17
|
+
write_json_atomic,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DIMENSIONS = (
|
|
22
|
+
"tests_written",
|
|
23
|
+
"tests_passed",
|
|
24
|
+
"persistence_real",
|
|
25
|
+
"feature_flags_audit",
|
|
26
|
+
"vertical_slice",
|
|
27
|
+
"spec_trace",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def slug(value: str) -> str:
|
|
32
|
+
return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def trace_observation(root: Path, task: dict) -> str:
|
|
36
|
+
trace = task.get("trace_to")
|
|
37
|
+
if not isinstance(trace, str) or "#" not in trace:
|
|
38
|
+
return "missing"
|
|
39
|
+
name, anchor = trace.rsplit("#", 1)
|
|
40
|
+
file = project_file(root, name, ultra_default=True)
|
|
41
|
+
if file is None:
|
|
42
|
+
return "missing"
|
|
43
|
+
try:
|
|
44
|
+
headings = re.findall(r"^#{1,6}\s+(.+?)\s*$", file.read_text(encoding="utf-8"), re.MULTILINE)
|
|
45
|
+
except OSError:
|
|
46
|
+
return "missing"
|
|
47
|
+
return "observed" if slug(anchor) in {slug(heading) for heading in headings} else "missing"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main() -> int:
|
|
51
|
+
payload = read_payload()
|
|
52
|
+
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
53
|
+
if root is None:
|
|
54
|
+
return 0
|
|
55
|
+
task = current_task(root)
|
|
56
|
+
if not task:
|
|
57
|
+
return 0
|
|
58
|
+
tool_input = payload.get("tool_input")
|
|
59
|
+
tool_input = tool_input if isinstance(tool_input, dict) else {}
|
|
60
|
+
changed = str(tool_input.get("file_path") or tool_input.get("path") or "")
|
|
61
|
+
relative = ""
|
|
62
|
+
if changed:
|
|
63
|
+
try:
|
|
64
|
+
relative = str(Path(changed).resolve().relative_to(root))
|
|
65
|
+
except ValueError:
|
|
66
|
+
relative = ""
|
|
67
|
+
|
|
68
|
+
file = root / ".ultra" / "progress" / f"{task.get('id')}.json"
|
|
69
|
+
try:
|
|
70
|
+
progress = json.loads(file.read_text(encoding="utf-8"))
|
|
71
|
+
except (OSError, json.JSONDecodeError):
|
|
72
|
+
progress = {}
|
|
73
|
+
if not isinstance(progress, dict):
|
|
74
|
+
progress = {}
|
|
75
|
+
existing_evidence = progress.get("evidence")
|
|
76
|
+
existing_evidence = existing_evidence if isinstance(existing_evidence, dict) else {}
|
|
77
|
+
progress["task_id"] = str(task.get("id"))
|
|
78
|
+
progress["evidence"] = {
|
|
79
|
+
name: existing_evidence.get(name, "unknown")
|
|
80
|
+
if isinstance(existing_evidence.get(name, "unknown"), str)
|
|
81
|
+
else "unknown"
|
|
82
|
+
for name in DIMENSIONS
|
|
83
|
+
}
|
|
84
|
+
touched_files = progress.get("touched_files")
|
|
85
|
+
progress["touched_files"] = list(dict.fromkeys(
|
|
86
|
+
item for item in touched_files if isinstance(item, str) and item
|
|
87
|
+
)) if isinstance(touched_files, list) else []
|
|
88
|
+
if relative and relative not in progress["touched_files"]:
|
|
89
|
+
progress["touched_files"].append(relative)
|
|
90
|
+
if re.search(r"(?:^|/)(?:test|tests|__tests__)/|(?:\.test|\.spec)\.", relative, re.IGNORECASE):
|
|
91
|
+
progress["evidence"]["tests_written"] = "observed"
|
|
92
|
+
progress["evidence"]["spec_trace"] = trace_observation(root, task)
|
|
93
|
+
progress["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
94
|
+
write_json_atomic(file, progress)
|
|
95
|
+
|
|
96
|
+
unknown = [name for name, value in progress["evidence"].items() if value == "unknown"]
|
|
97
|
+
emit_context(
|
|
98
|
+
"PostToolUse",
|
|
99
|
+
f"Ultra evidence sensor updated task {task.get('id')}. Still unobserved: {', '.join(unknown) or 'none'}.",
|
|
100
|
+
)
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Inject the one-line goal and current task acceptance at session start."""
|
|
3
|
+
|
|
4
|
+
from _common import current_task, emit_context, markdown_section, project_root, read_payload, task_sections
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> int:
|
|
8
|
+
payload = read_payload()
|
|
9
|
+
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
10
|
+
if root is None:
|
|
11
|
+
return 0
|
|
12
|
+
try:
|
|
13
|
+
north_star = (root / ".ultra" / "north-star.md").read_text(encoding="utf-8")
|
|
14
|
+
except OSError:
|
|
15
|
+
north_star = ""
|
|
16
|
+
parts = []
|
|
17
|
+
one_line = markdown_section(north_star, "One-line")
|
|
18
|
+
if one_line:
|
|
19
|
+
parts.append(f"Ultra north star:\n{one_line}")
|
|
20
|
+
task = current_task(root)
|
|
21
|
+
if task:
|
|
22
|
+
acceptance = task_sections(root, task)["acceptance"]
|
|
23
|
+
if acceptance:
|
|
24
|
+
parts.append(f"Current task {task.get('id')} acceptance:\n{acceptance}")
|
|
25
|
+
emit_context("SessionStart", "\n\n".join(parts))
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
raise SystemExit(main())
|