@tyroneross/build-loop 0.35.0 → 0.36.1
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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +67 -14
- package/README.md +208 -299
- package/agents/advisor.md +2 -0
- package/agents/alignment-checker.md +2 -0
- package/agents/api-assessor.md +3 -1
- package/agents/architecture-scout.md +2 -0
- package/agents/assessment-orchestrator.md +2 -0
- package/agents/build-orchestrator.md +13 -8
- package/agents/database-assessor.md +107 -3
- package/agents/design-contract-specialist.md +2 -0
- package/agents/fact-checker.md +2 -0
- package/agents/fix-critique.md +26 -5
- package/agents/frontend-assessor.md +3 -1
- package/agents/implementer.md +2 -0
- package/agents/independent-auditor.md +2 -0
- package/agents/mock-scanner.md +2 -0
- package/agents/optimize-runner.md +2 -0
- package/agents/overfitting-reviewer.md +2 -0
- package/agents/performance-assessor.md +3 -1
- package/agents/plan-critic.md +2 -0
- package/agents/promotion-reviewer.md +2 -0
- package/agents/recurring-pattern-detector.md +4 -1
- package/agents/retrospective-synthesizer.md +14 -0
- package/agents/root-cause-investigator.md +39 -2
- package/agents/scope-auditor.md +2 -0
- package/agents/security-reviewer.md +3 -0
- package/agents/self-improvement-architect.md +2 -0
- package/agents/synthesis-critic.md +2 -0
- package/agents/transcript-pattern-miner.md +2 -0
- package/agents/ui-validator.md +2 -0
- package/assets/build-loop-plugin-icon.png +0 -0
- package/bin/build-loop-install.js +234 -0
- package/commands/run.md +1 -1
- package/hooks/git/pre-commit +40 -0
- package/hooks/git/pre-push +35 -10
- package/hooks/hooks.json +42 -5
- package/hooks/pre-edit-architecture.sh +2 -2
- package/hooks/route-guard.sh +15 -0
- package/hooks/route_guard.py +221 -0
- package/hooks/session-start-extensions.sh +10 -0
- package/hooks/session-start-git-hooks.sh +6 -1
- package/hooks/session-start-rally-point.sh +23 -1
- package/hooks/test_session_start_rally_version.sh +123 -0
- package/package.json +7 -2
- package/plugin-artifacts/codex/.codex-plugin/plugin.json +1 -1
- package/plugin-artifacts/codex/AGENTS.md +67 -14
- package/plugin-artifacts/codex/README.md +208 -299
- package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
- package/plugin-artifacts/codex/references/backlog-system.md +285 -0
- package/plugin-artifacts/codex/references/capability-routing.md +49 -0
- package/plugin-artifacts/codex/references/coordination-rules.md +271 -2
- package/plugin-artifacts/codex/references/memory-systems.md +25 -2
- package/plugin-artifacts/codex/references/memory.md +14 -0
- package/plugin-artifacts/codex/references/model-tier-mapping.md +45 -6
- package/plugin-artifacts/codex/references/phase-1-assess.md +19 -1
- package/plugin-artifacts/codex/references/phase-2-plan.md +9 -1
- package/plugin-artifacts/codex/references/phase-3-execute.md +1 -1
- package/plugin-artifacts/codex/references/phase-4-review.md +20 -1
- package/plugin-artifacts/codex/references/phase-5-iterate.md +4 -3
- package/plugin-artifacts/codex/references/phase-6-learn.md +2 -0
- package/plugin-artifacts/codex/references/verify-dispatch.md +85 -0
- package/plugin-artifacts/codex/skills/build-loop/SKILL.md +19 -10
- package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +1 -1
- package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +49 -0
- package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +101 -0
- package/plugin-artifacts/codex/skills/build-loop/references/memory.md +14 -0
- package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +98 -5
- package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +19 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +9 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +1 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +20 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +4 -3
- package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +2 -0
- package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +85 -0
- package/scripts/build_codex_plugin_artifact.py +7 -0
- package/scripts/embed_backend.py +17 -13
- package/scripts/lessons_index/query.py +1 -1
- package/scripts/memory_graph/__init__.py +2 -2
- package/scripts/sync_navgator_lessons.py +4 -4
- package/skills/api-registry-bridge/SKILL.md +1 -1
- package/skills/auto-decision-capture/SKILL.md +4 -3
- package/skills/auto-finding-capture/SKILL.md +119 -0
- package/skills/build-loop/SKILL.md +19 -10
- package/skills/build-loop/fallbacks.md +1 -1
- package/skills/build-loop/references/capability-routing.md +49 -0
- package/skills/build-loop/references/experiment-results-template.md +101 -0
- package/skills/build-loop/references/memory.md +14 -0
- package/skills/build-loop/references/output-style.md +98 -5
- package/skills/build-loop/references/phase-1-assess.md +19 -1
- package/skills/build-loop/references/phase-2-plan.md +9 -1
- package/skills/build-loop/references/phase-3-execute.md +1 -1
- package/skills/build-loop/references/phase-4-review.md +20 -1
- package/skills/build-loop/references/phase-5-iterate.md +4 -3
- package/skills/build-loop/references/phase-6-learn.md +2 -0
- package/skills/build-loop/references/verify-dispatch.md +85 -0
- package/skills/debug-loop/SKILL.md +4 -2
- package/skills/debugging-memory/SKILL.md +24 -12
- package/skills/{debugging/assess/SKILL.md → debugging-memory/references/assess.md} +6 -13
- package/skills/{debugging/memory/SKILL.md → debugging-memory/references/search.md} +10 -17
- package/skills/{debugging/store/SKILL.md → debugging-memory/references/store.md} +5 -13
- package/skills/debugging-memory/references/subagent-integration.md +3 -3
- package/skills/defenseclaw-bridge/SKILL.md +1 -1
- package/skills/{loop-builder → focused-loop-builder}/SKILL.md +6 -6
- package/skills/ibr-bridge/SKILL.md +1 -1
- package/skills/knowledge/SKILL.md +13 -2
- package/skills/{knowledge-review/SKILL.md → knowledge/references/review-mode.md} +8 -20
- package/skills/logging-tracer/SKILL.md +20 -4
- package/skills/model-bakeoff/SKILL.md +44 -0
- package/skills/model-tiering/SKILL.md +55 -20
- package/skills/optimize/SKILL.md +2 -2
- package/skills/prd-bridge/SKILL.md +1 -1
- package/skills/recursive-retrospective/SKILL.md +46 -0
- package/skills/recursive-retrospective/references/01-retrospective.md +136 -0
- package/skills/recursive-retrospective/references/02-learning-capture.md +72 -0
- package/skills/recursive-retrospective/references/03-judge.md +45 -0
- package/skills/research/SKILL.md +1 -1
- package/skills/root-cause-analysis/SKILL.md +42 -0
- package/skills/security-methodology/SKILL.md +10 -1
- package/skills/security-scan/SKILL.md +55 -0
- package/skills/spec-writing/SKILL.md +42 -1
- package/skills/sync-skills/SKILL.md +8 -7
- package/commands/assess.md +0 -41
- package/commands/compose-handoff.md +0 -113
- package/commands/debug.md +0 -20
- package/commands/debugger-detail.md +0 -34
- package/commands/debugger-scan.md +0 -15
- package/commands/debugger-status.md +0 -20
- package/commands/debugger.md +0 -32
- package/commands/optimize-run.md +0 -31
- package/commands/promote-experiment.md +0 -129
- package/commands/rally-point.md +0 -301
- package/commands/research-run.md +0 -22
- package/commands/review-knowledge.md +0 -16
- package/commands/self-review.md +0 -51
- package/commands/setup-memory.md +0 -32
- package/commands/start-prd.md +0 -57
- package/commands/test.md +0 -34
- package/commands/verify-plan.md +0 -39
- package/skills/debugging/debug-loop/SKILL.md +0 -231
- package/skills/logging-tracer-bridge/SKILL.md +0 -70
- /package/skills/{loop-builder → focused-loop-builder}/presets/active-project-evidence.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/generic-artifact-loop.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/presentation-audit.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/research-synthesis.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/source-ingestion-raw-data-audit.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/references/spec-format.md +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/scripts/loop_builder.py +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# route_guard.py — keep build/fix/refactor prompts routed to build-loop instead
|
|
5
|
+
# of being hijacked by another plugin's coercive SessionStart injection
|
|
6
|
+
# (notably superpowers:brainstorming, whose using-superpowers skill injects a
|
|
7
|
+
# "brainstorming first, you MUST" block every session and out-ranks build-loop
|
|
8
|
+
# in the model's skill routing).
|
|
9
|
+
#
|
|
10
|
+
# Two modes, one classifier (single source of truth for the build-signal regex):
|
|
11
|
+
#
|
|
12
|
+
# prompt-submit (UserPromptSubmit) — classify the submitted prompt, persist a
|
|
13
|
+
# verdict to .build-loop/routing/last-verdict.json, and — only
|
|
14
|
+
# when superpowers is installed and the prompt is build-shaped —
|
|
15
|
+
# inject a short authoritative routing note. Fires AFTER all
|
|
16
|
+
# SessionStart injections already sit in context, so it wins on
|
|
17
|
+
# salience without a SessionStart-vs-SessionStart ordering race.
|
|
18
|
+
#
|
|
19
|
+
# pre-skill (PreToolUse, matcher "Skill") — if the model is about to invoke
|
|
20
|
+
# superpowers:brainstorming while the current turn's verdict is
|
|
21
|
+
# "build", DENY with a reason that redirects to build-loop. This
|
|
22
|
+
# is the deterministic layer: it does not depend on the model
|
|
23
|
+
# routing correctly; it catches the miss at the tool boundary.
|
|
24
|
+
#
|
|
25
|
+
# Design notes:
|
|
26
|
+
# * Fail-open ALWAYS. Any error -> emit nothing meaningful, exit 0. A routing
|
|
27
|
+
# guard must never break a session. See memory reference_hooks_minimal_path_failopen.
|
|
28
|
+
# * Advisory-by-design for legitimate ideation: a prompt that explicitly asks
|
|
29
|
+
# to brainstorm/ideate classifies as passthrough, so an intentional
|
|
30
|
+
# brainstorming request is never blocked ("named override").
|
|
31
|
+
# * Opt out with BUILD_LOOP_ROUTE_GUARD=off (mirrors RALLY_HOOKS=off).
|
|
32
|
+
|
|
33
|
+
import glob
|
|
34
|
+
import json
|
|
35
|
+
import os
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
VERDICT_TTL_SECONDS = 2 * 60 * 60 # a verdict older than this can never block
|
|
41
|
+
GUARDED_SKILLS = {"superpowers:brainstorming"}
|
|
42
|
+
|
|
43
|
+
# Build / fix / refactor intent — mirrors build-loop's own SKILL trigger vocab.
|
|
44
|
+
_BUILD_RE = re.compile(
|
|
45
|
+
r"\b(build|implement|creat(e|ing)|add(ing)?|ship|wire\s*up|wire|integrat(e|ing)|"
|
|
46
|
+
r"refactor|migrat(e|ing)|rewrit(e|ing)|replac(e|ing)|scaffold|set\s*up|"
|
|
47
|
+
r"fix(ing)?|broken|debug|doesn'?t\s+work|isn'?t\s+(work|load|display)|"
|
|
48
|
+
r"not\s+(work|load|display|render)|should\s+(show|display|be)|needs?\s+to|make\s+it)\b",
|
|
49
|
+
re.IGNORECASE,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Explicit ideation intent — user WANTS open-ended thinking. Suppresses routing
|
|
53
|
+
# even if build verbs are also present ("named override").
|
|
54
|
+
_IDEATION_RE = re.compile(
|
|
55
|
+
r"\b(brainstorm(ing)?|ideat(e|ion)|explore\s+ideas?|think\s+through|"
|
|
56
|
+
r"weigh\s+(the\s+)?options|options\s+for|what\s+should|help\s+me\s+decide|"
|
|
57
|
+
r"pros\s+and\s+cons|design\s+options|figure\s+out\s+what)\b",
|
|
58
|
+
re.IGNORECASE,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
ROUTING_NOTE = (
|
|
62
|
+
"[build-loop routing] This looks like a build/fix/refactor task. Enter via "
|
|
63
|
+
"/build-loop:build-loop, which runs its own assess->plan phases. Do NOT "
|
|
64
|
+
"auto-invoke superpowers:brainstorming (or other superpowers process skills) "
|
|
65
|
+
"for this unless the user names it explicitly."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
DENY_REASON = (
|
|
69
|
+
"build-loop owns build/fix/refactor routing in this repo, and the current "
|
|
70
|
+
"prompt was classified as a build task (not an explicit ideation request). "
|
|
71
|
+
"Route via /build-loop:build-loop instead of superpowers:brainstorming. If "
|
|
72
|
+
"the user actually asked to brainstorm, re-read the prompt and proceed; if "
|
|
73
|
+
"this guard is unwanted, set BUILD_LOOP_ROUTE_GUARD=off."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _read_stdin():
|
|
78
|
+
try:
|
|
79
|
+
raw = sys.stdin.read()
|
|
80
|
+
return json.loads(raw) if raw.strip() else {}
|
|
81
|
+
except Exception:
|
|
82
|
+
return {}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _is_build(prompt: str) -> bool:
|
|
86
|
+
if not prompt:
|
|
87
|
+
return False
|
|
88
|
+
if _IDEATION_RE.search(prompt):
|
|
89
|
+
return False
|
|
90
|
+
return bool(_BUILD_RE.search(prompt))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _project_dir(payload) -> str:
|
|
94
|
+
return (
|
|
95
|
+
os.environ.get("CLAUDE_PROJECT_DIR")
|
|
96
|
+
or payload.get("cwd")
|
|
97
|
+
or os.getcwd()
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _verdict_path(payload):
|
|
102
|
+
proj = _project_dir(payload)
|
|
103
|
+
if not proj:
|
|
104
|
+
return None
|
|
105
|
+
return os.path.join(proj, ".build-loop", "routing", "last-verdict.json")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _superpowers_installed() -> bool:
|
|
109
|
+
home = os.path.expanduser("~")
|
|
110
|
+
patterns = [
|
|
111
|
+
os.path.join(home, ".claude", "plugins", "cache", "*", "superpowers"),
|
|
112
|
+
os.path.join(home, ".claude", "plugins", "*", "superpowers"),
|
|
113
|
+
os.path.join(home, ".claude", "plugins", "*superpowers*"),
|
|
114
|
+
]
|
|
115
|
+
for pat in patterns:
|
|
116
|
+
if glob.glob(pat):
|
|
117
|
+
return True
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _emit(obj):
|
|
122
|
+
try:
|
|
123
|
+
sys.stdout.write(json.dumps(obj))
|
|
124
|
+
except Exception:
|
|
125
|
+
sys.stdout.write("{}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def mode_prompt_submit(payload):
|
|
129
|
+
prompt = payload.get("prompt", "") or ""
|
|
130
|
+
is_build = _is_build(prompt)
|
|
131
|
+
|
|
132
|
+
# Persist verdict for the pre-skill guard (best-effort).
|
|
133
|
+
path = _verdict_path(payload)
|
|
134
|
+
if path:
|
|
135
|
+
try:
|
|
136
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
137
|
+
with open(path, "w") as fh:
|
|
138
|
+
json.dump(
|
|
139
|
+
{
|
|
140
|
+
"verdict": "build" if is_build else "passthrough",
|
|
141
|
+
"ts": int(time.time()),
|
|
142
|
+
"prompt": prompt[:200],
|
|
143
|
+
},
|
|
144
|
+
fh,
|
|
145
|
+
)
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
# Proactively bias routing only when there's a real conflict to resolve.
|
|
150
|
+
if is_build and _superpowers_installed():
|
|
151
|
+
_emit(
|
|
152
|
+
{
|
|
153
|
+
"hookSpecificOutput": {
|
|
154
|
+
"hookEventName": "UserPromptSubmit",
|
|
155
|
+
"additionalContext": ROUTING_NOTE,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
sys.stdout.write("{}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def mode_pre_skill(payload):
|
|
164
|
+
tool = payload.get("tool_name") or payload.get("tool") or ""
|
|
165
|
+
if tool != "Skill":
|
|
166
|
+
sys.stdout.write("{}")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
tool_input = payload.get("tool_input") or {}
|
|
170
|
+
skill = (tool_input.get("skill") or tool_input.get("name") or "").strip()
|
|
171
|
+
if skill not in GUARDED_SKILLS:
|
|
172
|
+
sys.stdout.write("{}")
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
# Only deny when the current turn was classified as a build task.
|
|
176
|
+
path = _verdict_path(payload)
|
|
177
|
+
verdict = None
|
|
178
|
+
if path and os.path.exists(path):
|
|
179
|
+
try:
|
|
180
|
+
with open(path) as fh:
|
|
181
|
+
data = json.load(fh)
|
|
182
|
+
if int(time.time()) - int(data.get("ts", 0)) <= VERDICT_TTL_SECONDS:
|
|
183
|
+
verdict = data.get("verdict")
|
|
184
|
+
except Exception:
|
|
185
|
+
verdict = None
|
|
186
|
+
|
|
187
|
+
if verdict == "build":
|
|
188
|
+
_emit(
|
|
189
|
+
{
|
|
190
|
+
"hookSpecificOutput": {
|
|
191
|
+
"hookEventName": "PreToolUse",
|
|
192
|
+
"permissionDecision": "deny",
|
|
193
|
+
"permissionDecisionReason": DENY_REASON,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
sys.stdout.write("{}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def main():
|
|
202
|
+
# Escape hatch + hard fail-open wrapper.
|
|
203
|
+
if os.environ.get("BUILD_LOOP_ROUTE_GUARD", "").lower() == "off":
|
|
204
|
+
sys.stdout.write("{}")
|
|
205
|
+
return
|
|
206
|
+
try:
|
|
207
|
+
mode = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
208
|
+
payload = _read_stdin()
|
|
209
|
+
if mode == "prompt-submit":
|
|
210
|
+
mode_prompt_submit(payload)
|
|
211
|
+
elif mode == "pre-skill":
|
|
212
|
+
mode_pre_skill(payload)
|
|
213
|
+
else:
|
|
214
|
+
sys.stdout.write("{}")
|
|
215
|
+
except Exception:
|
|
216
|
+
# Never break a session over a routing hint.
|
|
217
|
+
sys.stdout.write("{}")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
set -u
|
|
4
|
+
PY="$(command -v python3 || true)"; [ -z "$PY" ] && exit 0
|
|
5
|
+
ROOT="${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}"
|
|
6
|
+
N="$("$PY" "$ROOT/scripts/extensions_pending_count.py" 2>/dev/null || echo 0)"
|
|
7
|
+
if [ "${N:-0}" -gt 0 ] 2>/dev/null; then
|
|
8
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"build-loop: %s pending extension draft(s) await review — run: python3 %s/scripts/extensions_approve.py --list"}}\n' "$N" "$ROOT"
|
|
9
|
+
fi
|
|
10
|
+
exit 0
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
# Auto-install build-loop git hooks on session start (idempotent).
|
|
4
|
-
# Installs: pre-push (
|
|
4
|
+
# Installs: pre-push (deploy-HOLD gate + deterministic pre-push TEST gate, composed
|
|
5
|
+
# in one hook body — the test gate added per the 2026-06-29 RCA so new sessions get
|
|
6
|
+
# the prevent-before-merge control automatically) + the artifact-guard pre-commit segment
|
|
7
|
+
# (regenerate-and-restage checked-in artifacts; block undeclared third-party
|
|
8
|
+
# imports — chained so it coexists with the rally-point private-slug segment).
|
|
5
9
|
# Skips silently when: not inside a git repo, Python unavailable, hook
|
|
6
10
|
# source not found, or a foreign (non-build-loop) pre-push is already
|
|
7
11
|
# installed (requires --force to overwrite; we never force on auto-install).
|
|
@@ -9,4 +13,5 @@ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-$PWD}}"
|
|
|
9
13
|
cd "${CLAUDE_PROJECT_DIR:-$PWD}" 2>/dev/null || exit 0
|
|
10
14
|
git rev-parse --git-dir >/dev/null 2>&1 || exit 0
|
|
11
15
|
python3 "${PLUGIN_ROOT}/scripts/install_git_hooks.py" --install >/dev/null 2>&1 || true
|
|
16
|
+
python3 "${PLUGIN_ROOT}/scripts/artifact_guard.py" --install-hook >/dev/null 2>&1 || true
|
|
12
17
|
exit 0
|
|
@@ -38,7 +38,29 @@ else
|
|
|
38
38
|
--workdir "$W" --tool claude_code --mode hook --start-watch >/dev/null 2>&1 || true
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
|
-
# Step 3:
|
|
41
|
+
# Step 3: opportunistic reaper sweep — physical cleanup of over-TTL presence/claims/lead.
|
|
42
|
+
# Fire-and-forget: never blocks, exit 0 preserved. Python fallback path (actuator).
|
|
43
|
+
[ -f "$PKG/reaper.py" ] && python3 "$PKG/reaper.py" --workdir "$W" --apply >/dev/null 2>&1 || true
|
|
44
|
+
|
|
45
|
+
# Step 4: advance cursor past probe's own writes (silent; same script, no VERBOSE).
|
|
42
46
|
python3 "$PKG/hooks.py" session-start-advance --workdir "$W" 2>/dev/null || true
|
|
43
47
|
|
|
48
|
+
# Step 5: on-PATH-vs-pin rally version staleness guard (lane E-a). This is
|
|
49
|
+
# the control that would have caught the rally version-mismatch incident:
|
|
50
|
+
# warn (never block) when the `rally` binary resolved from PATH is not the
|
|
51
|
+
# version build-loop is pinned to. NON-BLOCKING + fail-open — on any error
|
|
52
|
+
# (rally absent, python3 failure, unparseable output) this silently no-ops
|
|
53
|
+
# and NEVER affects the hook's exit code.
|
|
54
|
+
if command -v rally >/dev/null 2>&1; then
|
|
55
|
+
_rally_raw_ver="$(rally version 2>&1 | head -n1)"
|
|
56
|
+
_rally_on_path_ver="${_rally_raw_ver#rally }"
|
|
57
|
+
_rally_on_path_ver="${_rally_on_path_ver%%+*}"
|
|
58
|
+
_rally_on_path_ver="$(printf '%s' "$_rally_on_path_ver" | tr -d '[:space:]')"
|
|
59
|
+
_rally_pinned_ver="$(python3 "$PKG/binary_fetch.py" --print-pin 2>/dev/null | tr -d '[:space:]')"
|
|
60
|
+
if [ -n "$_rally_on_path_ver" ] && [ -n "$_rally_pinned_ver" ] \
|
|
61
|
+
&& [ "$_rally_on_path_ver" != "$_rally_pinned_ver" ]; then
|
|
62
|
+
echo "[rally] WARN: on-PATH rally ${_rally_on_path_ver} != pinned ${_rally_pinned_ver} — run 'python3 scripts/rally_point/binary_fetch.py --force' to reconcile" >&2
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
44
66
|
exit 0
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
# Fixture-driven tests for the on-PATH-vs-pin rally version staleness guard
|
|
5
|
+
# (lane E-a) added to session-start-rally-point.sh Step 5. This is the
|
|
6
|
+
# control that would have caught the rally version-mismatch incident: the
|
|
7
|
+
# guard must be NON-BLOCKING and fail-open in every case below — it must
|
|
8
|
+
# NEVER affect the hook's exit code, whether rally is absent, matches the
|
|
9
|
+
# pin, or is stale.
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
14
|
+
SS_HOOK="${SCRIPT_DIR}/session-start-rally-point.sh"
|
|
15
|
+
PKG="${REPO_ROOT}/scripts/rally_point"
|
|
16
|
+
|
|
17
|
+
PASS=0
|
|
18
|
+
FAIL=0
|
|
19
|
+
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
|
20
|
+
fail() { echo "FAIL: $1 — $2"; FAIL=$((FAIL + 1)); }
|
|
21
|
+
|
|
22
|
+
# Resolve the REAL pinned version from binary_fetch.py so this test tracks
|
|
23
|
+
# the pin instead of hardcoding a copy that can silently drift.
|
|
24
|
+
PINNED_VER="$(python3 - <<PYEOF
|
|
25
|
+
import sys
|
|
26
|
+
sys.path.insert(0, "${REPO_ROOT}/scripts")
|
|
27
|
+
from rally_point import binary_fetch as bf
|
|
28
|
+
print(bf.PINNED_VERSION)
|
|
29
|
+
PYEOF
|
|
30
|
+
)"
|
|
31
|
+
if [ -z "$PINNED_VER" ]; then
|
|
32
|
+
echo "FAIL: setup — could not resolve PINNED_VERSION from binary_fetch.py"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
REPO=$(mktemp -d)
|
|
37
|
+
APPS=$(mktemp -d)
|
|
38
|
+
( cd "$REPO" && git init -q && git config user.email t@e.com && git config user.name t )
|
|
39
|
+
export BUILD_LOOP_APPS_ROOT="$APPS"
|
|
40
|
+
export BUILD_LOOP_BRIDGE_INTERNAL_ONLY=1
|
|
41
|
+
export BUILD_LOOP_RALLY_POINT_SKIP_WATCH=1
|
|
42
|
+
export CLAUDE_PROJECT_DIR="$REPO"
|
|
43
|
+
|
|
44
|
+
STUBBIN=$(mktemp -d)
|
|
45
|
+
# Isolated PATH: the stub bin dir first, then just enough of the real system
|
|
46
|
+
# to run python3/git/bash — deliberately excludes any real `rally` that may
|
|
47
|
+
# be installed on this machine's normal PATH (e.g. ~/.local/bin/rally).
|
|
48
|
+
PYTHON3_DIR="$(dirname "$(command -v python3)")"
|
|
49
|
+
GIT_DIR="$(dirname "$(command -v git)")"
|
|
50
|
+
ISOLATED_PATH="$STUBBIN:$PYTHON3_DIR:$GIT_DIR:/usr/bin:/bin"
|
|
51
|
+
|
|
52
|
+
write_stub_rally() {
|
|
53
|
+
# $1 = version string the stub's "version" subcommand should print
|
|
54
|
+
local ver="$1"
|
|
55
|
+
cat > "$STUBBIN/rally" <<STUBEOF
|
|
56
|
+
#!/usr/bin/env bash
|
|
57
|
+
if [ "\$1" = "version" ]; then
|
|
58
|
+
echo "rally ${ver}"
|
|
59
|
+
fi
|
|
60
|
+
exit 0
|
|
61
|
+
STUBEOF
|
|
62
|
+
chmod +x "$STUBBIN/rally"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
run_hook_stderr() {
|
|
66
|
+
PATH="$ISOLATED_PATH" bash "$SS_HOOK" 2>&1 1>/dev/null </dev/null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
run_hook_rc() {
|
|
70
|
+
PATH="$ISOLATED_PATH" bash "$SS_HOOK" >/dev/null 2>&1 </dev/null
|
|
71
|
+
echo $?
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ---- Case 1: on-PATH version matches the pin -> no WARN, exit 0 --------
|
|
75
|
+
write_stub_rally "${PINNED_VER}+deadbeef"
|
|
76
|
+
ERR=$(run_hook_stderr)
|
|
77
|
+
RC=$(run_hook_rc)
|
|
78
|
+
if [ "$RC" -eq 0 ] && ! printf '%s' "$ERR" | grep -q "WARN"; then
|
|
79
|
+
pass "matching on-PATH version: no WARN, exit 0"
|
|
80
|
+
else
|
|
81
|
+
fail "matching version case" "rc=$RC stderr='$ERR'"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ---- Case 2: on-PATH version mismatches the pin -> WARN line, exit 0 ---
|
|
85
|
+
write_stub_rally "9.9.9+deadbeef"
|
|
86
|
+
ERR=$(run_hook_stderr)
|
|
87
|
+
RC=$(run_hook_rc)
|
|
88
|
+
if [ "$RC" -eq 0 ] \
|
|
89
|
+
&& printf '%s' "$ERR" | grep -q "\[rally\] WARN:" \
|
|
90
|
+
&& printf '%s' "$ERR" | grep -q "9.9.9" \
|
|
91
|
+
&& printf '%s' "$ERR" | grep -q "${PINNED_VER}"; then
|
|
92
|
+
pass "mismatched on-PATH version: WARN line present, exit 0"
|
|
93
|
+
else
|
|
94
|
+
fail "mismatched version case" "rc=$RC stderr='$ERR'"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# ---- Case 3: rally absent from PATH -> no crash, no WARN, exit 0 -------
|
|
98
|
+
rm -f "$STUBBIN/rally"
|
|
99
|
+
ERR=$(run_hook_stderr)
|
|
100
|
+
RC=$(run_hook_rc)
|
|
101
|
+
if [ "$RC" -eq 0 ] && ! printf '%s' "$ERR" | grep -q "WARN"; then
|
|
102
|
+
pass "rally absent from PATH: no crash, no WARN, exit 0"
|
|
103
|
+
else
|
|
104
|
+
fail "rally absent case" "rc=$RC stderr='$ERR'"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# ---- Case 4: rally on PATH but `rally version` itself errors (exit 1,
|
|
108
|
+
# empty output) -> guard silently no-ops, exit 0 preserved. ---------------
|
|
109
|
+
cat > "$STUBBIN/rally" <<'STUBEOF'
|
|
110
|
+
#!/usr/bin/env bash
|
|
111
|
+
exit 1
|
|
112
|
+
STUBEOF
|
|
113
|
+
chmod +x "$STUBBIN/rally"
|
|
114
|
+
ERR=$(run_hook_stderr)
|
|
115
|
+
RC=$(run_hook_rc)
|
|
116
|
+
if [ "$RC" -eq 0 ] && ! printf '%s' "$ERR" | grep -q "WARN"; then
|
|
117
|
+
pass "rally version errors: no crash, no WARN, exit 0"
|
|
118
|
+
else
|
|
119
|
+
fail "rally version error case" "rc=$RC stderr='$ERR'"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
123
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyroneross/build-loop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://npm.pkg.github.com",
|
|
6
6
|
"access": "public"
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"description": "Multi-phase build orchestration with integrated debugging: assess, plan, execute, review, iterate.",
|
|
9
9
|
"main": "dist/src/index.js",
|
|
10
10
|
"types": "dist/src/index.d.ts",
|
|
11
|
+
"bin": {
|
|
12
|
+
"build-loop-install": "bin/build-loop-install.js"
|
|
13
|
+
},
|
|
11
14
|
"exports": {
|
|
12
15
|
".": {
|
|
13
16
|
"types": "./dist/src/index.d.ts",
|
|
@@ -17,8 +20,10 @@
|
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
22
|
"dist",
|
|
23
|
+
"bin",
|
|
20
24
|
"codex-skills",
|
|
21
25
|
"plugin-artifacts/codex",
|
|
26
|
+
"assets",
|
|
22
27
|
"commands",
|
|
23
28
|
"skills",
|
|
24
29
|
"scripts/_paths.py",
|
|
@@ -58,7 +63,7 @@
|
|
|
58
63
|
"build": "tsc",
|
|
59
64
|
"watch": "tsc --watch",
|
|
60
65
|
"prepublishOnly": "npm run build",
|
|
61
|
-
"test": "python3 -m unittest scripts/test_commit_state_check.py scripts/test_bridge_preflight.py scripts/test_mcp_registration.py && bash scripts/hooks/test_hooks.sh",
|
|
66
|
+
"test": "python3 -m unittest scripts/test_commit_state_check.py scripts/test_bridge_preflight.py scripts/test_mcp_registration.py scripts/test_build_loop_install_cli.py && bash scripts/hooks/test_hooks.sh",
|
|
62
67
|
"benchmark": "node benchmark-scoring.js",
|
|
63
68
|
"clean": "rm -rf dist",
|
|
64
69
|
"plugin:prune-cache": "python3 scripts/prune_plugin_cache.py --source .",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "build-loop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"description": "Multi-phase build orchestration with native debugging: assess, plan, execute, review, iterate. Plan-verify gate on Phase 2. Optional UI design gates. Native debug-loop, debugging-memory, and logging-tracer skills provide root-cause investigation without a bundled MCP server. Orchestrator owns when-to-fire; bundled skills own procedural detail; bridges are optional escalation hops to supporting plugins for extended capability.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tyrone Ross",
|