@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 +7 -0
- package/dist/cli.js +55 -1
- package/dist/contributor-cta.d.ts +2 -0
- package/dist/contributor-cta.js +6 -0
- package/package.json +1 -1
- package/templates/SKILL.md.loop-guard +94 -0
- package/templates/loop-constraints.md +1 -0
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
|
-
|
|
537
|
+
printContributorCta();
|
|
484
538
|
}
|
|
485
539
|
async function readDirNames(dir) {
|
|
486
540
|
const { readdir } = await import('node:fs/promises');
|
|
@@ -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.
|
|
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
|