@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 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
- console.log('');
557
+ printContributorCta();
484
558
  }
485
559
  async function readDirNames(dir) {
486
560
  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.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