@cobusgreyling/loop-init 1.2.2 → 1.3.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/README.md +2 -0
- package/dist/cli.js +141 -30
- package/package.json +6 -2
- package/starters/README.md +2 -1
- package/starters/changelog-drafter/changelog-drafter-state.md.example +7 -0
- package/starters/minimal-loop/STATE.md.example +7 -0
- package/starters/minimal-loop-opencode/AGENTS.md +23 -0
- package/starters/minimal-loop-opencode/LOOP.md +33 -0
- package/starters/minimal-loop-opencode/README.md +50 -0
- package/starters/minimal-loop-opencode/STATE.md.example +12 -0
- package/starters/minimal-loop-opencode/opencode.json.example +35 -0
- package/starters/minimal-loop-opencode/skills/loop-triage/SKILL.md +59 -0
- package/templates/SKILL.md.loop-constraints +48 -0
- package/templates/loop-constraints.md +31 -0
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Scaffold loop engineering starters into your project by pattern and tool.
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok
|
|
11
|
+
npx @cobusgreyling/loop-init . --pattern daily-triage --tool opencode
|
|
11
12
|
npx @cobusgreyling/loop-init . -p pr-babysitter -t claude
|
|
12
13
|
npx @cobusgreyling/loop-init . -p dependency-sweeper --dry-run
|
|
13
14
|
```
|
|
@@ -41,6 +42,7 @@ Every scaffold also creates:
|
|
|
41
42
|
- `grok` (default)
|
|
42
43
|
- `claude`
|
|
43
44
|
- `codex`
|
|
45
|
+
- `opencode` — daily-triage ships `minimal-loop-opencode` (`skills/`, `AGENTS.md`, `opencode.json`)
|
|
44
46
|
|
|
45
47
|
Falls back to Grok starter paths when a per-tool variant is not yet available.
|
|
46
48
|
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
2
3
|
import { cp, mkdir, readFile, writeFile, access } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -19,6 +20,7 @@ const TOOL_SUFFIX = {
|
|
|
19
20
|
grok: '',
|
|
20
21
|
claude: '-claude',
|
|
21
22
|
codex: '-codex',
|
|
23
|
+
opencode: '-opencode',
|
|
22
24
|
};
|
|
23
25
|
const L2_PATTERNS = new Set(['ci-sweeper', 'dependency-sweeper']);
|
|
24
26
|
const PATTERNS_NEEDING_FIX = new Set([
|
|
@@ -99,6 +101,7 @@ async function copyTemplateSkill(templatesRoot, templateFile, targetDir, tool, s
|
|
|
99
101
|
grok: path.join(targetDir, '.grok', 'skills', skillName, 'SKILL.md'),
|
|
100
102
|
claude: path.join(targetDir, '.claude', 'skills', skillName, 'SKILL.md'),
|
|
101
103
|
codex: path.join(targetDir, '.codex', 'skills', skillName, 'SKILL.md'),
|
|
104
|
+
opencode: path.join(targetDir, 'skills', skillName, 'SKILL.md'),
|
|
102
105
|
};
|
|
103
106
|
const dest = destByTool[tool];
|
|
104
107
|
if (await exists(dest))
|
|
@@ -110,6 +113,7 @@ async function copyTemplateVerifier(templatesRoot, targetDir, tool, dryRun) {
|
|
|
110
113
|
grok: path.join(targetDir, '.grok', 'skills', 'loop-verifier', 'SKILL.md'),
|
|
111
114
|
claude: path.join(targetDir, '.claude', 'agents', 'loop-verifier.md'),
|
|
112
115
|
codex: path.join(targetDir, '.codex', 'agents', 'verifier.toml'),
|
|
116
|
+
opencode: path.join(targetDir, 'skills', 'loop-verifier', 'SKILL.md'),
|
|
113
117
|
};
|
|
114
118
|
const dest = verifierPaths[tool];
|
|
115
119
|
if (await exists(dest))
|
|
@@ -194,6 +198,14 @@ async function scaffoldObservability(pattern, tool, targetDir, templatesRoot, dr
|
|
|
194
198
|
}
|
|
195
199
|
await copyTemplateSkill(templatesRoot, 'SKILL.md.loop-budget', targetDir, tool, 'loop-budget', dryRun);
|
|
196
200
|
}
|
|
201
|
+
async function scaffoldConstraints(targetDir, templatesRoot, tool, dryRun) {
|
|
202
|
+
const constraintsPath = path.join(targetDir, 'loop-constraints.md');
|
|
203
|
+
const constraintsTemplate = path.join(templatesRoot, 'loop-constraints.md');
|
|
204
|
+
if (!(await exists(constraintsPath)) && (await exists(constraintsTemplate))) {
|
|
205
|
+
await copyFile(constraintsTemplate, constraintsPath, dryRun);
|
|
206
|
+
}
|
|
207
|
+
await copyTemplateSkill(templatesRoot, 'SKILL.md.loop-constraints', targetDir, tool, 'loop-constraints', dryRun);
|
|
208
|
+
}
|
|
197
209
|
async function copyFile(src, dest, dryRun) {
|
|
198
210
|
if (!(await exists(src)))
|
|
199
211
|
return false;
|
|
@@ -206,53 +218,112 @@ async function copyFile(src, dest, dryRun) {
|
|
|
206
218
|
console.log(` copied: ${src} → ${dest}`);
|
|
207
219
|
return true;
|
|
208
220
|
}
|
|
221
|
+
const OPENCODE_RUN = 'opencode run';
|
|
209
222
|
function firstLoopCommand(pattern, tool) {
|
|
210
223
|
const cmds = {
|
|
211
224
|
'daily-triage': {
|
|
212
225
|
grok: '/loop 1d Run loop-triage. Update STATE.md. No auto-fix in week one.',
|
|
213
226
|
claude: '/loop 1d $loop-triage — update STATE.md. Report-only week one.',
|
|
214
227
|
codex: 'Automation daily: $loop-triage → update STATE.md. Report-only.',
|
|
228
|
+
opencode: `${OPENCODE_RUN} "Run loop-triage. Read STATE.md first. Update High Priority and Watch List. No auto-fix in week one." --agent loop-triage`,
|
|
215
229
|
},
|
|
216
230
|
'pr-babysitter': {
|
|
217
231
|
grok: '/loop 10m Run pr-review-triage. Update pr-babysitter-state.md. Worktree + minimal-fix + verifier for allowlisted PRs only. Escalate after 3 attempts.',
|
|
218
232
|
claude: '/loop 10m $pr-review-triage — update pr-babysitter-state.md. No auto-merge.',
|
|
219
233
|
codex: 'Automation 10m: pr-review-triage → pr-babysitter-state.md. No auto-merge.',
|
|
234
|
+
opencode: `${OPENCODE_RUN} "Run PR babysitter triage. Read pr-babysitter-state.md first. Report only — no code edits." --title "PR babysitter"`,
|
|
220
235
|
},
|
|
221
236
|
'ci-sweeper': {
|
|
222
237
|
grok: '/loop 15m Run ci-triage on failing CI. Update ci-sweeper-state.md. Fix only regressions in worktree. Max 3 attempts.',
|
|
223
238
|
claude: '/loop 15m $ci-triage — update ci-sweeper-state.md. Max 3 fix attempts.',
|
|
224
239
|
codex: 'Automation 15m: ci-triage on CI failures. Max 3 attempts.',
|
|
240
|
+
opencode: `${OPENCODE_RUN} "Run ci-triage on failing CI. Update ci-sweeper-state.md. Report only in week one."`,
|
|
225
241
|
},
|
|
226
242
|
'dependency-sweeper': {
|
|
227
243
|
grok: '/loop 6h Run dependency-triage. Patch-only auto-fix in worktree + verifier. Escalate majors and denylist.',
|
|
228
244
|
claude: '/loop 6h $dependency-triage — patch-only with verifier. Escalate risky bumps.',
|
|
229
245
|
codex: 'Automation 6h: dependency-triage. Patch-only with verifier.',
|
|
246
|
+
opencode: `${OPENCODE_RUN} "Run dependency-triage. Update dependency-sweeper-state.md. Report only — escalate majors."`,
|
|
230
247
|
},
|
|
231
248
|
'post-merge-cleanup': {
|
|
232
249
|
grok: '/loop 1d Run post-merge-scan on recent merges. Update post-merge-state.md. Small fixes only in worktree.',
|
|
233
250
|
claude: '/loop 1d $post-merge-scan — update post-merge-state.md. Small fixes only.',
|
|
234
251
|
codex: 'Automation daily: post-merge-scan → post-merge-state.md.',
|
|
252
|
+
opencode: `${OPENCODE_RUN} "Run post-merge-scan. Update post-merge-state.md. Report only in week one."`,
|
|
235
253
|
},
|
|
236
254
|
'changelog-drafter': {
|
|
237
255
|
grok: '/loop 1d Run changelog-scan on merges since last tag. Produce categorized draft in RELEASE_NOTES_DRAFT.md using draft-release-notes. Update changelog-drafter-state.md. Human review only.',
|
|
238
256
|
claude: '/loop 1d $changelog-scan + draft-release-notes — write RELEASE_NOTES_DRAFT.md and update state. Human approves before publish.',
|
|
239
257
|
codex: 'Automation daily: changelog-scan + draft-release-notes → RELEASE_NOTES_DRAFT.md. Human review.',
|
|
258
|
+
opencode: `${OPENCODE_RUN} "Run changelog-scan. Draft RELEASE_NOTES_DRAFT.md. Human review only — no publish."`,
|
|
240
259
|
},
|
|
241
260
|
'issue-triage': {
|
|
242
261
|
grok: '/loop 2h Run issue-triage. Update issue-triage-state.md. Propose labels and priority only. No auto-apply. Human reviews the needs-human slice.',
|
|
243
262
|
claude: '/loop 2h $issue-triage — update issue-triage-state.md. Suggest labels on allowlisted areas only. Report mode week one.',
|
|
244
263
|
codex: 'Automation 2h: issue-triage → issue-triage-state.md. Propose only.',
|
|
264
|
+
opencode: `${OPENCODE_RUN} "Run issue-triage. Update issue-triage-state.md. Propose labels only — no auto-apply."`,
|
|
245
265
|
},
|
|
246
266
|
};
|
|
247
267
|
return cmds[pattern][tool];
|
|
248
268
|
}
|
|
269
|
+
async function resolveAuditCli() {
|
|
270
|
+
const monorepo = path.resolve(PACKAGE_ROOT, '../loop-audit/dist/cli.js');
|
|
271
|
+
if (await exists(monorepo))
|
|
272
|
+
return monorepo;
|
|
273
|
+
try {
|
|
274
|
+
const { createRequire } = await import('node:module');
|
|
275
|
+
const require = createRequire(import.meta.url);
|
|
276
|
+
const pkg = require.resolve('@cobusgreyling/loop-audit/package.json');
|
|
277
|
+
return path.join(path.dirname(pkg), 'dist/cli.js');
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function runAuditJson(cli, targetDir) {
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
const child = spawn('node', [cli, targetDir, '--json'], {
|
|
286
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
287
|
+
});
|
|
288
|
+
let stdout = '';
|
|
289
|
+
child.stdout.on('data', (chunk) => {
|
|
290
|
+
stdout += chunk.toString();
|
|
291
|
+
});
|
|
292
|
+
child.on('error', reject);
|
|
293
|
+
child.on('close', () => {
|
|
294
|
+
if (stdout.trim())
|
|
295
|
+
resolve(stdout);
|
|
296
|
+
else
|
|
297
|
+
reject(new Error('loop-audit produced no output'));
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
async function runAuditSummary(targetDir) {
|
|
302
|
+
const cli = await resolveAuditCli();
|
|
303
|
+
if (!cli)
|
|
304
|
+
return null;
|
|
305
|
+
try {
|
|
306
|
+
const stdout = await runAuditJson(cli, targetDir);
|
|
307
|
+
return JSON.parse(stdout);
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function formatScoreBar(score, width = 20) {
|
|
314
|
+
const filled = Math.max(0, Math.min(width, Math.round((score / 100) * width)));
|
|
315
|
+
return `${'█'.repeat(filled)}${'░'.repeat(width - filled)} ${score}/100`;
|
|
316
|
+
}
|
|
317
|
+
function auditTargetArg(target, targetDir) {
|
|
318
|
+
return target === '.' ? '.' : targetDir;
|
|
319
|
+
}
|
|
249
320
|
async function main() {
|
|
250
321
|
const args = parseArgs(process.argv.slice(2));
|
|
251
322
|
if (args.help) {
|
|
252
323
|
console.log(`loop-init — scaffold loop engineering starters
|
|
253
324
|
|
|
254
325
|
Usage:
|
|
255
|
-
loop-init [target-dir] --pattern <name> --tool <grok|claude|codex>
|
|
326
|
+
loop-init [target-dir] --pattern <name> --tool <grok|claude|codex|opencode>
|
|
256
327
|
|
|
257
328
|
Patterns:
|
|
258
329
|
daily-triage (default)
|
|
@@ -272,6 +343,7 @@ Options:
|
|
|
272
343
|
Examples:
|
|
273
344
|
npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok
|
|
274
345
|
npx @cobusgreyling/loop-init . -p pr-babysitter -t claude
|
|
346
|
+
npx @cobusgreyling/loop-init . -p daily-triage -t opencode
|
|
275
347
|
`);
|
|
276
348
|
process.exit(0);
|
|
277
349
|
}
|
|
@@ -305,33 +377,52 @@ Examples:
|
|
|
305
377
|
? starterRoot
|
|
306
378
|
: path.join(startersRoot, baseStarter);
|
|
307
379
|
console.log(`\nloop-init: ${pattern} → ${targetDir} (${tool})${dryRun ? ' [dry-run]' : ''}\n`);
|
|
308
|
-
|
|
309
|
-
path.join(effectiveStarter, '
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await copyDir(path.join(skillsDir, entry), path.join(targetDir, toolPrefix, entry), dryRun);
|
|
380
|
+
if (tool === 'opencode') {
|
|
381
|
+
const skillsDir = path.join(effectiveStarter, 'skills');
|
|
382
|
+
if (await exists(skillsDir)) {
|
|
383
|
+
const entries = await readDirNames(skillsDir);
|
|
384
|
+
for (const entry of entries) {
|
|
385
|
+
await copyDir(path.join(skillsDir, entry), path.join(targetDir, 'skills', entry), dryRun);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const agentsMd = path.join(effectiveStarter, 'AGENTS.md');
|
|
389
|
+
if (await exists(agentsMd)) {
|
|
390
|
+
await copyFile(agentsMd, path.join(targetDir, 'AGENTS.md'), dryRun);
|
|
391
|
+
}
|
|
392
|
+
const opencodeJson = path.join(effectiveStarter, 'opencode.json.example');
|
|
393
|
+
if (await exists(opencodeJson)) {
|
|
394
|
+
await copyFile(opencodeJson, path.join(targetDir, 'opencode.json'), dryRun);
|
|
324
395
|
}
|
|
325
396
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
397
|
+
else {
|
|
398
|
+
const skillRoots = [
|
|
399
|
+
path.join(effectiveStarter, '.grok', 'skills'),
|
|
400
|
+
path.join(effectiveStarter, '.claude', 'skills'),
|
|
401
|
+
path.join(effectiveStarter, '.codex', 'skills'),
|
|
402
|
+
];
|
|
403
|
+
for (const skillsDir of skillRoots) {
|
|
404
|
+
if (!(await exists(skillsDir)))
|
|
405
|
+
continue;
|
|
406
|
+
const toolPrefix = skillsDir.includes('.grok')
|
|
407
|
+
? '.grok/skills'
|
|
408
|
+
: skillsDir.includes('.claude')
|
|
409
|
+
? '.claude/skills'
|
|
410
|
+
: '.codex/skills';
|
|
411
|
+
const entries = await readDirNames(skillsDir);
|
|
333
412
|
for (const entry of entries) {
|
|
334
|
-
await
|
|
413
|
+
await copyDir(path.join(skillsDir, entry), path.join(targetDir, toolPrefix, entry), dryRun);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const agentFiles = [
|
|
417
|
+
{ src: path.join(effectiveStarter, '.claude', 'agents'), dest: path.join(targetDir, '.claude', 'agents') },
|
|
418
|
+
{ src: path.join(effectiveStarter, '.codex', 'agents'), dest: path.join(targetDir, '.codex', 'agents') },
|
|
419
|
+
];
|
|
420
|
+
for (const { src, dest } of agentFiles) {
|
|
421
|
+
if (await exists(src)) {
|
|
422
|
+
const entries = await readDirNames(src);
|
|
423
|
+
for (const entry of entries) {
|
|
424
|
+
await copyFile(path.join(src, entry), path.join(dest, entry), dryRun);
|
|
425
|
+
}
|
|
335
426
|
}
|
|
336
427
|
}
|
|
337
428
|
}
|
|
@@ -352,7 +443,8 @@ Examples:
|
|
|
352
443
|
}
|
|
353
444
|
await copyL2Templates(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
354
445
|
await scaffoldObservability(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
355
|
-
|
|
446
|
+
await scaffoldConstraints(targetDir, templatesRoot, tool, dryRun);
|
|
447
|
+
if (tool !== 'opencode' && !dryRun && !(await exists(path.join(targetDir, 'AGENTS.md')))) {
|
|
356
448
|
const agentsTemplate = `# AGENTS.md
|
|
357
449
|
|
|
358
450
|
## Test commands
|
|
@@ -366,10 +458,29 @@ npm run lint
|
|
|
366
458
|
await writeFile(path.join(targetDir, 'AGENTS.md'), agentsTemplate);
|
|
367
459
|
console.log(' created: AGENTS.md (template)');
|
|
368
460
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
461
|
+
const auditArg = auditTargetArg(target, targetDir);
|
|
462
|
+
if (!dryRun) {
|
|
463
|
+
const audit = await runAuditSummary(targetDir);
|
|
464
|
+
if (audit) {
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log(`✓ Loop Ready: ${audit.score}/100 (${audit.level})`);
|
|
467
|
+
console.log(` ${formatScoreBar(audit.score)}`);
|
|
468
|
+
console.log(` ${audit.assessment}`);
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log('Paste badge in README:');
|
|
471
|
+
console.log(` npx @cobusgreyling/loop-audit ${auditArg} --badge`);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
console.log('\n=== Loop Ready score ===');
|
|
475
|
+
console.log(` npx @cobusgreyling/loop-audit ${auditArg} --suggest`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log(`First loop (${tool}):`);
|
|
480
|
+
console.log(` ${firstLoopCommand(pattern, tool)}`);
|
|
481
|
+
console.log('');
|
|
482
|
+
console.log(`Estimate cost: npx @cobusgreyling/loop-cost --pattern ${pattern} --level L1`);
|
|
483
|
+
console.log('');
|
|
373
484
|
}
|
|
374
485
|
async function readDirNames(dir) {
|
|
375
486
|
const { readdir } = await import('node:fs/promises');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cobusgreyling/loop-init",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Scaffold loop engineering patterns and starters into any project. Supports Grok, Claude Code, Codex and more. npx @cobusgreyling/loop-init . --pattern daily-triage --tool
|
|
3
|
+
"version": "1.3.1",
|
|
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": {
|
|
7
7
|
"loop-init": "./dist/cli.js"
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"claude-code",
|
|
32
32
|
"grok",
|
|
33
33
|
"codex",
|
|
34
|
+
"opencode",
|
|
34
35
|
"github-actions",
|
|
35
36
|
"patterns"
|
|
36
37
|
],
|
|
@@ -48,6 +49,9 @@
|
|
|
48
49
|
"publishConfig": {
|
|
49
50
|
"access": "public"
|
|
50
51
|
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@cobusgreyling/loop-audit": "^1.5.0"
|
|
54
|
+
},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@types/node": "^26.0.0",
|
|
53
57
|
"typescript": "^6.0.3"
|
package/starters/README.md
CHANGED
|
@@ -14,6 +14,7 @@ npx @cobusgreyling/loop-init . -p pr-babysitter -t claude
|
|
|
14
14
|
| [minimal-loop](./minimal-loop/) | Grok | `.grok/skills/` |
|
|
15
15
|
| [minimal-loop-claude](./minimal-loop-claude/) | Claude Code | `.claude/skills/` + `.claude/agents/` |
|
|
16
16
|
| [minimal-loop-codex](./minimal-loop-codex/) | Codex | `.codex/skills/` + `.codex/agents/` |
|
|
17
|
+
| [minimal-loop-opencode](./minimal-loop-opencode/) | Opencode | `skills/` + `AGENTS.md` |
|
|
17
18
|
|
|
18
19
|
## L2 assisted patterns
|
|
19
20
|
|
|
@@ -31,4 +32,4 @@ After copying:
|
|
|
31
32
|
```bash
|
|
32
33
|
npx @cobusgreyling/loop-audit .
|
|
33
34
|
npx @cobusgreyling/loop-audit . --suggest
|
|
34
|
-
```
|
|
35
|
+
```
|
|
@@ -13,6 +13,13 @@ Last release tag: v2.14.0 (2026-06-01)
|
|
|
13
13
|
## Recently Published
|
|
14
14
|
- v2.14.0 — published 2026-06-01 (human reviewed draft)
|
|
15
15
|
|
|
16
|
+
## Post-Run Critique (from last run)
|
|
17
|
+
- **Missed items**: 0 reported
|
|
18
|
+
- **False positives**: 2 internal chore PRs — tightened bot filter
|
|
19
|
+
- **Grouping issues**: 1 fix in Features — add label check
|
|
20
|
+
- **Retries**: 0
|
|
21
|
+
- **Prompt/policy adjustments**: None needed this run
|
|
22
|
+
|
|
16
23
|
## Scan Window Notes
|
|
17
24
|
- Ignore Dependabot / Renovate PRs (handled by dependency-sweeper)
|
|
18
25
|
- Direct commits on main are included if they have conventional type or linked issue
|
|
@@ -8,5 +8,12 @@ Last run: never
|
|
|
8
8
|
|
|
9
9
|
## Recent Noise (ignored this run)
|
|
10
10
|
|
|
11
|
+
## Post-Run Critique (from last run)
|
|
12
|
+
- High-noise: dependabot PRs surfaced again — add to ignore list
|
|
13
|
+
- False positives: 1 CI flake (known flaky test)
|
|
14
|
+
- Deprioritize: lint warnings moved to Watch List
|
|
15
|
+
- Friction: triage missed nightly deploy failure (was infra, not code)
|
|
16
|
+
- Adjustment: include infra check status in scan
|
|
17
|
+
|
|
11
18
|
---
|
|
12
19
|
Run log: —
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# AGENTS.md — Opencode Minimal Loop
|
|
2
|
+
|
|
3
|
+
These rules are loaded by opencode before loop work.
|
|
4
|
+
|
|
5
|
+
## Loop Mode
|
|
6
|
+
|
|
7
|
+
- Start in L1 report-only mode.
|
|
8
|
+
- Read `STATE.md` before any triage.
|
|
9
|
+
- Update `STATE.md` after every loop run.
|
|
10
|
+
- Do not edit source code until the human explicitly enables L2.
|
|
11
|
+
|
|
12
|
+
## Safety
|
|
13
|
+
|
|
14
|
+
- Never push or merge without human approval.
|
|
15
|
+
- Never edit `.env`, `.env.*`, `auth/`, `payments/`, `secrets/`, or `credentials/`.
|
|
16
|
+
- Use a git worktree for every code-changing attempt.
|
|
17
|
+
- Max 3 fix attempts per item; escalate after that.
|
|
18
|
+
|
|
19
|
+
## Verification
|
|
20
|
+
|
|
21
|
+
- For L2+ changes, dispatch a verifier sub-agent after implementation.
|
|
22
|
+
- Run the project's documented tests before proposing a fix.
|
|
23
|
+
- Record test evidence in `STATE.md`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Loop Configuration — Minimal Triage (Opencode)
|
|
2
|
+
|
|
3
|
+
## Active Loops
|
|
4
|
+
|
|
5
|
+
| Pattern | Cadence | Status | Command |
|
|
6
|
+
|---------|---------|--------|---------|
|
|
7
|
+
| Daily Triage | 1d | L1 report-only | `opencode run "Run loop-triage" --agent loop-triage` via cron/systemd |
|
|
8
|
+
|
|
9
|
+
## Human Gates
|
|
10
|
+
|
|
11
|
+
- No auto-fix until L2 checklist complete.
|
|
12
|
+
- All high-risk paths require human review (see docs/safety.md denylist).
|
|
13
|
+
|
|
14
|
+
## Worktrees
|
|
15
|
+
|
|
16
|
+
- Use an explicit `git worktree` and run opencode with `--dir <worktree>` for implementer runs (L2+).
|
|
17
|
+
- One worktree per fix attempt; discard after verifier REJECT.
|
|
18
|
+
|
|
19
|
+
## Connectors (MCP)
|
|
20
|
+
|
|
21
|
+
- MCP optional for L1 report-only loops.
|
|
22
|
+
- For L2+: GitHub MCP can read CI/issues; scope connectors to read + comment until trusted.
|
|
23
|
+
|
|
24
|
+
## Budget
|
|
25
|
+
|
|
26
|
+
- Max sub-agent spawns per run: 0 (L1).
|
|
27
|
+
- Review STATE.md daily.
|
|
28
|
+
- If token spend hits 80% of daily cap, switch to report-only.
|
|
29
|
+
|
|
30
|
+
## Links
|
|
31
|
+
|
|
32
|
+
- Pattern: [daily-triage](../../patterns/daily-triage.md)
|
|
33
|
+
- Checklist: [loop-design-checklist](../../docs/loop-design-checklist.md)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Minimal Loop Starter — Opencode
|
|
2
|
+
|
|
3
|
+
Clone this into your project root to run a **report-only daily triage loop** (L1 readiness) with opencode.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Scaffold (recommended):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @cobusgreyling/loop-init . --pattern daily-triage --tool opencode
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or copy manually:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cp -r starters/minimal-loop-opencode/skills .
|
|
17
|
+
cp starters/minimal-loop-opencode/AGENTS.md .
|
|
18
|
+
cp starters/minimal-loop-opencode/LOOP.md .
|
|
19
|
+
cp starters/minimal-loop-opencode/STATE.md.example STATE.md
|
|
20
|
+
cp starters/minimal-loop-opencode/opencode.json.example opencode.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. Customize `STATE.md` project name.
|
|
24
|
+
|
|
25
|
+
3. Start the loop:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
opencode run "Run loop-triage. Read STATE.md first. Append high-priority and watch items. Update Last run timestamp. Do not auto-fix anything in week one." --agent loop-triage
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
4. Read `STATE.md` each morning for 1-2 weeks. Tune the triage skill.
|
|
32
|
+
|
|
33
|
+
5. When triage quality is good, add `minimal-fix` from `templates/SKILL.md.minimal-fix` and enable small auto-wins with a verifier agent in a worktree.
|
|
34
|
+
|
|
35
|
+
## What's Included
|
|
36
|
+
|
|
37
|
+
| File | Purpose |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| `STATE.md.example` | State spine template |
|
|
40
|
+
| `skills/loop-triage/SKILL.md` | Triage skill |
|
|
41
|
+
| `AGENTS.md` | Always-on project rules for opencode |
|
|
42
|
+
| `LOOP.md` | Loop config doc for your team |
|
|
43
|
+
| `opencode.json.example` | Example opencode agent definitions |
|
|
44
|
+
|
|
45
|
+
## Next Steps
|
|
46
|
+
|
|
47
|
+
- [Loop Design Checklist](../../docs/loop-design-checklist.md)
|
|
48
|
+
- [Daily Triage pattern](../../patterns/daily-triage.md)
|
|
49
|
+
- [Opencode example](../../examples/opencode/daily-triage.md)
|
|
50
|
+
- Run `npx @cobusgreyling/loop-audit .` for readiness score
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"agent": {
|
|
4
|
+
"loop-triage": {
|
|
5
|
+
"name": "loop-triage",
|
|
6
|
+
"description": "Report-only daily triage loop. Reads STATE.md, updates high-priority items, and does not edit source code in L1 mode.",
|
|
7
|
+
"mode": "primary",
|
|
8
|
+
"prompt": "Read AGENTS.md, LOOP.md, STATE.md, and skills/loop-triage/SKILL.md. Run report-only triage. Update STATE.md. Do not edit source code unless the human has explicitly enabled L2.",
|
|
9
|
+
"permission": {
|
|
10
|
+
"bash": "ask",
|
|
11
|
+
"edit": "ask"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"implementer": {
|
|
15
|
+
"name": "implementer",
|
|
16
|
+
"description": "L2 implementer agent for minimal scoped fixes inside an isolated worktree.",
|
|
17
|
+
"mode": "subagent",
|
|
18
|
+
"prompt": "Implement only the requested minimal fix. Stay within the supplied worktree, respect AGENTS.md and LOOP.md, run documented tests, and stop for human approval on denylisted paths.",
|
|
19
|
+
"permission": {
|
|
20
|
+
"bash": "ask",
|
|
21
|
+
"edit": "ask"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"verifier": {
|
|
25
|
+
"name": "verifier",
|
|
26
|
+
"description": "Checker agent for L2+ changes. Reviews diffs and test evidence; APPROVE or REJECT only.",
|
|
27
|
+
"mode": "subagent",
|
|
28
|
+
"prompt": "Review the supplied diff or worktree summary against project rules, tests, and docs/safety.md. Do not edit files. Respond with APPROVE or REJECT and concise evidence.",
|
|
29
|
+
"permission": {
|
|
30
|
+
"bash": "ask",
|
|
31
|
+
"edit": "deny"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop-triage
|
|
3
|
+
description: >
|
|
4
|
+
Triage recent changes, CI failures, issues, and conversations.
|
|
5
|
+
Produces a concise, actionable findings report suitable for a loop to consume.
|
|
6
|
+
Writes structured output to a state file or issue tracker.
|
|
7
|
+
user_invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Loop Triage Skill
|
|
11
|
+
|
|
12
|
+
You are an expert engineering triage agent. Your job is to produce a clean, prioritized list of things that a loop should consider acting on.
|
|
13
|
+
|
|
14
|
+
## Inputs (the loop will provide these)
|
|
15
|
+
|
|
16
|
+
- Recent CI / test failures (last 24h)
|
|
17
|
+
- Open issues / tickets assigned to the team
|
|
18
|
+
- Recent commits on main (last 24-48h)
|
|
19
|
+
- Any chat threads the loop has visibility into
|
|
20
|
+
- The current state file (what the loop already knows about)
|
|
21
|
+
|
|
22
|
+
## Output Format
|
|
23
|
+
|
|
24
|
+
Produce a markdown report with these sections:
|
|
25
|
+
|
|
26
|
+
### 1. High-Priority Items (act on these)
|
|
27
|
+
|
|
28
|
+
- Clear, one-line description
|
|
29
|
+
- Why it matters (impact, risk, or customer pain)
|
|
30
|
+
- Suggested next action for the loop (for example, "draft minimal fix in isolated worktree")
|
|
31
|
+
- Rough effort estimate
|
|
32
|
+
|
|
33
|
+
### 2. Watch Items (monitor, do not act yet)
|
|
34
|
+
|
|
35
|
+
- Same format but lower urgency
|
|
36
|
+
|
|
37
|
+
### 3. Noise / Ignore
|
|
38
|
+
|
|
39
|
+
- Brief list of things the loop looked at and decided were not worth action
|
|
40
|
+
|
|
41
|
+
### 4. State Updates
|
|
42
|
+
|
|
43
|
+
- Any facts the loop should remember for the next run (for example, "PR #1234 now has 2 approvals")
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
- Be brutally concise. The loop and the human reading the state will thank you.
|
|
48
|
+
- Only put something in "High-Priority" if a reasonable engineer would want to know about it today.
|
|
49
|
+
- When in doubt, put it in Watch or Noise rather than creating work.
|
|
50
|
+
- Never propose architectural overhauls during triage — this skill is for signal, not invention.
|
|
51
|
+
- Respect the project's existing skills and conventions (they will be provided in context).
|
|
52
|
+
|
|
53
|
+
## Example Invocation (in an opencode loop)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
opencode run "Call loop-triage and append high-priority items to STATE.md. Do not edit code."
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The triage skill should be the "eyes" of the loop. Keep it focused and honest.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop-constraints
|
|
3
|
+
description: >
|
|
4
|
+
Read loop-constraints.md at the start of every run and enforce every rule.
|
|
5
|
+
This skill runs BEFORE triage or any action skill. Constraints are binding.
|
|
6
|
+
user_invocable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Loop Constraints Enforcer
|
|
10
|
+
|
|
11
|
+
You are the guardrail. Before any other work begins, you MUST:
|
|
12
|
+
|
|
13
|
+
1. Read `loop-constraints.md` from the project root.
|
|
14
|
+
2. Load every rule into your working memory.
|
|
15
|
+
3. Check if `loop-pause-all` is active → exit immediately.
|
|
16
|
+
4. Apply these rules to EVERY action that follows.
|
|
17
|
+
|
|
18
|
+
## How to enforce
|
|
19
|
+
|
|
20
|
+
- Before pushing: re-read the Push & Merge section. If ANY rule blocks it, stop and tell the human.
|
|
21
|
+
- Before editing a file: re-read the Paths section. If the path matches a denylist pattern, escalate.
|
|
22
|
+
- Before proposing a fix: re-read the Code section. Run tests. One fix per run.
|
|
23
|
+
- Before merging: re-read the Push & Merge section. Human must approve.
|
|
24
|
+
|
|
25
|
+
## Output at start of run
|
|
26
|
+
|
|
27
|
+
Always begin with a one-line confirmation:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Constraints loaded from loop-constraints.md: N rules active.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If no `loop-constraints.md` exists, say so and proceed with default safety rules from `docs/safety.md`.
|
|
34
|
+
|
|
35
|
+
## Interaction with other skills
|
|
36
|
+
|
|
37
|
+
- `loop-triage` — constraints may override triage priority (e.g. "don't push" means don't act on CI fixes)
|
|
38
|
+
- `minimal-fix` — constraints limit what files can be touched
|
|
39
|
+
- `loop-verifier` — constraints define denylist paths the verifier must check
|
|
40
|
+
- `loop-budget` — constraints may impose stricter budget than loop-budget.md
|
|
41
|
+
|
|
42
|
+
## Default constraints (when no file exists)
|
|
43
|
+
|
|
44
|
+
If `loop-constraints.md` is absent, enforce these minimums:
|
|
45
|
+
- Never edit `.env`, `.env.*`, `auth/`, `payments/`, `secrets/`, `credentials/`
|
|
46
|
+
- Never auto-merge to main
|
|
47
|
+
- Never disable tests
|
|
48
|
+
- Escalate after 3 failed fix attempts
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Loop Constraints
|
|
2
|
+
|
|
3
|
+
> Add rules below with `/constraints <rule>` in your agent.
|
|
4
|
+
> The `loop-constraints` skill reads this file at the start of every run.
|
|
5
|
+
> Constraints here are **binding** — the agent MUST follow them.
|
|
6
|
+
|
|
7
|
+
## Push & Merge
|
|
8
|
+
- Don't push before telling me
|
|
9
|
+
- Never auto-merge to main without human approval
|
|
10
|
+
- Always create a draft PR first; let me review before marking ready
|
|
11
|
+
|
|
12
|
+
## Paths
|
|
13
|
+
- Never edit .env, .env.*, auth/, payments/, secrets/, credentials/
|
|
14
|
+
- Never edit infrastructure configs without human approval
|
|
15
|
+
|
|
16
|
+
## Code
|
|
17
|
+
- Always run tests before proposing a fix
|
|
18
|
+
- Never disable tests to make CI green
|
|
19
|
+
- Never refactor unrelated code — one fix per run
|
|
20
|
+
- Max 3 fix attempts per item; escalate after
|
|
21
|
+
|
|
22
|
+
## Communication
|
|
23
|
+
- Always tell me what you're about to do before doing it
|
|
24
|
+
- Never close an issue or PR without my approval
|
|
25
|
+
|
|
26
|
+
## Budget
|
|
27
|
+
- If token spend hits 80% of daily cap, switch to report-only
|
|
28
|
+
- If loop-pause-all is active, exit immediately
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
<!-- Add your own rules below. Use plain English. The loop reads this verbatim. -->
|