@tyroneross/build-loop 0.35.0 → 0.36.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 (151) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/AGENTS.md +67 -14
  6. package/README.md +208 -299
  7. package/agents/advisor.md +2 -0
  8. package/agents/alignment-checker.md +2 -0
  9. package/agents/api-assessor.md +3 -1
  10. package/agents/architecture-scout.md +2 -0
  11. package/agents/assessment-orchestrator.md +2 -0
  12. package/agents/build-orchestrator.md +13 -8
  13. package/agents/database-assessor.md +107 -3
  14. package/agents/design-contract-specialist.md +2 -0
  15. package/agents/fact-checker.md +2 -0
  16. package/agents/fix-critique.md +26 -5
  17. package/agents/frontend-assessor.md +3 -1
  18. package/agents/implementer.md +2 -0
  19. package/agents/independent-auditor.md +2 -0
  20. package/agents/mock-scanner.md +2 -0
  21. package/agents/optimize-runner.md +2 -0
  22. package/agents/overfitting-reviewer.md +2 -0
  23. package/agents/performance-assessor.md +3 -1
  24. package/agents/plan-critic.md +2 -0
  25. package/agents/promotion-reviewer.md +2 -0
  26. package/agents/recurring-pattern-detector.md +4 -1
  27. package/agents/retrospective-synthesizer.md +14 -0
  28. package/agents/root-cause-investigator.md +39 -2
  29. package/agents/scope-auditor.md +2 -0
  30. package/agents/security-reviewer.md +3 -0
  31. package/agents/self-improvement-architect.md +2 -0
  32. package/agents/synthesis-critic.md +2 -0
  33. package/agents/transcript-pattern-miner.md +2 -0
  34. package/agents/ui-validator.md +2 -0
  35. package/assets/build-loop-plugin-icon.png +0 -0
  36. package/bin/build-loop-install.js +234 -0
  37. package/commands/run.md +1 -1
  38. package/hooks/git/pre-commit +40 -0
  39. package/hooks/git/pre-push +35 -10
  40. package/hooks/hooks.json +42 -5
  41. package/hooks/pre-edit-architecture.sh +2 -2
  42. package/hooks/route-guard.sh +15 -0
  43. package/hooks/route_guard.py +221 -0
  44. package/hooks/session-start-extensions.sh +10 -0
  45. package/hooks/session-start-git-hooks.sh +6 -1
  46. package/hooks/session-start-rally-point.sh +23 -1
  47. package/hooks/test_session_start_rally_version.sh +123 -0
  48. package/package.json +7 -2
  49. package/plugin-artifacts/codex/.codex-plugin/plugin.json +1 -1
  50. package/plugin-artifacts/codex/AGENTS.md +67 -14
  51. package/plugin-artifacts/codex/README.md +208 -299
  52. package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
  53. package/plugin-artifacts/codex/references/backlog-system.md +285 -0
  54. package/plugin-artifacts/codex/references/capability-routing.md +49 -0
  55. package/plugin-artifacts/codex/references/coordination-rules.md +271 -2
  56. package/plugin-artifacts/codex/references/memory-systems.md +25 -2
  57. package/plugin-artifacts/codex/references/memory.md +14 -0
  58. package/plugin-artifacts/codex/references/model-tier-mapping.md +45 -6
  59. package/plugin-artifacts/codex/references/phase-1-assess.md +19 -1
  60. package/plugin-artifacts/codex/references/phase-2-plan.md +9 -1
  61. package/plugin-artifacts/codex/references/phase-3-execute.md +1 -1
  62. package/plugin-artifacts/codex/references/phase-4-review.md +20 -1
  63. package/plugin-artifacts/codex/references/phase-5-iterate.md +4 -3
  64. package/plugin-artifacts/codex/references/phase-6-learn.md +2 -0
  65. package/plugin-artifacts/codex/references/verify-dispatch.md +85 -0
  66. package/plugin-artifacts/codex/skills/build-loop/SKILL.md +19 -10
  67. package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +1 -1
  68. package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +49 -0
  69. package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +101 -0
  70. package/plugin-artifacts/codex/skills/build-loop/references/memory.md +14 -0
  71. package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +98 -5
  72. package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +19 -1
  73. package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +9 -1
  74. package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +1 -1
  75. package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +20 -1
  76. package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +4 -3
  77. package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +2 -0
  78. package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +85 -0
  79. package/scripts/build_codex_plugin_artifact.py +7 -0
  80. package/scripts/embed_backend.py +17 -13
  81. package/scripts/lessons_index/query.py +1 -1
  82. package/scripts/memory_graph/__init__.py +2 -2
  83. package/scripts/sync_navgator_lessons.py +4 -4
  84. package/skills/api-registry-bridge/SKILL.md +1 -1
  85. package/skills/auto-decision-capture/SKILL.md +4 -3
  86. package/skills/auto-finding-capture/SKILL.md +119 -0
  87. package/skills/build-loop/SKILL.md +19 -10
  88. package/skills/build-loop/fallbacks.md +1 -1
  89. package/skills/build-loop/references/capability-routing.md +49 -0
  90. package/skills/build-loop/references/experiment-results-template.md +101 -0
  91. package/skills/build-loop/references/memory.md +14 -0
  92. package/skills/build-loop/references/output-style.md +98 -5
  93. package/skills/build-loop/references/phase-1-assess.md +19 -1
  94. package/skills/build-loop/references/phase-2-plan.md +9 -1
  95. package/skills/build-loop/references/phase-3-execute.md +1 -1
  96. package/skills/build-loop/references/phase-4-review.md +20 -1
  97. package/skills/build-loop/references/phase-5-iterate.md +4 -3
  98. package/skills/build-loop/references/phase-6-learn.md +2 -0
  99. package/skills/build-loop/references/verify-dispatch.md +85 -0
  100. package/skills/debug-loop/SKILL.md +4 -2
  101. package/skills/debugging-memory/SKILL.md +24 -12
  102. package/skills/{debugging/assess/SKILL.md → debugging-memory/references/assess.md} +6 -13
  103. package/skills/{debugging/memory/SKILL.md → debugging-memory/references/search.md} +10 -17
  104. package/skills/{debugging/store/SKILL.md → debugging-memory/references/store.md} +5 -13
  105. package/skills/debugging-memory/references/subagent-integration.md +3 -3
  106. package/skills/defenseclaw-bridge/SKILL.md +1 -1
  107. package/skills/{loop-builder → focused-loop-builder}/SKILL.md +6 -6
  108. package/skills/ibr-bridge/SKILL.md +1 -1
  109. package/skills/knowledge/SKILL.md +13 -2
  110. package/skills/{knowledge-review/SKILL.md → knowledge/references/review-mode.md} +8 -20
  111. package/skills/logging-tracer/SKILL.md +20 -4
  112. package/skills/model-bakeoff/SKILL.md +44 -0
  113. package/skills/model-tiering/SKILL.md +55 -20
  114. package/skills/optimize/SKILL.md +2 -2
  115. package/skills/prd-bridge/SKILL.md +1 -1
  116. package/skills/recursive-retrospective/SKILL.md +46 -0
  117. package/skills/recursive-retrospective/references/01-retrospective.md +136 -0
  118. package/skills/recursive-retrospective/references/02-learning-capture.md +72 -0
  119. package/skills/recursive-retrospective/references/03-judge.md +45 -0
  120. package/skills/research/SKILL.md +1 -1
  121. package/skills/root-cause-analysis/SKILL.md +42 -0
  122. package/skills/security-methodology/SKILL.md +10 -1
  123. package/skills/security-scan/SKILL.md +55 -0
  124. package/skills/spec-writing/SKILL.md +42 -1
  125. package/skills/sync-skills/SKILL.md +8 -7
  126. package/commands/assess.md +0 -41
  127. package/commands/compose-handoff.md +0 -113
  128. package/commands/debug.md +0 -20
  129. package/commands/debugger-detail.md +0 -34
  130. package/commands/debugger-scan.md +0 -15
  131. package/commands/debugger-status.md +0 -20
  132. package/commands/debugger.md +0 -32
  133. package/commands/optimize-run.md +0 -31
  134. package/commands/promote-experiment.md +0 -129
  135. package/commands/rally-point.md +0 -301
  136. package/commands/research-run.md +0 -22
  137. package/commands/review-knowledge.md +0 -16
  138. package/commands/self-review.md +0 -51
  139. package/commands/setup-memory.md +0 -32
  140. package/commands/start-prd.md +0 -57
  141. package/commands/test.md +0 -34
  142. package/commands/verify-plan.md +0 -39
  143. package/skills/debugging/debug-loop/SKILL.md +0 -231
  144. package/skills/logging-tracer-bridge/SKILL.md +0 -70
  145. /package/skills/{loop-builder → focused-loop-builder}/presets/active-project-evidence.yaml +0 -0
  146. /package/skills/{loop-builder → focused-loop-builder}/presets/generic-artifact-loop.yaml +0 -0
  147. /package/skills/{loop-builder → focused-loop-builder}/presets/presentation-audit.yaml +0 -0
  148. /package/skills/{loop-builder → focused-loop-builder}/presets/research-synthesis.yaml +0 -0
  149. /package/skills/{loop-builder → focused-loop-builder}/presets/source-ingestion-raw-data-audit.yaml +0 -0
  150. /package/skills/{loop-builder → focused-loop-builder}/references/spec-format.md +0 -0
  151. /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 (push-HOLD gate).
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: advance cursor past probe's own writes (silent; same script, no VERBOSE).
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.35.0",
3
+ "version": "0.36.0",
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.35.0",
3
+ "version": "0.36.0",
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",