@cobusgreyling/loop-init 1.3.2 → 1.3.3

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/README.md CHANGED
@@ -31,6 +31,13 @@ After scaffolding, always run `npx @cobusgreyling/loop-audit . --suggest` and ac
31
31
 
32
32
  L2 patterns (`ci-sweeper`, `dependency-sweeper`) also copy `minimal-fix` and `loop-verifier` templates when missing from the starter.
33
33
 
34
+ Fix-capable patterns (`pr-babysitter`, `ci-sweeper`, `dependency-sweeper`, `post-merge-cleanup`) also get a **circuit breaker**:
35
+
36
+ - `loop-guard` skill — logs each attempt to `loop-ledger.json` and runs [`loop-context`](../loop-context) `--check` before retrying
37
+ - `loop-ledger.json` — seeded with the pattern's goal, its `pattern`/`level`, and an empty `attempts` array
38
+
39
+ The ledger's `pattern`/`level` let `loop-guard` size the breaker's `--token-budget` from [`loop-cost`](../loop-cost)'s realistic per-run estimate instead of a hand-typed number. The breaker escalates (same error N× in a row, too many consecutive failures, token budget, or iteration cap) instead of looping in vain. Report-only patterns skip it.
40
+
34
41
  Every scaffold also creates:
35
42
 
36
43
  - `loop-budget.md` — pattern-specific daily caps and kill switch
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { spawn } from 'node:child_process';
3
3
  import { cp, mkdir, readFile, writeFile, access } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { printContributorCta } from './contributor-cta.js';
6
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
8
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
8
9
  const MONOREPO_STARTERS = path.resolve(PACKAGE_ROOT, '../../starters');
@@ -142,6 +143,53 @@ async function copyL2Templates(pattern, tool, targetDir, templatesRoot, dryRun)
142
143
  await copyTemplateVerifier(templatesRoot, targetDir, tool, dryRun);
143
144
  }
144
145
  }
146
+ /** Per-pattern goal seeded into loop-ledger.json for the circuit breaker. */
147
+ const LEDGER_GOAL = {
148
+ 'daily-triage': 'Keep the repo healthy and STATE.md current',
149
+ 'pr-babysitter': 'Get the watched PR review-ready and green',
150
+ 'ci-sweeper': 'Get failing CI back to green',
151
+ 'dependency-sweeper': 'Land safe dependency updates',
152
+ 'post-merge-cleanup': 'Clean up regressions from recent merges',
153
+ 'changelog-drafter': 'Draft accurate release notes',
154
+ 'issue-triage': 'Triage the open issue queue',
155
+ };
156
+ /**
157
+ * Readiness level seeded into loop-ledger.json so the loop-guard skill can
158
+ * resolve a realistic per-run token budget from `loop-cost --json` instead of a
159
+ * hand-typed number. Fix-capable loops draft changes with a verifier (a human
160
+ * still merges), so L2 is the right default; tune it in the ledger if a loop
161
+ * runs unattended (L3) or report-only (L1).
162
+ */
163
+ const LEDGER_LEVEL = {
164
+ 'daily-triage': 'L1',
165
+ 'pr-babysitter': 'L2',
166
+ 'ci-sweeper': 'L2',
167
+ 'dependency-sweeper': 'L2',
168
+ 'post-merge-cleanup': 'L2',
169
+ 'changelog-drafter': 'L1',
170
+ 'issue-triage': 'L1',
171
+ };
172
+ /**
173
+ * Fix-capable loops retry actions, so they need a circuit breaker: scaffold the
174
+ * loop-guard skill plus a seeded loop-ledger.json wired to `loop-context`.
175
+ * Report-only patterns (daily-triage, issue-triage, changelog-drafter) don't
176
+ * retry fixes, so they skip this to keep the scaffold minimal.
177
+ */
178
+ async function scaffoldCircuitBreaker(pattern, tool, targetDir, templatesRoot, dryRun) {
179
+ if (!PATTERNS_NEEDING_FIX.has(pattern))
180
+ return;
181
+ await copyTemplateSkill(templatesRoot, 'SKILL.md.loop-guard', targetDir, tool, 'loop-guard', dryRun);
182
+ const ledgerPath = path.join(targetDir, 'loop-ledger.json');
183
+ if (await exists(ledgerPath))
184
+ return;
185
+ const seed = `${JSON.stringify({ goal: LEDGER_GOAL[pattern], pattern, level: LEDGER_LEVEL[pattern], attempts: [] }, null, 2)}\n`;
186
+ if (dryRun) {
187
+ console.log(` would write: ${ledgerPath}`);
188
+ return;
189
+ }
190
+ await writeFile(ledgerPath, seed);
191
+ console.log(' created: loop-ledger.json (circuit breaker)');
192
+ }
145
193
  function formatTokenCap(n) {
146
194
  if (n >= 1_000_000)
147
195
  return `${n / 1_000_000}M`;
@@ -442,6 +490,7 @@ Examples:
442
490
  await copyFile(loopMd, path.join(targetDir, 'LOOP.md'), dryRun);
443
491
  }
444
492
  await copyL2Templates(pattern, tool, targetDir, templatesRoot, dryRun);
493
+ await scaffoldCircuitBreaker(pattern, tool, targetDir, templatesRoot, dryRun);
445
494
  await scaffoldObservability(pattern, tool, targetDir, templatesRoot, dryRun);
446
495
  await scaffoldConstraints(targetDir, templatesRoot, tool, dryRun);
447
496
  if (tool !== 'opencode' && !dryRun && !(await exists(path.join(targetDir, 'AGENTS.md')))) {
@@ -475,12 +524,17 @@ npm run lint
475
524
  console.log(` npx @cobusgreyling/loop-audit ${auditArg} --suggest`);
476
525
  }
477
526
  }
527
+ if (PATTERNS_NEEDING_FIX.has(pattern)) {
528
+ console.log('');
529
+ console.log('Circuit breaker wired (loop-guard skill + loop-ledger.json):');
530
+ console.log(' npx @cobusgreyling/loop-context --check --ledger loop-ledger.json');
531
+ }
478
532
  console.log('');
479
533
  console.log(`First loop (${tool}):`);
480
534
  console.log(` ${firstLoopCommand(pattern, tool)}`);
481
535
  console.log('');
482
536
  console.log(`Estimate cost: npx @cobusgreyling/loop-cost --pattern ${pattern} --level L1`);
483
- console.log('');
537
+ printContributorCta();
484
538
  }
485
539
  async function readDirNames(dir) {
486
540
  const { readdir } = await import('node:fs/promises');
@@ -0,0 +1,2 @@
1
+ export declare const CONTRIBUTOR_QUICKSTART_URL = "https://github.com/cobusgreyling/loop-engineering/discussions/123";
2
+ export declare function printContributorCta(): void;
@@ -0,0 +1,6 @@
1
+ export const CONTRIBUTOR_QUICKSTART_URL = 'https://github.com/cobusgreyling/loop-engineering/discussions/123';
2
+ export function printContributorCta() {
3
+ console.log('');
4
+ console.log('Contribute (~15 min tasks):');
5
+ console.log(` ${CONTRIBUTOR_QUICKSTART_URL}`);
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cobusgreyling/loop-init",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Scaffold loop engineering patterns and starters into any project. Supports Grok, Claude Code, Codex, Opencode and more. npx @cobusgreyling/loop-init . --pattern daily-triage --tool opencode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: loop-guard
3
+ description: >
4
+ Circuit breaker for fix-capable loops. Before each iteration, append the last
5
+ attempt to loop-ledger.json and run loop-context --check; if it escalates,
6
+ stop and hand the human a clean summary instead of looping in vain.
7
+ user_invocable: true
8
+ ---
9
+
10
+ # Loop Guard (Circuit Breaker)
11
+
12
+ You keep a fix loop from burning tokens on a problem it cannot solve. You wrap
13
+ every iteration of an action skill (`minimal-fix`, `ci-triage`, `dependency-triage`, …)
14
+ with a deterministic circuit-breaker check powered by
15
+ [`loop-context`](https://github.com/cobusgreyling/loop-engineering/tree/main/tools/loop-context).
16
+
17
+ The breaker needs no LLM call, so it is cheap enough to run on every iteration.
18
+
19
+ ## The ledger
20
+
21
+ `loop-ledger.json` records the loop's goal, its pattern/level, and one entry per
22
+ attempt:
23
+
24
+ ```json
25
+ { "goal": "Get failing CI green", "pattern": "ci-sweeper", "level": "L2", "attempts": [] }
26
+ ```
27
+
28
+ `pattern` and `level` are seeded by `loop-init`; the breaker ignores them but the
29
+ budget step below reads them to size `--token-budget` from real cost data.
30
+
31
+ After every iteration, append what you just tried:
32
+
33
+ ```json
34
+ { "iteration": 3, "action": "patch flaky auth test", "outcome": "failure",
35
+ "error": "AssertionError: expected 200 got 500", "tokensUsed": 1800 }
36
+ ```
37
+
38
+ `outcome` is `success | failure | noop`. Always include `error` on failures —
39
+ that is how the breaker detects a repeated (stagnant) failure.
40
+
41
+ ## Size the token budget from the pattern (once per run)
42
+
43
+ Don't hand-type a token cap — derive it from the pattern's realistic per-run
44
+ cost so the breaker trips on genuine cost blowup, not a made-up number.
45
+ [`loop-cost`](https://github.com/cobusgreyling/loop-engineering/tree/main/tools/loop-cost)
46
+ already computes this from `patterns/registry.yaml`; read the loop's `pattern`
47
+ and `level` straight from the ledger:
48
+
49
+ ```bash
50
+ # Substitute the ledger's own pattern/level (here: ci-sweeper / L2).
51
+ BUDGET=$(npx @cobusgreyling/loop-cost --pattern ci-sweeper --level L2 --json \
52
+ | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(String(JSON.parse(d).scenarios.realistic.tokensPerRun)))')
53
+ ```
54
+
55
+ `scenarios.realistic.tokensPerRun` is a rounded integer, so it feeds
56
+ `--token-budget` directly. The two tools stay independent — this is shell
57
+ wiring, not a code dependency.
58
+
59
+ ## Before each iteration
60
+
61
+ 1. Append the previous attempt to `loop-ledger.json`.
62
+ 2. Run the breaker with the resolved budget:
63
+ ```bash
64
+ npx @cobusgreyling/loop-context --check --ledger loop-ledger.json --token-budget "$BUDGET"
65
+ ```
66
+ 3. Act on the exit code:
67
+ - **0** → continue. Optionally trim the next prompt first:
68
+ `npx @cobusgreyling/loop-context --inject --ledger loop-ledger.json`
69
+ - **2** → **STOP.** The breaker tripped — same error N× in a row, too many
70
+ consecutive failures, the token budget, or the iteration cap. Do not retry.
71
+
72
+ ## On escalate (exit 2)
73
+
74
+ 1. Capture a clean, pruned summary for the human:
75
+ ```bash
76
+ npx @cobusgreyling/loop-context --inject --ledger loop-ledger.json > escalation.md
77
+ ```
78
+ 2. Write the escalation into STATE.md High Priority (or open an issue).
79
+ 3. Exit the loop. A human decides the next step.
80
+
81
+ ## Rules
82
+
83
+ - Never widen thresholds just to keep looping — escalation is a feature, not a failure.
84
+ - Never edit the ledger to hide a repeated error; the breaker exists to catch it.
85
+ - Defaults: 3× same error, 5 consecutive failures, 10 iterations. Tune with
86
+ `--stagnation`, `--no-progress`, `--max-iterations`, `--token-budget`.
87
+
88
+ ## Interaction with other skills
89
+
90
+ - `minimal-fix` / `ci-triage` — record each attempt's outcome + error in the ledger.
91
+ - `loop-verifier` — a verifier rejection is a `failure`; log it so repeats trip the breaker.
92
+ - `loop-constraints` — honors "escalate after N attempts"; this skill makes it mechanical.
93
+ - `loop-budget` — the per-run `--token-budget` here comes from loop-cost's
94
+ realistic estimate; loop-budget.md still governs the *daily* cap across runs.
@@ -18,6 +18,7 @@
18
18
  - Never disable tests to make CI green
19
19
  - Never refactor unrelated code — one fix per run
20
20
  - Max 3 fix attempts per item; escalate after
21
+ - Enforce the attempt limit mechanically: log each try to `loop-ledger.json` and run `loop-context --check` before retrying (see the `loop-guard` skill)
21
22
 
22
23
  ## Communication
23
24
  - Always tell me what you're about to do before doing it