@cobusgreyling/loop-init 1.2.3 → 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 +132 -30
- package/package.json +6 -2
- package/starters/README.md +2 -1
- 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/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))
|
|
@@ -214,53 +218,112 @@ async function copyFile(src, dest, dryRun) {
|
|
|
214
218
|
console.log(` copied: ${src} → ${dest}`);
|
|
215
219
|
return true;
|
|
216
220
|
}
|
|
221
|
+
const OPENCODE_RUN = 'opencode run';
|
|
217
222
|
function firstLoopCommand(pattern, tool) {
|
|
218
223
|
const cmds = {
|
|
219
224
|
'daily-triage': {
|
|
220
225
|
grok: '/loop 1d Run loop-triage. Update STATE.md. No auto-fix in week one.',
|
|
221
226
|
claude: '/loop 1d $loop-triage — update STATE.md. Report-only week one.',
|
|
222
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`,
|
|
223
229
|
},
|
|
224
230
|
'pr-babysitter': {
|
|
225
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.',
|
|
226
232
|
claude: '/loop 10m $pr-review-triage — update pr-babysitter-state.md. No auto-merge.',
|
|
227
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"`,
|
|
228
235
|
},
|
|
229
236
|
'ci-sweeper': {
|
|
230
237
|
grok: '/loop 15m Run ci-triage on failing CI. Update ci-sweeper-state.md. Fix only regressions in worktree. Max 3 attempts.',
|
|
231
238
|
claude: '/loop 15m $ci-triage — update ci-sweeper-state.md. Max 3 fix attempts.',
|
|
232
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."`,
|
|
233
241
|
},
|
|
234
242
|
'dependency-sweeper': {
|
|
235
243
|
grok: '/loop 6h Run dependency-triage. Patch-only auto-fix in worktree + verifier. Escalate majors and denylist.',
|
|
236
244
|
claude: '/loop 6h $dependency-triage — patch-only with verifier. Escalate risky bumps.',
|
|
237
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."`,
|
|
238
247
|
},
|
|
239
248
|
'post-merge-cleanup': {
|
|
240
249
|
grok: '/loop 1d Run post-merge-scan on recent merges. Update post-merge-state.md. Small fixes only in worktree.',
|
|
241
250
|
claude: '/loop 1d $post-merge-scan — update post-merge-state.md. Small fixes only.',
|
|
242
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."`,
|
|
243
253
|
},
|
|
244
254
|
'changelog-drafter': {
|
|
245
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.',
|
|
246
256
|
claude: '/loop 1d $changelog-scan + draft-release-notes — write RELEASE_NOTES_DRAFT.md and update state. Human approves before publish.',
|
|
247
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."`,
|
|
248
259
|
},
|
|
249
260
|
'issue-triage': {
|
|
250
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.',
|
|
251
262
|
claude: '/loop 2h $issue-triage — update issue-triage-state.md. Suggest labels on allowlisted areas only. Report mode week one.',
|
|
252
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."`,
|
|
253
265
|
},
|
|
254
266
|
};
|
|
255
267
|
return cmds[pattern][tool];
|
|
256
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
|
+
}
|
|
257
320
|
async function main() {
|
|
258
321
|
const args = parseArgs(process.argv.slice(2));
|
|
259
322
|
if (args.help) {
|
|
260
323
|
console.log(`loop-init — scaffold loop engineering starters
|
|
261
324
|
|
|
262
325
|
Usage:
|
|
263
|
-
loop-init [target-dir] --pattern <name> --tool <grok|claude|codex>
|
|
326
|
+
loop-init [target-dir] --pattern <name> --tool <grok|claude|codex|opencode>
|
|
264
327
|
|
|
265
328
|
Patterns:
|
|
266
329
|
daily-triage (default)
|
|
@@ -280,6 +343,7 @@ Options:
|
|
|
280
343
|
Examples:
|
|
281
344
|
npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok
|
|
282
345
|
npx @cobusgreyling/loop-init . -p pr-babysitter -t claude
|
|
346
|
+
npx @cobusgreyling/loop-init . -p daily-triage -t opencode
|
|
283
347
|
`);
|
|
284
348
|
process.exit(0);
|
|
285
349
|
}
|
|
@@ -313,33 +377,52 @@ Examples:
|
|
|
313
377
|
? starterRoot
|
|
314
378
|
: path.join(startersRoot, baseStarter);
|
|
315
379
|
console.log(`\nloop-init: ${pattern} → ${targetDir} (${tool})${dryRun ? ' [dry-run]' : ''}\n`);
|
|
316
|
-
|
|
317
|
-
path.join(effectiveStarter, '
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
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);
|
|
332
395
|
}
|
|
333
396
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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);
|
|
341
412
|
for (const entry of entries) {
|
|
342
|
-
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
|
+
}
|
|
343
426
|
}
|
|
344
427
|
}
|
|
345
428
|
}
|
|
@@ -361,7 +444,7 @@ Examples:
|
|
|
361
444
|
await copyL2Templates(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
362
445
|
await scaffoldObservability(pattern, tool, targetDir, templatesRoot, dryRun);
|
|
363
446
|
await scaffoldConstraints(targetDir, templatesRoot, tool, dryRun);
|
|
364
|
-
if (!dryRun && !(await exists(path.join(targetDir, 'AGENTS.md')))) {
|
|
447
|
+
if (tool !== 'opencode' && !dryRun && !(await exists(path.join(targetDir, 'AGENTS.md')))) {
|
|
365
448
|
const agentsTemplate = `# AGENTS.md
|
|
366
449
|
|
|
367
450
|
## Test commands
|
|
@@ -375,10 +458,29 @@ npm run lint
|
|
|
375
458
|
await writeFile(path.join(targetDir, 'AGENTS.md'), agentsTemplate);
|
|
376
459
|
console.log(' created: AGENTS.md (template)');
|
|
377
460
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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('');
|
|
382
484
|
}
|
|
383
485
|
async function readDirNames(dir) {
|
|
384
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
|
+
```
|
|
@@ -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.
|