@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
|
@@ -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",
|
package/agents/scope-auditor.md
CHANGED
|
@@ -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 (private-app stress test on 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
|
---
|
package/agents/ui-validator.md
CHANGED
|
@@ -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
|
---
|
|
Binary file
|
|
@@ -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
|
|
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
|
package/hooks/git/pre-push
CHANGED
|
@@ -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.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
stderr and exit
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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" "$@"
|