@cobusgreyling/loop-init 1.3.2 → 1.4.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.
- package/README.md +13 -0
- package/dist/cli.js +75 -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 +92 -0
- package/templates/SKILL.md.loop-intake +71 -0
- package/templates/loop-constraints.md +1 -0
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Scaffold loop engineering starters into your project by pattern and tool.
|
|
|
4
4
|
|
|
5
5
|
**npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok** works immediately.
|
|
6
6
|
|
|
7
|
+
See [docs/loop-init-validation.md](../../docs/loop-init-validation.md) for a validated pattern × --tool matrix.
|
|
8
|
+
|
|
7
9
|
## Install & Run
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -31,6 +33,17 @@ After scaffolding, always run `npx @cobusgreyling/loop-audit . --suggest` and ac
|
|
|
31
33
|
|
|
32
34
|
L2 patterns (`ci-sweeper`, `dependency-sweeper`) also copy `minimal-fix` and `loop-verifier` templates when missing from the starter.
|
|
33
35
|
|
|
36
|
+
Fix-capable patterns (`pr-babysitter`, `ci-sweeper`, `dependency-sweeper`, `post-merge-cleanup`) also get a **circuit breaker**:
|
|
37
|
+
|
|
38
|
+
- `loop-guard` skill — logs each attempt to `loop-ledger.json` and runs [`loop-context`](../loop-context) `--check` before retrying
|
|
39
|
+
- `loop-ledger.json` — seeded with the pattern's goal, its `pattern`/`level`, and an empty `attempts` array
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
Patterns that act on human-authored, often underspecified input (`issue-triage`) also get an **intake** skill:
|
|
44
|
+
|
|
45
|
+
- `loop-intake` skill — when a work item is too vague to verify "done", it asks one question at a time, pushes for exact values, and writes an open question + `needs-human` escalation instead of guessing. Clarifying up front keeps the loop from burning fix attempts on a goal that was never well defined.
|
|
46
|
+
|
|
34
47
|
Every scaffold also creates:
|
|
35
48
|
|
|
36
49
|
- `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');
|
|
@@ -29,6 +30,12 @@ const PATTERNS_NEEDING_FIX = new Set([
|
|
|
29
30
|
'dependency-sweeper',
|
|
30
31
|
'post-merge-cleanup',
|
|
31
32
|
]);
|
|
33
|
+
/**
|
|
34
|
+
* Patterns that act on human-authored, often underspecified input (issues).
|
|
35
|
+
* They get the loop-intake skill so the loop clarifies a vague item or escalates
|
|
36
|
+
* it instead of guessing and burning fix attempts.
|
|
37
|
+
*/
|
|
38
|
+
const PATTERNS_NEEDING_INTAKE = new Set(['issue-triage']);
|
|
32
39
|
const STATE_FILES = {
|
|
33
40
|
'daily-triage': 'STATE.md',
|
|
34
41
|
'pr-babysitter': 'pr-babysitter-state.md',
|
|
@@ -142,6 +149,62 @@ async function copyL2Templates(pattern, tool, targetDir, templatesRoot, dryRun)
|
|
|
142
149
|
await copyTemplateVerifier(templatesRoot, targetDir, tool, dryRun);
|
|
143
150
|
}
|
|
144
151
|
}
|
|
152
|
+
/** Per-pattern goal seeded into loop-ledger.json for the circuit breaker. */
|
|
153
|
+
const LEDGER_GOAL = {
|
|
154
|
+
'daily-triage': 'Keep the repo healthy and STATE.md current',
|
|
155
|
+
'pr-babysitter': 'Get the watched PR review-ready and green',
|
|
156
|
+
'ci-sweeper': 'Get failing CI back to green',
|
|
157
|
+
'dependency-sweeper': 'Land safe dependency updates',
|
|
158
|
+
'post-merge-cleanup': 'Clean up regressions from recent merges',
|
|
159
|
+
'changelog-drafter': 'Draft accurate release notes',
|
|
160
|
+
'issue-triage': 'Triage the open issue queue',
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Readiness level seeded into loop-ledger.json so the loop-guard skill can
|
|
164
|
+
* resolve a realistic per-run token budget from `loop-cost --json` instead of a
|
|
165
|
+
* hand-typed number. Fix-capable loops draft changes with a verifier (a human
|
|
166
|
+
* still merges), so L2 is the right default; tune it in the ledger if a loop
|
|
167
|
+
* runs unattended (L3) or report-only (L1).
|
|
168
|
+
*/
|
|
169
|
+
const LEDGER_LEVEL = {
|
|
170
|
+
'daily-triage': 'L1',
|
|
171
|
+
'pr-babysitter': 'L2',
|
|
172
|
+
'ci-sweeper': 'L2',
|
|
173
|
+
'dependency-sweeper': 'L2',
|
|
174
|
+
'post-merge-cleanup': 'L2',
|
|
175
|
+
'changelog-drafter': 'L1',
|
|
176
|
+
'issue-triage': 'L1',
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Fix-capable loops retry actions, so they need a circuit breaker: scaffold the
|
|
180
|
+
* loop-guard skill plus a seeded loop-ledger.json wired to `loop-context`.
|
|
181
|
+
* Report-only patterns (daily-triage, issue-triage, changelog-drafter) don't
|
|
182
|
+
* retry fixes, so they skip this to keep the scaffold minimal.
|
|
183
|
+
*/
|
|
184
|
+
async function scaffoldCircuitBreaker(pattern, tool, targetDir, templatesRoot, dryRun) {
|
|
185
|
+
if (!PATTERNS_NEEDING_FIX.has(pattern))
|
|
186
|
+
return;
|
|
187
|
+
await copyTemplateSkill(templatesRoot, 'SKILL.md.loop-guard', targetDir, tool, 'loop-guard', dryRun);
|
|
188
|
+
const ledgerPath = path.join(targetDir, 'loop-ledger.json');
|
|
189
|
+
if (await exists(ledgerPath))
|
|
190
|
+
return;
|
|
191
|
+
const seed = `${JSON.stringify({ goal: LEDGER_GOAL[pattern], pattern, level: LEDGER_LEVEL[pattern], attempts: [] }, null, 2)}\n`;
|
|
192
|
+
if (dryRun) {
|
|
193
|
+
console.log(` would write: ${ledgerPath}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
await writeFile(ledgerPath, seed);
|
|
197
|
+
console.log(' created: loop-ledger.json (circuit breaker)');
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Scaffold the loop-intake skill for patterns that receive ambiguous human
|
|
201
|
+
* input, so the loop sharpens the goal (or escalates) before acting on it.
|
|
202
|
+
*/
|
|
203
|
+
async function scaffoldIntake(pattern, tool, targetDir, templatesRoot, dryRun) {
|
|
204
|
+
if (!PATTERNS_NEEDING_INTAKE.has(pattern))
|
|
205
|
+
return;
|
|
206
|
+
await copyTemplateSkill(templatesRoot, 'SKILL.md.loop-intake', targetDir, tool, 'loop-intake', dryRun);
|
|
207
|
+
}
|
|
145
208
|
function formatTokenCap(n) {
|
|
146
209
|
if (n >= 1_000_000)
|
|
147
210
|
return `${n / 1_000_000}M`;
|
|
@@ -442,7 +505,9 @@ Examples:
|
|
|
442
505
|
await copyFile(loopMd, path.join(targetDir, 'LOOP.md'), dryRun);
|
|
443
506
|
}
|
|
444
507
|
await copyL2Templates(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
508
|
+
await scaffoldCircuitBreaker(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
445
509
|
await scaffoldObservability(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
510
|
+
await scaffoldIntake(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
446
511
|
await scaffoldConstraints(targetDir, templatesRoot, tool, dryRun);
|
|
447
512
|
if (tool !== 'opencode' && !dryRun && !(await exists(path.join(targetDir, 'AGENTS.md')))) {
|
|
448
513
|
const agentsTemplate = `# AGENTS.md
|
|
@@ -475,12 +540,21 @@ npm run lint
|
|
|
475
540
|
console.log(` npx @cobusgreyling/loop-audit ${auditArg} --suggest`);
|
|
476
541
|
}
|
|
477
542
|
}
|
|
543
|
+
if (PATTERNS_NEEDING_FIX.has(pattern)) {
|
|
544
|
+
console.log('');
|
|
545
|
+
console.log('Circuit breaker wired (loop-guard skill + loop-ledger.json):');
|
|
546
|
+
console.log(' npx @cobusgreyling/loop-context --check --ledger loop-ledger.json');
|
|
547
|
+
}
|
|
548
|
+
if (PATTERNS_NEEDING_INTAKE.has(pattern)) {
|
|
549
|
+
console.log('');
|
|
550
|
+
console.log('Intake wired (loop-intake skill): clarify a vague item or escalate before acting.');
|
|
551
|
+
}
|
|
478
552
|
console.log('');
|
|
479
553
|
console.log(`First loop (${tool}):`);
|
|
480
554
|
console.log(` ${firstLoopCommand(pattern, tool)}`);
|
|
481
555
|
console.log('');
|
|
482
556
|
console.log(`Estimate cost: npx @cobusgreyling/loop-cost --pattern ${pattern} --level L1`);
|
|
483
|
-
|
|
557
|
+
printContributorCta();
|
|
484
558
|
}
|
|
485
559
|
async function readDirNames(dir) {
|
|
486
560
|
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
|
+
"version": "1.4.0",
|
|
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,92 @@
|
|
|
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
|
|
42
|
+
|
|
43
|
+
Don't hand-type a token cap — `loop-context` can derive it directly from the
|
|
44
|
+
pattern's realistic per-run cost
|
|
45
|
+
([`loop-cost`](https://github.com/cobusgreyling/loop-engineering/tree/main/tools/loop-cost)
|
|
46
|
+
computes this from `patterns/registry.yaml`) so the breaker trips on genuine
|
|
47
|
+
cost blowup, not a made-up number. Substitute the ledger's own `pattern`/`level`
|
|
48
|
+
(here: `ci-sweeper` / `L2`):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx @cobusgreyling/loop-context --check --ledger loop-ledger.json \
|
|
52
|
+
--budget-from-pattern ci-sweeper --budget-level L2
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Before each iteration
|
|
56
|
+
|
|
57
|
+
1. Append the previous attempt to `loop-ledger.json`.
|
|
58
|
+
2. Run the breaker with the resolved budget:
|
|
59
|
+
```bash
|
|
60
|
+
npx @cobusgreyling/loop-context --check --ledger loop-ledger.json \
|
|
61
|
+
--budget-from-pattern ci-sweeper --budget-level L2
|
|
62
|
+
```
|
|
63
|
+
3. Act on the exit code:
|
|
64
|
+
- **0** → continue. Optionally trim the next prompt first:
|
|
65
|
+
`npx @cobusgreyling/loop-context --inject --ledger loop-ledger.json`
|
|
66
|
+
- **2** → **STOP.** The breaker tripped — same error N× in a row, too many
|
|
67
|
+
consecutive failures, the token budget, or the iteration cap. Do not retry.
|
|
68
|
+
|
|
69
|
+
## On escalate (exit 2)
|
|
70
|
+
|
|
71
|
+
1. Capture a clean, pruned summary for the human:
|
|
72
|
+
```bash
|
|
73
|
+
npx @cobusgreyling/loop-context --inject --ledger loop-ledger.json > escalation.md
|
|
74
|
+
```
|
|
75
|
+
2. Write the escalation into STATE.md High Priority (or open an issue).
|
|
76
|
+
3. Exit the loop. A human decides the next step.
|
|
77
|
+
|
|
78
|
+
## Rules
|
|
79
|
+
|
|
80
|
+
- Never widen thresholds just to keep looping — escalation is a feature, not a failure.
|
|
81
|
+
- Never edit the ledger to hide a repeated error; the breaker exists to catch it.
|
|
82
|
+
- Defaults: 3× same error, 5 consecutive failures, 10 iterations. Tune with
|
|
83
|
+
`--stagnation`, `--no-progress`, `--max-iterations`, or an explicit
|
|
84
|
+
`--token-budget` (wins over `--budget-from-pattern` if both are given).
|
|
85
|
+
|
|
86
|
+
## Interaction with other skills
|
|
87
|
+
|
|
88
|
+
- `minimal-fix` / `ci-triage` — record each attempt's outcome + error in the ledger.
|
|
89
|
+
- `loop-verifier` — a verifier rejection is a `failure`; log it so repeats trip the breaker.
|
|
90
|
+
- `loop-constraints` — honors "escalate after N attempts"; this skill makes it mechanical.
|
|
91
|
+
- `loop-budget` — the per-run budget here comes from `--budget-from-pattern`;
|
|
92
|
+
loop-budget.md still governs the *daily* cap across runs.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop-intake
|
|
3
|
+
description: >
|
|
4
|
+
Clarify an ambiguous goal or work item BEFORE the loop acts on it. Runs at the
|
|
5
|
+
front of a run when the assigned task is underspecified. Ask one question at a
|
|
6
|
+
time, push for exact values, and escalate instead of guessing.
|
|
7
|
+
user_invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Loop Intake - Clarify Before You Act
|
|
11
|
+
|
|
12
|
+
Autonomous loops waste attempts (and budget) when they guess at a vague goal.
|
|
13
|
+
This skill runs FIRST, before triage or any action skill, whenever the item the
|
|
14
|
+
loop is about to work on is not specific enough to verify "done".
|
|
15
|
+
|
|
16
|
+
## When to run
|
|
17
|
+
|
|
18
|
+
Run intake when the goal or work item is missing any of:
|
|
19
|
+
|
|
20
|
+
- a single, testable definition of done
|
|
21
|
+
- the exact scope (which repo / branch / paths / tickets)
|
|
22
|
+
- concrete values a fix would depend on (error text, thresholds, versions)
|
|
23
|
+
|
|
24
|
+
If the goal is already specific and verifiable, skip intake and proceed.
|
|
25
|
+
|
|
26
|
+
## How to clarify (one question at a time)
|
|
27
|
+
|
|
28
|
+
1. Read the current state file and the item (issue, PR, request) in full. Do NOT
|
|
29
|
+
re-ask anything already answered there.
|
|
30
|
+
2. Ask the SINGLE most decision-blocking question. Wait for the answer.
|
|
31
|
+
3. Push for an exact value. "It should be fast" becomes "What p95 latency, in
|
|
32
|
+
ms?". "Add a limit" becomes "How many per minute?". Re-ask once if the answer
|
|
33
|
+
is still vague.
|
|
34
|
+
4. Stop as soon as the goal is verifiable. Do not interrogate for its own sake.
|
|
35
|
+
|
|
36
|
+
Ask in behavior terms, not implementation: what the system must do, what the user
|
|
37
|
+
sees, which values matter. Not: which function, table, or endpoint.
|
|
38
|
+
|
|
39
|
+
## When an answer stays vague
|
|
40
|
+
|
|
41
|
+
Do not guess. Record it and move on:
|
|
42
|
+
|
|
43
|
+
- Write the gap as an open question in the state file: `- [ ] OQ: <question>`.
|
|
44
|
+
- Mark the item `needs-human` (escalate per the loop's handoff rule).
|
|
45
|
+
- If NO blocking question can be resolved, escalate the whole item and stop.
|
|
46
|
+
Acting on a guess burns attempts the circuit breaker should not have to catch.
|
|
47
|
+
|
|
48
|
+
## Output
|
|
49
|
+
|
|
50
|
+
Append a short, sharpened goal to the state file (or the item):
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Goal (clarified): <one testable sentence>
|
|
54
|
+
Done when: <verifiable condition>
|
|
55
|
+
Open questions: <N> (needs-human) - <list>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Hand back to triage or the action skill only if "Done when" is now verifiable.
|
|
59
|
+
|
|
60
|
+
## Interaction with other skills
|
|
61
|
+
|
|
62
|
+
- Runs BEFORE `loop-triage` / `issue-triage` and any action skill.
|
|
63
|
+
- Feeds `loop-verifier`: the "Done when" line becomes the verifier's check.
|
|
64
|
+
- Pairs with `loop-guard` (circuit breaker): intake prevents wasted attempts on a
|
|
65
|
+
goal that was never well defined; the breaker catches the ones that slip through.
|
|
66
|
+
- Respects `loop-constraints`: never ask for, or act on, denylisted scope.
|
|
67
|
+
|
|
68
|
+
## Default behavior
|
|
69
|
+
|
|
70
|
+
Report-only in week one: propose the clarified goal and open questions, but let a
|
|
71
|
+
human confirm before the loop acts on the sharpened goal.
|
|
@@ -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
|