@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
@@ -2,6 +2,8 @@
2
2
  name: root-cause-investigator
3
3
  description: Use this agent when a debugging symptom needs deep causal analysis beyond surface-level diagnosis. Builds a causal tree (not a single chain) to explore multiple potential root causes in parallel. Flags when investigation reaches external/environmental boundaries or when internet research is needed. Examples - "why does this keep failing", "what's the real cause", "dig deeper into this error", "this fix didn't stick".
4
4
  model: inherit
5
+ tier: inherit
6
+ segment: inherit
5
7
  color: red
6
8
  tools: ["Read", "Grep", "Bash", "Glob", "WebSearch"]
7
9
  ---
@@ -30,6 +32,8 @@ Instead, build a **causal tree**: at each level, identify ALL plausible causes,
30
32
 
31
33
  ## Your Core Responsibilities
32
34
 
35
+ > **Durable post-failure RCA:** for the blameless durable-lever pass (creation+escape paths, action-strength hierarchy, lever+actuator, regression artifact, spread check), delegate to the shared `references/root-cause-analysis/` suite. This skill/agent finds and fixes the live issue; that suite is the post-failure prevention layer.
36
+
33
37
  1. Build a causal tree — at each node, identify multiple possible causes before pursuing any
34
38
  2. Investigate branches by evidence strength, not by order of appearance
35
39
  3. Determine when the real root cause is found (it explains ALL symptoms)
@@ -66,6 +70,12 @@ SYMPTOM: API returns empty array for search
66
70
  - If I ruled out my first guess, what would I investigate next?
67
71
  - Could this be caused by something upstream? Downstream? Environmental?
68
72
 
73
+ **Trace two distinct axes, not one chain — creation and escape:**
74
+ - **Creation** — why did the defect exist at all? (its origin)
75
+ - **Escape** — why did no control catch it before it reached the surface? (the detection gap)
76
+
77
+ A bug often needs both fixed, and a single chain nudges toward fixing only one. Worked example: a value was computed wrong (`int(dict)→0` — *creation*) AND the test injected that value by hand, so the gate's blindness to the real shape never surfaced (*escape*). Fixing only the gate, or only the writer, leaves half the bug live.
78
+
69
79
  ### Step 3: Prioritize — Rank Branches by Evidence
70
80
 
71
81
  Before investigating any branch, quickly assess each:
@@ -122,7 +132,7 @@ Add environment findings as branches in the causal tree with `evidence_type: "en
122
132
 
123
133
  Stop investigating a branch when you reach one of:
124
134
 
125
- - **Actionable system cause**: A concrete, fixable control failure (missing check, weak contract, ambiguous ownership, stale model/cache, missing feedback, wrong assumption) with evidence
135
+ - **Actionable system cause that passes the counterfactual**: A concrete, fixable control failure (missing check, weak contract, ambiguous ownership, stale model/cache, missing feedback, wrong assumption) with evidence — **and it is not closed until the named lever + actuator would have prevented, detected, or contained THIS exact failure before it reached the surface.** State this as a one-line counterfactual with the evidence that the lever fires on the *real* input, not a hand-constructed one. A control that is "actionable" but dormant on the real signal — a rule that exists yet never fires on the actual phrasing/shape that triggered the bug — does NOT close the branch. (Observed: a shipped `activation-map-required` rule was dormant on 2 of its 4 motivating phrasings; the counterfactual is the test that catches that.)
126
136
  - **External boundary**: The cause is outside the codebase (OS behavior, library bug, third-party API change) — document and flag
127
137
  - **Depth limit**: After 5 levels deep on any branch, the problem may be architectural — report findings and recommend broader investigation
128
138
  - **All branches pruned**: Every plausible cause has been rejected with evidence — the symptom may have an unusual or environmental cause. Flag for user input
@@ -167,6 +177,28 @@ Common traps where surface-level diagnosis stops too early:
167
177
  | "The component re-renders" | Why does the dependency change? | Object identity not stable across renders |
168
178
  | "It works locally but not in CI" | What differs between environments? | Missing env var in CI config |
169
179
 
180
+ ## Fix Strength — Prefer the Stronger Control (W3)
181
+
182
+ When you name the `prevention_control`, prefer the strongest *feasible* rung — do not default to "add a detect-gate." Strength order, strongest first:
183
+
184
+ 1. **eliminate** — remove the failure mode entirely (delete the code path / dependency)
185
+ 2. **impossible-state** — make the invalid state unrepresentable (normalize at the *writer* so the bad shape can never exist, vs. coercing at the gate)
186
+ 3. **automated-block** — a gate that hard-fails the bad input before it propagates
187
+ 4. **detect** — surface/alert on the bad state after it occurs
188
+ 5. **contain** — limit blast radius (isolate, validate, monitor, degrade gracefully, escalate, or accept residual risk *explicitly*)
189
+ 6. **decision-support** — give a human the signal to decide
190
+ 7. **docs** — record the hazard for future readers
191
+
192
+ Name the rung you chose in `fix_strength` and, if you did not pick the strongest, say why it was infeasible. **Never** route a dependency you don't own to "ignore it" — route it to isolate / validate / monitor / degrade / escalate / accept-residual-risk-explicitly.
193
+
194
+ ## Root-Cause Layer — Classify the Origin (W4)
195
+
196
+ For each confirmed root cause, classify its true origin layer (where the defect was *born*, not where the symptom surfaced) as exactly one of:
197
+
198
+ `input-data` · `requirements-spec` · `prompt-instruction` · `model-reasoning` · `tool-api` · `state-memory-cache` · `orchestration-workflow` · `permission-security` · `test-eval-gate` · `observability-alerting` · `human-handoff-process` · `external-dependency`
199
+
200
+ A multi-root tree carries one layer per confirmed branch. This field lets `recurring-pattern-detector` surface a project-shaped blind spot (e.g. three `test-eval-gate` roots across runs → fixtures are the systemic weak point) that free-text `root_cause` cannot cluster.
201
+
170
202
  ## Output Format
171
203
 
172
204
  Return a structured JSON assessment:
@@ -183,8 +215,13 @@ Return a structured JSON assessment:
183
215
  },
184
216
  "tradeoffs": "What the fix improves, what it risks, and what it does not solve",
185
217
  "impact": "User impact, engineering impact, recurrence risk",
186
- "prevention_control": "Durable control: test, verifier, lint, trace, smoke gate, protocol, memory, or routing rule",
218
+ "prevention_control": "Durable control: test, verifier, lint, trace, smoke gate, protocol, memory, or routing rule — at the strongest feasible rung (see Fix Strength)",
187
219
  "system_control_failure": "The first controllable system control that failed or was missing",
220
+ "counterfactual": "If <lever+actuator> had existed, it would have <prevented|detected|contained> this because <evidence the lever fires on the REAL input, not a hand-constructed one>",
221
+ "creation_path": "Why the defect existed at all — the origin axis",
222
+ "escape_path": "Why no control caught it before the surface — the detection axis",
223
+ "fix_strength": "eliminate | impossible-state | automated-block | detect | contain | decision-support | docs — the rung of prevention_control; if not the strongest, why the stronger rung was infeasible",
224
+ "root_cause_layer": "input-data | requirements-spec | prompt-instruction | model-reasoning | tool-api | state-memory-cache | orchestration-workflow | permission-security | test-eval-gate | observability-alerting | human-handoff-process | external-dependency",
188
225
  "failure_map": [
189
226
  "User-visible symptom",
190
227
  "Immediate technical failure",
@@ -2,6 +2,8 @@
2
2
  name: scope-auditor
3
3
  description: Read-only Plan→Execute boundary check. For every commit that changes a public function/component/type signature, traces every caller-site outside the commit's owned-files, then either confirms `internal_only: true` or appends the missing caller files to the appropriate commit's owned-files list. Prevents the "fan-out scope-blindness" defect class observed in round-2 of dispatch-pattern testing (example-app 2026-05-07).
4
4
  model: fable
5
+ tier: frontier
6
+ segment: governance_evaluation
5
7
  tools: ["Read", "Grep", "Glob"]
6
8
  ---
7
9
 
@@ -15,6 +15,8 @@ description: |
15
15
  assistant: "I'll use the security-reviewer agent — diff vs OWASP Web A01/A03 + LLM06 + ASI03 — and emit a structured findings report."
16
16
  </example>
17
17
  model: fable
18
+ tier: frontier
19
+ segment: governance_evaluation
18
20
  color: red
19
21
  tools: ["Read", "Grep", "Glob"]
20
22
  ---
@@ -91,6 +93,7 @@ Severity rules:
91
93
  1. Read `.build-loop/state.json.triggers`. If `riskSurfaceChange` is not true, emit `{"findings": [], "skipped_reason": "..."}` and stop.
92
94
  2. Read `.build-loop/goal.md` and `.build-loop/intent.md` — orient on what was supposed to change.
93
95
  3. Load `Skill("build-loop:security-methodology")`. Read the cross-source matrix and the detection-pattern files for the OWASP layer that applies (LLM Top 10 always; Agentic Top 10 when an agent or tool was added; Web Top 10 when an HTTP endpoint changed).
96
+ 3b. **Required route-auth enumeration (LO-5, A01).** When any HTTP endpoint changed, do not sample — **walk every** `app/api/**/route.ts` (or framework equivalent) mutating/DDL handler and confirm each has an auth guard that **fails closed** when its secret env is unset (the `token !== process.env.X` bypass: if `X` is undefined the check passes). This access-control sweep is the counterpart to `database-assessor`'s destructive-FK sweep — neither lens is a superset (atomize-ai 2026-06-30: 8 A/B runs here missed a destructive cascade; a DB-RCA missed 4 unauth routes). Lead findings with a blast-radius verdict per the methodology's SC-1 default.
94
97
  4. Get the file list from the orchestrator's dispatch packet. Read each changed file; do not scan files outside the chunk.
95
98
  5. For each change, walk the table above. When a row matches, draft a finding with mandatory fields below.
96
99
  6. Cross-reference each finding against `skills/security-methodology/references/cross-source-matrix.md` to assign `mapped_risks`. If no row in the matrix applies, the finding is not security — drop it (other agents handle non-security drift).
@@ -15,6 +15,8 @@ description: |
15
15
  assistant: "I'll use the self-improvement-architect agent to draft the agent definition."
16
16
  </example>
17
17
  model: sonnet
18
+ tier: code
19
+ segment: generative_reasoning
18
20
  color: cyan
19
21
  tools: ["Read", "Write", "Edit", "Glob", "Grep", "Skill"]
20
22
  ---
@@ -15,6 +15,8 @@ description: |
15
15
  assistant: "I'll use the synthesis-critic agent to read the diff against the claimed copy_tone register and empty-state pattern, returning a JSON verdict (pass | flag) without blocking."
16
16
  </example>
17
17
  model: sonnet
18
+ tier: code
19
+ segment: governance_evaluation
18
20
  color: cyan
19
21
  tools: ["Read", "Glob", "Grep"]
20
22
  ---
@@ -21,6 +21,8 @@ description: |
21
21
  assistant: "I'll use the transcript-pattern-miner agent. Section 5 of its report is a rotation tracker — truncated previews, first-seen and last-seen dates, project context."
22
22
  </example>
23
23
  model: haiku
24
+ tier: pattern
25
+ segment: generative_reasoning
24
26
  color: cyan
25
27
  tools: ["Bash", "Read"]
26
28
  ---
@@ -15,6 +15,8 @@ description: |
15
15
  assistant: "I'll use the ui-validator agent — build-loop's owned UI validation path — and return route-level evidence without routing through IBR."
16
16
  </example>
17
17
  model: sonnet
18
+ tier: code
19
+ segment: agentic_execution
18
20
  color: blue
19
21
  tools: ["Read", "Bash", "Grep", "Glob"]
20
22
  ---
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ "use strict";
6
+
7
+ const { spawnSync } = require("node:child_process");
8
+ const fs = require("node:fs");
9
+ const path = require("node:path");
10
+
11
+ const root = path.resolve(__dirname, "..");
12
+
13
+ function usage() {
14
+ return `build-loop-install
15
+
16
+ Install build-loop from this npm package into local agent caches.
17
+
18
+ Usage:
19
+ build-loop-install [options]
20
+
21
+ Options:
22
+ --host <all|claude|codex> Agent host cache to sync (default: all)
23
+ --project <slug> Ensure a project memory scaffold
24
+ --memory-dest <path> Override build-loop memory root
25
+ --skip-memory Do not bootstrap build-loop memory
26
+ --dry-run Show cache sync actions without writing
27
+ --no-verify Skip post-sync cache verification
28
+ --allow-non-mac Suppress the macOS platform warning
29
+ --json Emit one machine-readable JSON result
30
+ -h, --help Show help
31
+ `;
32
+ }
33
+
34
+ function parseArgs(argv) {
35
+ const args = {
36
+ host: "all",
37
+ projects: [],
38
+ memoryDest: null,
39
+ skipMemory: false,
40
+ dryRun: false,
41
+ noVerify: false,
42
+ allowNonMac: false,
43
+ json: false,
44
+ help: false,
45
+ };
46
+
47
+ for (let i = 0; i < argv.length; i += 1) {
48
+ const arg = argv[i];
49
+ const next = () => {
50
+ i += 1;
51
+ if (i >= argv.length) {
52
+ throw new Error(`${arg} requires a value`);
53
+ }
54
+ return argv[i];
55
+ };
56
+
57
+ if (arg === "-h" || arg === "--help") {
58
+ args.help = true;
59
+ } else if (arg === "--host") {
60
+ args.host = next();
61
+ } else if (arg === "--project") {
62
+ args.projects.push(next());
63
+ } else if (arg === "--memory-dest") {
64
+ args.memoryDest = next();
65
+ } else if (arg === "--skip-memory") {
66
+ args.skipMemory = true;
67
+ } else if (arg === "--dry-run") {
68
+ args.dryRun = true;
69
+ } else if (arg === "--no-verify") {
70
+ args.noVerify = true;
71
+ } else if (arg === "--allow-non-mac") {
72
+ args.allowNonMac = true;
73
+ } else if (arg === "--json") {
74
+ args.json = true;
75
+ } else {
76
+ throw new Error(`unknown option: ${arg}`);
77
+ }
78
+ }
79
+
80
+ if (!["all", "claude", "codex"].includes(args.host)) {
81
+ throw new Error("--host must be one of: all, claude, codex");
82
+ }
83
+
84
+ return args;
85
+ }
86
+
87
+ function findPython() {
88
+ for (const candidate of ["python3", "python"]) {
89
+ const probe = spawnSync(candidate, ["--version"], { encoding: "utf8" });
90
+ if (probe.status === 0) {
91
+ return candidate;
92
+ }
93
+ }
94
+ throw new Error("python3 is required to install build-loop");
95
+ }
96
+
97
+ function parseJsonMaybe(text) {
98
+ const trimmed = text.trim();
99
+ if (!trimmed) {
100
+ return null;
101
+ }
102
+ try {
103
+ return JSON.parse(trimmed);
104
+ } catch (_err) {
105
+ return trimmed;
106
+ }
107
+ }
108
+
109
+ function runStep(label, command, commandArgs, options) {
110
+ const result = spawnSync(command, commandArgs, {
111
+ cwd: root,
112
+ encoding: "utf8",
113
+ });
114
+
115
+ const step = {
116
+ label,
117
+ command: [command, ...commandArgs],
118
+ exitCode: result.status,
119
+ ok: result.status === 0,
120
+ stdout: result.stdout || "",
121
+ stderr: result.stderr || "",
122
+ };
123
+
124
+ if (options.json) {
125
+ step.parsedStdout = parseJsonMaybe(step.stdout);
126
+ return step;
127
+ }
128
+
129
+ process.stdout.write(`\n== ${label} ==\n`);
130
+ if (step.stdout) {
131
+ process.stdout.write(step.stdout);
132
+ if (!step.stdout.endsWith("\n")) process.stdout.write("\n");
133
+ }
134
+ if (step.stderr) {
135
+ process.stderr.write(step.stderr);
136
+ if (!step.stderr.endsWith("\n")) process.stderr.write("\n");
137
+ }
138
+ if (!step.ok) {
139
+ process.stderr.write(`${label} failed with exit ${step.exitCode}\n`);
140
+ }
141
+ return step;
142
+ }
143
+
144
+ function syncArgs({ host, source, args }) {
145
+ const cmd = [
146
+ path.join(root, "scripts", "sync_plugin_cache.py"),
147
+ "--source",
148
+ source,
149
+ "--host",
150
+ host,
151
+ "--marketplace",
152
+ "build-loop",
153
+ "--dirty",
154
+ ];
155
+ if (args.dryRun) cmd.push("--dry-run");
156
+ if (args.noVerify) cmd.push("--no-verify");
157
+ if (args.json) cmd.push("--json");
158
+ return cmd;
159
+ }
160
+
161
+ function memoryArgs(args) {
162
+ const cmd = [path.join(root, "scripts", "install_memory.py")];
163
+ if (args.memoryDest) {
164
+ cmd.push("--dest", args.memoryDest);
165
+ }
166
+ for (const project of args.projects) {
167
+ cmd.push("--ensure-project", project);
168
+ }
169
+ return cmd;
170
+ }
171
+
172
+ function hostsFor(host) {
173
+ return host === "all" ? ["claude", "codex"] : [host];
174
+ }
175
+
176
+ function main() {
177
+ let args;
178
+ try {
179
+ args = parseArgs(process.argv.slice(2));
180
+ } catch (err) {
181
+ process.stderr.write(`${err.message}\n\n${usage()}`);
182
+ return 2;
183
+ }
184
+
185
+ if (args.help) {
186
+ process.stdout.write(usage());
187
+ return 0;
188
+ }
189
+
190
+ const python = findPython();
191
+ const steps = [];
192
+ const codexArtifact = path.join(root, "plugin-artifacts", "codex");
193
+
194
+ if (process.platform !== "darwin" && !args.allowNonMac && !args.json) {
195
+ process.stderr.write(
196
+ "warning: this installer is optimized for macOS agent cache paths; continuing on this platform.\n"
197
+ );
198
+ }
199
+
200
+ for (const host of hostsFor(args.host)) {
201
+ const source = host === "codex" && fs.existsSync(codexArtifact) ? codexArtifact : root;
202
+ steps.push(
203
+ runStep(`sync ${host} plugin cache`, python, syncArgs({ host, source, args }), args)
204
+ );
205
+ }
206
+
207
+ if (!args.skipMemory && !args.dryRun) {
208
+ steps.push(runStep("bootstrap build-loop memory", python, memoryArgs(args), args));
209
+ } else if (args.dryRun && !args.json) {
210
+ process.stdout.write("\n== bootstrap build-loop memory ==\nskipped during --dry-run\n");
211
+ }
212
+
213
+ const payload = {
214
+ ok: steps.every((step) => step.ok),
215
+ packageRoot: root,
216
+ platform: process.platform,
217
+ host: args.host,
218
+ memory: args.skipMemory ? "skipped" : args.dryRun ? "dry-run-skipped" : "bootstrapped",
219
+ steps,
220
+ };
221
+
222
+ if (args.json) {
223
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
224
+ }
225
+
226
+ return payload.ok ? 0 : 1;
227
+ }
228
+
229
+ try {
230
+ process.exitCode = main();
231
+ } catch (err) {
232
+ process.stderr.write(`${err.message}\n`);
233
+ process.exitCode = 1;
234
+ }
package/commands/run.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "The single entry for any coding task — build, fix, refactor, optimize, research, or test. Auto-routes to the right mode; you don't pick."
2
+ description: "The one build-loop command. Describe anything in plain language — build, fix, debug, optimize, research, test, root-cause, retrospective, plan, PRD and it routes automatically. You never pick a mode."
3
3
  argument-hint: "[--parallel] [goal description]"
4
4
  ---
5
5
 
@@ -0,0 +1,40 @@
1
+ #!/bin/sh
2
+ # build-loop:artifact-guard-pre-commit
3
+ # SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
4
+ # SPDX-License-Identifier: Apache-2.0
5
+ #
6
+ # Drift-prevention pre-commit. Two scoped guards (systems-not-discipline):
7
+ # 1. artifact_guard --staged: when a checked-in generated artifact's watched
8
+ # source paths are staged, regenerate + re-stage it so drift cannot be
9
+ # committed (architecture diagram, Codex plugin artifact; registry-driven).
10
+ # 2. import_manifest_lint: block a commit that adds an undeclared third-party
11
+ # import (the pyyaml-outage class), only when a .py file is staged.
12
+ # The artifact guard is scoped to staged watched paths; the import lint
13
+ # runs only when a .py is staged. Set BL_ARTIFACT_ADVISORY=1 to warn instead
14
+ # of regenerate/block (covers BOTH guards). Unrelated commits run nothing.
15
+ #
16
+ # Installed (chained, coexisting with other pre-commit segments — e.g. the
17
+ # rally-point private-slug guard) by:
18
+ # python3 scripts/artifact_guard.py --install-hook
19
+ # Auto-installed on session start via hooks/session-start-git-hooks.sh.
20
+ # --- BEGIN build-loop artifact-guard pre-commit ---
21
+ BL_AG_TOP="$(git rev-parse --show-toplevel 2>/dev/null)"
22
+ if [ -n "$BL_AG_TOP" ] && [ -f "$BL_AG_TOP/scripts/artifact_guard.py" ]; then
23
+ python3 "$BL_AG_TOP/scripts/artifact_guard.py" --staged || exit 1
24
+ if [ -f "$BL_AG_TOP/scripts/import_manifest_lint.py" ] && \
25
+ git diff --cached --name-only | grep -q '\.py$'; then
26
+ if [ "${BL_ARTIFACT_ADVISORY:-0}" = "1" ]; then
27
+ python3 "$BL_AG_TOP/scripts/import_manifest_lint.py" || true
28
+ else
29
+ python3 "$BL_AG_TOP/scripts/import_manifest_lint.py" --quiet
30
+ bl_imp_rc=$?
31
+ # Block only on a real finding (exit 1). Exit 2 means the lint itself
32
+ # could not run (e.g. interpreter < 3.11, no tomllib) — fail open with
33
+ # the message already on stderr rather than block an unrelated commit;
34
+ # the non-bypassable pytest.yml step remains the hard backstop.
35
+ [ "$bl_imp_rc" -eq 1 ] && exit 1
36
+ fi
37
+ fi
38
+ fi
39
+ # --- END build-loop artifact-guard pre-commit ---
40
+ exit 0
@@ -20,19 +20,23 @@ Per ``man githooks``: pre-push receives ``<remote> <url>`` as argv, and four
20
20
  fields per ref on stdin: ``<local_ref> <local_sha> <remote_ref> <remote_sha>``.
21
21
  Exit non-zero → push is blocked.
22
22
 
23
- This hook:
23
+ This hook runs TWO stages; either blocking stops the push:
24
24
 
25
25
  1. Locates the repo (git rev-parse) and adds ``<repo>/scripts`` to sys.path.
26
- 2. Imports ``push_hold.evaluate_push``.
27
- 3. Calls it with the stdin lines.
28
- 4. On ``allow``/``bypass``: exit 0 (push proceeds).
29
- 5. On ``block``: print the reason + release instructions to stderr, exit 1.
30
- 6. On ANY internal exception (broken import, syntax error, OSError): log to
31
- stderr and exit 0. A broken hook MUST NOT permanently wedge the user's
32
- ability to push fail OPEN on internal errors, fail CLOSED on holds.
26
+ 2. STAGE 1 — deploy-HOLD: ``push_hold.evaluate_push``. On ``block`` print the
27
+ reason + release instructions to stderr and exit 1.
28
+ 3. STAGE 2 deterministic TEST gate: ``prepush_test_gate.evaluate`` (added per
29
+ the 2026-06-29 L2 RCA red commits kept reaching origin/main because the
30
+ deterministic suite ran only post-push in CI). On ``block`` print the failing
31
+ gate to stderr and exit 1. Stage 2 COMPOSES with never replaces — stage 1.
32
+ 4. On ``allow``/``bypass`` from both stages: arm closeout, exit 0 (push proceeds).
33
+ 5. On ANY internal exception (broken import, syntax error, OSError) in EITHER
34
+ stage: log to stderr and exit 0. A broken hook MUST NOT permanently wedge the
35
+ user's ability to push — fail OPEN on internal errors, fail CLOSED on a real
36
+ hold / a real deterministic test failure.
33
37
 
34
38
  The block-vs-fail-open distinction is the whole reason for the try/except
35
- wrapper below. Do NOT collapse it into a bare ``raise``.
39
+ wrappers below. Do NOT collapse them into a bare ``raise``.
36
40
  """
37
41
  from __future__ import annotations
38
42
 
@@ -185,12 +189,33 @@ def main() -> int:
185
189
  except OSError as exc:
186
190
  return _fail_open(f"could not read stdin: {exc!r}", repo=repo)
187
191
 
192
+ # --- STAGE 1: deploy-HOLD gate ---
188
193
  verdict = push_hold.evaluate_push(repo, stdin_lines, env=os.environ)
189
194
  action = verdict.get("action", "allow")
190
195
  if action == "block":
191
196
  sys.stderr.write(_format_block_message(verdict))
192
197
  return int(verdict.get("exit_code") or 1)
193
- # allow / bypass / anything else → exit 0.
198
+
199
+ # --- STAGE 2: deterministic TEST gate (composes with stage 1) ---
200
+ # Runs the fast deterministic gate subset (the integration-gate class CI
201
+ # also runs) and BLOCKS the push on a real failure. Import + evaluate are
202
+ # wrapped so a missing/broken gate fails OPEN — a broken gate must never
203
+ # permanently wedge pushing (same contract as stage 1). evaluate() itself
204
+ # never raises; this try/except backstops an import/attr error.
205
+ try:
206
+ import prepush_test_gate # type: ignore
207
+
208
+ test_verdict = prepush_test_gate.evaluate(repo, stdin_lines, env=os.environ)
209
+ if test_verdict.get("action") == "block":
210
+ sys.stderr.write(prepush_test_gate.format_block_message(test_verdict))
211
+ return int(test_verdict.get("exit_code") or 1)
212
+ except Exception as exc: # noqa: BLE001 — test-gate errors fail OPEN
213
+ sys.stderr.write(
214
+ "[build-loop pre-push] test gate internal error — "
215
+ f"allowing push: {exc!r}\n"
216
+ )
217
+
218
+ # Both stages passed (or the test gate failed open) → exit 0.
194
219
  # Arm the post-push closeout baton BEFORE the push completes — git has
195
220
  # no native client-side post-push hook, so the next session-start
196
221
  # drains the baton via ``python3 -m closeout --source post-push-armed``.
package/hooks/hooks.json CHANGED
@@ -53,11 +53,38 @@
53
53
  "type": "command",
54
54
  "command": "bash \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/hooks/closeout.sh\" session-start </dev/null 2>/dev/null; exit 0",
55
55
  "timeout": 3000
56
+ },
57
+ {
58
+ "type": "command",
59
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/hooks/session-start-extensions.sh\" </dev/null 2>/dev/null; exit 0",
60
+ "timeout": 2000
61
+ }
62
+ ]
63
+ }
64
+ ],
65
+ "UserPromptSubmit": [
66
+ {
67
+ "matcher": "",
68
+ "hooks": [
69
+ {
70
+ "type": "command",
71
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/hooks/route-guard.sh\" prompt-submit 2>/dev/null || printf '{}'",
72
+ "timeout": 3000
56
73
  }
57
74
  ]
58
75
  }
59
76
  ],
60
77
  "PreToolUse": [
78
+ {
79
+ "matcher": "Skill",
80
+ "hooks": [
81
+ {
82
+ "type": "command",
83
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/hooks/route-guard.sh\" pre-skill 2>/dev/null || printf '{}'",
84
+ "timeout": 3000
85
+ }
86
+ ]
87
+ },
61
88
  {
62
89
  "matcher": "Edit|Write",
63
90
  "hooks": [
@@ -120,6 +147,11 @@
120
147
  "command": "if [ -n \"$CLAUDE_TRANSCRIPT_PATH\" ]; then root=\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}\"; nohup env PYTHONPATH=\"$root/scripts\" python3 -m scan_corrections --workdir \"$CLAUDE_PROJECT_DIR\" --transcript \"$CLAUDE_TRANSCRIPT_PATH\" --source stop-hook </dev/null >/dev/null 2>&1 & fi; printf '{}'",
121
148
  "timeout": 5000
122
149
  },
150
+ {
151
+ "type": "command",
152
+ "command": "if [ -n \"$CLAUDE_TRANSCRIPT_PATH\" ]; then root=\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}\"; nohup env PYTHONPATH=\"$root/scripts\" python3 -m scan_findings --workdir \"$CLAUDE_PROJECT_DIR\" --transcript \"$CLAUDE_TRANSCRIPT_PATH\" --log-file \"${XDG_STATE_HOME:-$HOME/.local/state}/build-loop/findings-scan.log\" </dev/null >/dev/null 2>&1 & fi; printf '{}'",
153
+ "timeout": 5000
154
+ },
123
155
  {
124
156
  "type": "command",
125
157
  "command": "python3 \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/scripts/state_finalize.py\" --workdir \"$CLAUDE_PROJECT_DIR\" --mark-incomplete-as-crashed </dev/null >/dev/null 2>&1; printf '{}'",
@@ -130,17 +162,22 @@
130
162
  "command": "root=\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}\"; hook=\"$root/scripts/hooks/stop_finalize.sh\"; if [ -x \"$hook\" ]; then \"$hook\"; else printf '{}'; fi",
131
163
  "timeout": 30000
132
164
  },
133
- {
134
- "type": "command",
135
- "command": "if [ -f \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/scripts/commit_state_check.py\" ]; then python3 \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/scripts/commit_state_check.py\" --workdir \"${CLAUDE_PROJECT_DIR}\" --hook; else printf '{}'; fi",
136
- "timeout": 5000
137
- },
138
165
  {
139
166
  "type": "command",
140
167
  "command": "bash \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/hooks/closeout.sh\" stop 2>/dev/null; exit 0",
141
168
  "timeout": 6000
142
169
  }
143
170
  ]
171
+ },
172
+ {
173
+ "matcher": "",
174
+ "hooks": [
175
+ {
176
+ "type": "command",
177
+ "command": "if [ -f \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/scripts/commit_state_check.py\" ]; then python3 \"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/scripts/commit_state_check.py\" --workdir \"${CLAUDE_PROJECT_DIR}\" --hook; else printf '{}'; fi",
178
+ "timeout": 5000
179
+ }
180
+ ]
144
181
  }
145
182
  ]
146
183
  }
@@ -17,7 +17,7 @@
17
17
  # mark `.build-loop/architecture/.enrich-needed` and EXIT — DEFER the
18
18
  # actual enriched scan to the scout pass (OQ3); never run it inline.
19
19
  # 4. Else bail silently if file extension is not in the source-code
20
- # allowlist (.py .ts .tsx .js .jsx .mjs .cjs). Doc-only edits (.md,
20
+ # allowlist (.py .ts .tsx .js .jsx .mjs .cjs .rs). Doc-only edits (.md,
21
21
  # .txt, plain .json, images) never mark architecture stale or fire a scan.
22
22
  # 5. Resolve to repo-relative path; bail if not in file_map.
23
23
  # 5. Mark stale (always — even if a scan is in flight, orchestrator must see
@@ -50,7 +50,7 @@ STDIN_JSON=$(cat 2>/dev/null)
50
50
  REL_PATH=$(WORKDIR="$WORKDIR" FILE_MAP="$FILE_MAP" STDIN_JSON="$STDIN_JSON" python3 - <<'PYEOF' 2>/dev/null
51
51
  import json, os, sys
52
52
  from pathlib import Path
53
- ALLOWED_EXTS = {".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"}
53
+ ALLOWED_EXTS = {".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".rs"}
54
54
  MANIFESTS = {
55
55
  "package.json", "package-lock.json", "pnpm-lock.yaml", "requirements.txt",
56
56
  "pyproject.toml", "uv.lock", "Cargo.toml", "Cargo.lock", "go.mod", "Gemfile",
@@ -0,0 +1,15 @@
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
+ #
5
+ # route-guard.sh — minimal-PATH wrapper for route_guard.py. Hooks run under a
6
+ # stripped PATH, so resolve python via the shared resolver before dispatch.
7
+ # Fail-open: if no python is available, emit an empty decision and exit 0 so a
8
+ # routing hint can never break a session.
9
+ #
10
+ # usage: route-guard.sh <prompt-submit|pre-skill> (payload on stdin)
11
+
12
+ root="${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}"
13
+ . "$root/hooks/_resolve_python.sh"
14
+ [ -n "$_py" ] || { printf '{}'; exit 0; }
15
+ exec "$_py" "$root/hooks/route_guard.py" "$@"