@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 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
- const skillRoots = [
317
- path.join(effectiveStarter, '.grok', 'skills'),
318
- path.join(effectiveStarter, '.claude', 'skills'),
319
- path.join(effectiveStarter, '.codex', 'skills'),
320
- ];
321
- for (const skillsDir of skillRoots) {
322
- if (!(await exists(skillsDir)))
323
- continue;
324
- const toolPrefix = skillsDir.includes('.grok')
325
- ? '.grok/skills'
326
- : skillsDir.includes('.claude')
327
- ? '.claude/skills'
328
- : '.codex/skills';
329
- const entries = await readDirNames(skillsDir);
330
- for (const entry of entries) {
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
- const agentFiles = [
335
- { src: path.join(effectiveStarter, '.claude', 'agents'), dest: path.join(targetDir, '.claude', 'agents') },
336
- { src: path.join(effectiveStarter, '.codex', 'agents'), dest: path.join(targetDir, '.codex', 'agents') },
337
- ];
338
- for (const { src, dest } of agentFiles) {
339
- if (await exists(src)) {
340
- const entries = await readDirNames(src);
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 copyFile(path.join(src, entry), path.join(dest, entry), dryRun);
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
- console.log('\n=== Next steps ===');
379
- console.log(` npx @cobusgreyling/loop-audit ${target === '.' ? '.' : target} --suggest`);
380
- console.log(` npx @cobusgreyling/loop-cost --pattern ${pattern}`);
381
- console.log(` First loop command (${tool}):\n ${firstLoopCommand(pattern, tool)}\n`);
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.2.3",
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 grok",
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"
@@ -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,12 @@
1
+ # Loop State — My Project
2
+
3
+ Last run: never
4
+
5
+ ## High Priority (loop is acting or waiting on human)
6
+
7
+ ## Watch List
8
+
9
+ ## Recent Noise (ignored this run)
10
+
11
+ ---
12
+ Run log: —
@@ -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.