diff --git a/.github/workflows/daily-triage.yml b/.github/workflows/daily-triage.yml index 6b96c7e..f03a847 100644 --- a/.github/workflows/daily-triage.yml +++ b/.github/workflows/daily-triage.yml @@ -65,7 +65,7 @@ jobs: ## High Priority (loop is acting or waiting on human) - Maintain loop readiness score ≥ 58 (current: **${SCORE}**, level **${LEVEL}**). - - Publish \`@cobusgreyling/loop-audit\` and \`@cobusgreyling/loop-init\` to npm when \`NPM_TOKEN\` is configured (see docs/RELEASE.md). + - Keep npm packages current after tool changes (tag \`loop-audit-v*\`, \`loop-init-v*\`, \`loop-cost-v*\` — see docs/RELEASE.md). $([ "$FAILING" -gt 0 ] && echo "- **${FAILING}** dogfood workflow(s) failing — investigate CI.") ## Watch List diff --git a/.github/workflows/release-loop-cost.yml b/.github/workflows/release-loop-cost.yml new file mode 100644 index 0000000..5e5c66d --- /dev/null +++ b/.github/workflows/release-loop-cost.yml @@ -0,0 +1,39 @@ +name: Release loop-cost + +on: + push: + tags: + - 'loop-cost-v*' + +permissions: + contents: read + id-token: write + +jobs: + test-and-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + cache-dependency-path: tools/loop-cost/package-lock.json + + - name: Ensure npm supports trusted publishing (OIDC) + run: npm install -g npm@latest + + - name: Install, build, test + working-directory: tools/loop-cost + run: | + npm ci + npm run build + npm test + + - name: Publish to npm + working-directory: tools/loop-cost + run: npm publish --access public --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba4e8ab..b5d1199 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,8 +52,9 @@ Also add an entry to `patterns/registry.yaml`. ## Community -- **Questions**: open an issue with label `question` or `pattern-request` -- **Discussions**: enable GitHub Discussions on the repo for pattern Q&A (recommended for maintainers) +- **Questions**: [GitHub Discussions](https://github.com/cobusgreyling/loop-engineering/discussions) (preferred) or issue with label `question` +- **Show your loop**: post in Discussions or add a row to [docs/adopters.md](./docs/adopters.md) +- **Good first issues**: look for label [`good first issue`](https://github.com/cobusgreyling/loop-engineering/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) - **Security**: see [SECURITY.md](./SECURITY.md) — do not file public issues for exploitable vulnerabilities Thank you for helping make this the go-to reference for loop engineering. \ No newline at end of file diff --git a/README.md b/README.md index 3cd47dc..db4ab1d 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@

+ GitHub stars loop-audit dogfood + loop-audit npm MIT - Pages + Pages

@@ -42,10 +44,10 @@ A loop is a recursive goal: you define a purpose and the AI iterates (often with | [Pattern Picker](docs/pattern-picker.md) | Which loop to run first — **start here if unsure** | | [Primitives Matrix](docs/primitives-matrix.md) | Grok vs Claude Code vs Codex — bookmark this | | [Loop Design Checklist](docs/loop-design-checklist.md) | Ship readiness rubric | -| [Patterns](patterns/README.md) | 6 production patterns including the new low-risk Changelog Drafter | +| [Patterns](patterns/README.md) | 7 production patterns + [interactive picker](https://cobusgreyling.github.io/loop-engineering/#interactive) | | [Starters](starters/) | Clone-and-run kits (Grok, Claude Code, Codex) | -| [loop-audit](tools/loop-audit/) | Loop Readiness Score CLI — `npx @cobusgreyling/loop-audit` | -| [loop-init](tools/loop-init/) | Scaffold starters + budget/run-log — `npx @cobusgreyling/loop-init` | +| [loop-audit](tools/loop-audit/) | Loop Readiness Score CLI (v1.4 + activity detection) — `npx @cobusgreyling/loop-audit . --suggest` | +| [loop-init](tools/loop-init/) | Scaffold starters + budget/run-log (v1.2) — `npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok` | | [loop-cost](tools/loop-cost/) | Token spend estimator — `npx @cobusgreyling/loop-cost` | | [Stories](stories/) | Real wins and honest failures | @@ -107,10 +109,11 @@ flowchart LR | [Dependency Sweeper](patterns/dependency-sweeper.md) | 6h–1d | [dependency-sweeper](starters/dependency-sweeper/) | L2 patch-only | Medium | | [Changelog Drafter](patterns/changelog-drafter.md) | 1d or tag | [changelog-drafter](starters/changelog-drafter/) | **L1** draft | Low | | [Post-Merge Cleanup](patterns/post-merge-cleanup.md) | 1d–6h | [post-merge-cleanup](starters/post-merge-cleanup/) | **L1** off-peak | Low | +| [Issue Triage](patterns/issue-triage.md) | 2h–1d | [minimal-loop](starters/minimal-loop/) | **L1** propose-only | Low | -Not sure which to pick? See [pattern-picker](docs/pattern-picker.md). +Not sure which to pick? Try the [interactive picker](https://cobusgreyling.github.io/loop-engineering/#interactive) or [pattern-picker](docs/pattern-picker.md). -Machine-readable index: [patterns/registry.yaml](patterns/registry.yaml) (now 6 patterns) +Machine-readable index: [patterns/registry.yaml](patterns/registry.yaml) (7 patterns) ## Getting Started (5 minutes) @@ -131,7 +134,9 @@ bash scripts/before-after-demo.sh /loop 1d Run loop-triage. Update STATE.md. No auto-fix in week one. ``` -Packages publish from tagged releases — see [docs/RELEASE.md](docs/RELEASE.md). Until npm is live, run from this repo: +All three CLIs publish to npm from tagged releases — see [docs/RELEASE.md](docs/RELEASE.md). No clone required. + +**Develop from source** (monorepo contributors): ```bash cd tools/loop-init && npm ci && npm test && node dist/cli.js /path/to/project --pattern daily-triage --tool grok @@ -173,7 +178,7 @@ Addy Osmani: ## Contributing -Share production patterns, tool mappings, and failure stories. See [CONTRIBUTING.md](CONTRIBUTING.md) and [GitHub Discussions](https://github.com/cobusgreyling/loop-engineering/discussions). +Share production patterns, tool mappings, and failure stories. See [CONTRIBUTING.md](CONTRIBUTING.md), [adopters](docs/adopters.md), and [GitHub Discussions](https://github.com/cobusgreyling/loop-engineering/discussions). ## Sources diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 3641ac0..ed0a23a 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -6,7 +6,7 @@ This repo ships two public npm packages from `tools/`: |---------|-----------|-------------| | `@cobusgreyling/loop-audit` | `tools/loop-audit` | `loop-audit-v*` | | `@cobusgreyling/loop-init` | `tools/loop-init` | `loop-init-v*` | -| `@cobusgreyling/loop-cost` | `tools/loop-cost` | `loop-cost-v*` (add workflow when publishing) | +| `@cobusgreyling/loop-cost` | `tools/loop-cost` | `loop-cost-v*` | ## One-time setup (trusted publishing — recommended) @@ -16,6 +16,7 @@ Link npm to GitHub, then for **each package** on [npmjs.com](https://www.npmjs.c |---------|--------------|-------------------| | `@cobusgreyling/loop-audit` | `cobusgreyling/loop-engineering` | `release-loop-audit.yml` | | `@cobusgreyling/loop-init` | `cobusgreyling/loop-engineering` | `release-loop-init.yml` | +| `@cobusgreyling/loop-cost` | `cobusgreyling/loop-engineering` | `release-loop-cost.yml` | Names must match **exactly** (case-sensitive). No `NPM_TOKEN` secret is required when trusted publishing is configured. @@ -35,17 +36,22 @@ git tag loop-audit-v1.3.0 git push origin loop-audit-v1.3.0 # loop-init (bundles starters/templates, runs smoke tests) -git tag loop-init-v1.1.0 -git push origin loop-init-v1.1.0 +git tag loop-init-v1.2.0 +git push origin loop-init-v1.2.0 + +# loop-cost (bundles patterns/registry.yaml) +git tag loop-cost-v1.0.0 +git push origin loop-cost-v1.0.0 ``` -Workflows: `.github/workflows/release-loop-audit.yml`, `.github/workflows/release-loop-init.yml`. +Workflows: `.github/workflows/release-loop-audit.yml`, `.github/workflows/release-loop-init.yml`, `.github/workflows/release-loop-cost.yml`. ## Verify after publish ```bash npx @cobusgreyling/loop-audit --help npx @cobusgreyling/loop-init --help +npx @cobusgreyling/loop-cost --help mkdir /tmp/loop-init-test && cd /tmp/loop-init-test npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok --dry-run diff --git a/docs/adopters.md b/docs/adopters.md new file mode 100644 index 0000000..d37947f --- /dev/null +++ b/docs/adopters.md @@ -0,0 +1,33 @@ +# Adopters & community setups + +Forks and stars mean people are trying this in their own stacks. If you run a loop from this repo (or adapted from it), add yourself here via PR. + +## How to list your project + +Open a PR that adds a row to the table below: + +| Field | What to include | +|-------|-----------------| +| **Project** | Repo link or product name | +| **Pattern(s)** | e.g. Daily Triage + Issue Triage | +| **Tool** | Grok, Claude Code, Codex, GitHub Actions, mixed | +| **Level** | L1 / L2 / L3 (honest) | +| **Notes** | One line — what worked or what broke | + +## Adopters + +| Project | Pattern(s) | Tool | Level | Notes | +|---------|------------|------|-------|-------| +| [loop-engineering](https://github.com/cobusgreyling/loop-engineering) (this repo) | Daily Triage, Changelog Drafter, audit dogfood | GitHub Actions + Grok | L3 | Reference implementation — dogfoods `loop-audit` on every PR | + +*Your project here — see [CONTRIBUTING.md](../CONTRIBUTING.md).* + +## Show & tell + +Prefer chat over a PR? Post in [GitHub Discussions → Show your loop](https://github.com/cobusgreyling/loop-engineering/discussions/categories/show-your-loop) with: + +1. Which pattern you picked and why +2. Your first `/loop` or scheduler command +3. One surprise (good or bad) + +Failure reports are first-class — see [stories/](../stories/). \ No newline at end of file diff --git a/docs/assets/css/showcase.css b/docs/assets/css/showcase.css index baf7adf..2bb301f 100644 --- a/docs/assets/css/showcase.css +++ b/docs/assets/css/showcase.css @@ -506,4 +506,106 @@ section { .nav-links { display: none; } .hero { padding: 48px 0 40px; } .flow-arrow { display: none; } -} \ No newline at end of file +} + +/* === Interactive widgets (v1.4 site upgrade) === */ +.interactive { + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 28px; + margin: 24px 0; +} + +.symptom-pills { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 16px 0; +} + +.symptom-pill { + padding: 8px 14px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 999px; + font-size: 0.82rem; + color: var(--text-muted); + cursor: pointer; + user-select: none; + transition: all 0.15s; +} + +.symptom-pill:hover { border-color: var(--accent); color: var(--text); } +.symptom-pill.active { + background: rgba(62, 232, 197, 0.15); + border-color: var(--accent); + color: var(--accent); + font-weight: 600; +} + +.reco-card { + margin-top: 16px; + padding: 16px 18px; + background: #050810; + border: 1px solid var(--accent); + border-radius: 10px; + font-size: 0.9rem; +} + +.reco-card .cmd { + font-family: var(--font-mono); + background: rgba(255,255,255,0.04); + padding: 2px 6px; + border-radius: 4px; +} + +.check-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 8px 16px; + margin: 16px 0; +} + +.check-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; + color: var(--text-muted); + cursor: pointer; +} + +.check-item input { accent-color: var(--accent); } + +.live-score { + display: flex; + align-items: center; + gap: 16px; + margin: 12px 0; + padding: 12px 16px; + background: var(--bg-card); + border-radius: 10px; +} + +.live-score .big { + font-size: 2.1rem; + font-weight: 800; + color: var(--accent); + line-height: 1; +} + +.live-score .meta { font-size: 0.85rem; } + +.copy-btn { + font-size: 0.7rem; + padding: 2px 8px; + border-radius: 4px; + background: rgba(62,232,197,0.1); + color: var(--accent); + border: 1px solid rgba(62,232,197,0.3); + cursor: pointer; + margin-left: 8px; +} + +.copy-btn:hover { background: rgba(62,232,197,0.2); } \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 03670d2..2f9cf00 100644 --- a/docs/index.html +++ b/docs/index.html @@ -32,7 +32,7 @@
-
Open source · Tool-agnostic · Production-ready · L3 dogfood score · 6 patterns
+
Open source · Tool-agnostic · Production-ready · L3 dogfood · 7 patterns · Interactive tools

Design the system
that prompts your agents

Loop engineering moves you from "I prompt → agent responds → I prompt again" @@ -43,6 +43,12 @@

Design the system
that prompts your agents

Read the Essay Primitives Matrix +

+ Or try instantly: npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok +   +    npx @cobusgreyling/loop-audit . --suggest + +

Get started

+ +
+ +

Interactive Pattern Picker & Readiness Simulator

+

Pick your pain. See the exact loop + commands. Simulate your score live.

+ +
+

What's hurting right now?

+
+
CI red / flaky checks
+
PRs stalling on review/CI
+
Morning chaos — what should I do?
+
Dependabot / CVE noise
+
Merge debt / TODOs piling up
+
Stale release notes / changelogs
+
+ + +
+ +
+

Live Loop Readiness Simulator (mirrors loop-audit)

+

Check the boxes you already have (or plan to add). Score updates live.

+ +
+ + + + + + + + + + + + + +
+ +
+
+
10
+
/ 100
+
+
+
L0Not loop-ready — start with a starter.
+
+
+
+ +
+ Client-side approximation of loop-audit v1.4 scoring. L3 requires verifier + state + cost observability + proven activity. +
+
+
+

Engineering, not hype

@@ -330,5 +401,209 @@

Ready to stop prompting?

+ + \ No newline at end of file diff --git a/docs/pattern-picker.md b/docs/pattern-picker.md index 2d13d1e..dfcf619 100644 --- a/docs/pattern-picker.md +++ b/docs/pattern-picker.md @@ -8,8 +8,8 @@ flowchart TD B -->|yes| C[CI Sweeper] B -->|no| D{PRs stalling?} D -->|yes| E[PR Babysitter] - D -->|no| F{Morning chaos / unknown priorities?} - F -->|yes| G[Daily Triage] + D -->|no| F{Morning chaos or noisy issues?} + F -->|yes| G[Daily Triage + Issue Triage] F -->|no| H{Dependabot / CVE noise?} H -->|yes| I[Dependency Sweeper] H -->|no| J{Merge debt / TODOs piling up?} @@ -45,7 +45,7 @@ npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok # scaffolds |---------|---------|------------| | CI failing on main or PRs | [CI Sweeper](../patterns/ci-sweeper.md) | L2, 15m cadence, max 3 attempts | | PRs waiting on review/CI/rebase | [PR Babysitter](../patterns/pr-babysitter.md) | L1 watch → L2 assisted | -| "What should I work on?" every morning | [Daily Triage](../patterns/daily-triage.md) | **L1 report-only week one** | +| "What should I work on?" every morning or noisy GitHub issues | [Daily Triage](../patterns/daily-triage.md) + [Issue Triage](../patterns/issue-triage.md) (new) | **L1 report-only week one** — low risk, excellent pair | | Outdated packages / CVE alerts | [Dependency Sweeper](../patterns/dependency-sweeper.md) | L2 patch-only, denylist majors | | TODOs and cleanup after merges | [Post-Merge Cleanup](../patterns/post-merge-cleanup.md) | L1 off-peak, small fixes only | | Stale or missing release notes | [Changelog Drafter](../patterns/changelog-drafter.md) | **L1** (draft only first), very low risk | diff --git a/patterns/README.md b/patterns/README.md index c0e1a9a..a12ac01 100644 --- a/patterns/README.md +++ b/patterns/README.md @@ -16,6 +16,7 @@ Each pattern answers: |---------|---------|------|------| | PR Babysitter | 5–15m | Medium | [pr-babysitter.md](./pr-babysitter.md) | | Daily Triage | 1d–2h | Low | [daily-triage.md](./daily-triage.md) | +| Issue Triage (new) | 2h–1d | Low | [issue-triage.md](./issue-triage.md) | | CI Sweeper | 5–15m | Medium | [ci-sweeper.md](./ci-sweeper.md) | | Post-Merge Cleanup | 1d–6h | Low | [post-merge-cleanup.md](./post-merge-cleanup.md) | | Dependency Sweeper | 6h–1d | Medium | [dependency-sweeper.md](./dependency-sweeper.md) | diff --git a/patterns/issue-triage.md b/patterns/issue-triage.md new file mode 100644 index 0000000..a530c80 --- /dev/null +++ b/patterns/issue-triage.md @@ -0,0 +1,94 @@ +# Issue Triage Loop + +**Goal**: Continuously discover, deduplicate, prioritize, and label incoming issues, feature requests, and discussions so the team (and other loops) always have a clean, actionable top-of-queue. Pure report / proposal mode in week one. Extremely low risk, high leverage. + +## Scheduling + +**Recommended**: +- `/loop 2h` or `1d` (morning + end of day for busy repos) +- GitHub Action on `issues` / `discussion` events + scheduled fallback +- Pairs beautifully with Daily Triage (this loop feeds the "what should I work on" report) + +This is an excellent always-on, low-cost companion loop. + +## Required Skills + +- `issue-triage` — Scans open issues, discussions, and (optionally via MCP) Linear / Jira. Dedupes, extracts signals (labels, comments, linked PRs, age, reactions), proposes priority + suggested labels + one-sentence summary. +- `loop-verifier` (light or human) — Sanity check on the proposed triage actions / new labels before anything is applied. + +## State + +Filename: `issue-triage-state.md` + +Compact rolling view of the current backlog health: + +```markdown +# Issue Triage State +Last run: 2026-06-09 09:15 UTC +Open actionable: 14 (was 17) +New since last run: 3 +Needs human: 2 (one potential duplicate of #412, one unclear spec) + +## Top 5 (by loop score) +- #487 (bug, p1, 2d old) — "Crash on export with large files" — suggested: bug + needs-repro + area:export +- ... +``` + +The loop prunes closed/merged items and only keeps "needs attention" items. + +## How the Loop Runs (Typical Cycle) + +1. Discover new/updated issues + discussions since last run (or all open if first run). +2. For each: summarize intent, detect duplicates (title + embedding hints or simple text match), pull signals (age, author, linked PRs, reactions, existing labels). +3. Score / bucket: P0 (security, prod breakage), P1 (high impact + clear), P2, P3, needs-info, duplicate. +4. Write or update a clean prioritized list + suggested label set + short "why this matters" into the state file (and optionally a comment on the issue itself as "Loop triage note"). +5. Verifier (or human) reviews only the "needs human" bucket and any proposed label changes on sensitive areas. +6. Record run, prune resolved items, update counts. + +## Verification Strategy + +- The loop **never auto-labels or closes** in L1. +- In L2 it can apply allowlisted labels only (e.g. `area:*`, `needs-repro`) after verifier passes. +- Human always owns P0/P1 assignment for the first weeks and for anything touching auth, payments, security, or public API. + +## Human Handoff Points + +- Any issue touching security, auth, billing, or infra +- Duplicate detection that is uncertain (>30% chance wrong) +- Issues older than N days that the loop wants to close as "stale" (human confirms) +- When > X new issues appear in a single run (context overload signal) + +## Tool-Specific Notes + +**Grok Build TUI**: +``` +/loop 2h Run issue-triage skill. Read issue-triage-state.md first. Produce updated state + suggested labels for new items only. No auto-label or close. Escalate anything ambiguous. +``` + +**Claude Code**: +``` +/loop 2h $issue-triage — update issue-triage-state.md. Propose labels only on allowlisted areas. Human review for P0/P1. +``` + +**Codex**: +Automation every 2h or on `issues` event: run issue-triage → update state. Report mode. + +**GitHub Actions**: +See `examples/github-actions/` for a starter workflow that can react to issue events + scheduled run. + +## Failure Modes & Mitigations + +| Failure | Mitigation | +|--------------------------|----------| +| Over-prioritizing noisy reporter | Weight by signals the team actually cares about (reactions, linked PRs, internal +1s). Human overrides recorded in state. | +| Duplicate false positives | Conservative matching + always surface "possible duplicate of #NNN" for human confirmation in L1. | +| Alert fatigue on every new issue | Only notify human for the "needs human" slice. Everything else lives in the state file that Daily Triage or the engineer reads. | + +## Success Metrics + +- Reduction in time from issue open → first meaningful label or "needs info" comment. +- % of issues that have a clear priority within 24h. +- Engineer-reported "I always know what the top 5 things are" score (qualitative, from state file reviews). +- Number of duplicates caught before two people start working on them. + +See also: [Daily Triage](../daily-triage.md) (this loop is a feeder), [Multi-Loop Coordination](../docs/multi-loop.md), and the [Loop Design Checklist](../../docs/loop-design-checklist.md). diff --git a/patterns/registry.yaml b/patterns/registry.yaml index bd1c12d..072c202 100644 --- a/patterns/registry.yaml +++ b/patterns/registry.yaml @@ -126,4 +126,25 @@ patterns: tokens_report: 35000 tokens_action: 80000 suggested_daily_cap: 100000 - early_exit_required: false \ No newline at end of file + early_exit_required: false + + - id: issue-triage + name: Issue Triage + file: issue-triage.md + goal: Discover, deduplicate, prioritize and label incoming issues/discussions so the team always has a clean actionable queue. Excellent low-risk companion to Daily Triage. + cadence: 2h-1d + risk: low + tools: [grok, claude-code, codex, github-actions] + skills: [issue-triage, loop-verifier] + state: issue-triage-state.md + phases: [discover, dedupe, score, propose-labels, human-review] + human_gates: [security, p0-p1, ambiguous-duplicates, stale-closures] + starter: starters/minimal-loop + week_one_mode: L1 + token_cost: low + cost: + tokens_noop: 3000 + tokens_report: 30000 + tokens_action: 60000 + suggested_daily_cap: 80000 + early_exit_required: false diff --git a/tools/loop-audit/README.md b/tools/loop-audit/README.md index d6f8d97..ff13e11 100644 --- a/tools/loop-audit/README.md +++ b/tools/loop-audit/README.md @@ -2,6 +2,8 @@ CLI that scores a project's **Loop Readiness** (0–100) and suggests next steps. +**npx @cobusgreyling/loop-audit . --suggest** works immediately (published package). + ## Install & Run **npm (recommended):** @@ -34,7 +36,7 @@ bash scripts/before-after-demo.sh loop-audit . # human-readable (default) loop-audit . --json # machine-readable loop-audit . --md # markdown report -loop-audit . --suggest # copy-from-template commands (all tools) +loop-audit . --suggest # copy-from-template commands + activity tips (all tools) ``` Exit code `2` if score < 40 (useful for CI gates once your project is loop-ready). @@ -49,7 +51,7 @@ npm run build npm publish --access public ``` -## Signals Checked (v1.2+) +## Signals Checked (v1.4+) | Signal | Notes | |-------------------------|-------| @@ -67,8 +69,9 @@ npm publish --access public | loop-run-log.md | Append-only run history | | LOOP.md budget section | Cadence limits documented in config | | loop-budget skill | Runtime budget guard | +| **loopActivity (v1.4)** | **Dynamic proof**: "Last run" timestamps in state, loop-related git commits, scheduled workflows, run logs | -L3 requires budget doc + run log + LOOP.md budget section (in addition to verifier + state). +L3 requires verifier + state + cost observability (budget + run log + LOOP.md budget) **and** proven loop activity (not just files on disk). ## Levels diff --git a/tools/loop-audit/dist/auditor.d.ts b/tools/loop-audit/dist/auditor.d.ts index a0b34e1..a5ebb7d 100644 --- a/tools/loop-audit/dist/auditor.d.ts +++ b/tools/loop-audit/dist/auditor.d.ts @@ -49,6 +49,10 @@ export interface LoopSignals { loopMdBudget: boolean; budgetSkill: boolean; }; + loopActivity: { + present: boolean; + evidence: string[]; + }; } export interface Finding { level: 'ok' | 'warn' | 'fail'; diff --git a/tools/loop-audit/dist/auditor.js b/tools/loop-audit/dist/auditor.js index 326f202..3cb57b6 100644 --- a/tools/loop-audit/dist/auditor.js +++ b/tools/loop-audit/dist/auditor.js @@ -1,5 +1,6 @@ import { readdir, readFile, stat } from 'node:fs/promises'; import path from 'node:path'; +import { execSync } from 'node:child_process'; const STATE_FILES = [ 'STATE.md', 'pr-babysitter-state.md', @@ -7,6 +8,7 @@ const STATE_FILES = [ 'post-merge-state.md', 'dependency-sweeper-state.md', 'changelog-drafter-state.md', + 'issue-triage-state.md', ]; const LOOP_SKILL_NAMES = [ 'loop-triage', @@ -19,6 +21,7 @@ const LOOP_SKILL_NAMES = [ 'rebase-and-clean', 'changelog-scan', 'draft-release-notes', + 'issue-triage', ]; const SAFETY_FILES = ['safety.md', 'docs/safety.md', 'SECURITY.md']; const MCP_FILES = ['.mcp.json', 'mcp.json', '.mcp/config.json']; @@ -72,6 +75,73 @@ async function findSkills(root) { } return found; } +async function detectLoopActivity(root) { + const evidence = []; + const stateCandidates = [...STATE_FILES, 'STATE.md']; + // 1. Look for "Last run" timestamps or dated entries inside state files (strong real-usage signal) + for (const sf of stateCandidates) { + try { + const p = path.join(root, sf); + if (await fileExists(p)) { + const txt = await readFile(p, 'utf8'); + if (/last\s*run|last updated|^\s*-\s*\d{4}-\d{2}-\d{2}/im.test(txt) || /triage|loop run|changelog drafter/i.test(txt)) { + evidence.push(`state:${sf}`); + } + } + } + catch { } + } + // 2. Presence of run log artifacts or dedicated log templates being used + const logHints = ['loop-run-log', 'run-log', 'loop.log', 'audit-report']; + try { + const entries = await readdir(root, { withFileTypes: true }); + for (const e of entries) { + if (e.isFile() && logHints.some(h => e.name.toLowerCase().includes(h))) { + evidence.push(`log:${e.name}`); + } + } + } + catch { } + // 3. Workflow or LOOP evidence of scheduled execution + try { + const wfDir = path.join(root, '.github', 'workflows'); + if (await fileExists(wfDir)) { + const wfs = await readdir(wfDir); + if (wfs.some(w => /triage|changelog|daily|loop|audit|pr-babysit/i.test(w))) { + evidence.push('github:loop-workflows'); + } + } + } + catch { } + // 4. Light git history scan for loop-related commits (best dynamic proof) + try { + const log = execSync('git log --oneline -25 -- .', { + cwd: root, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + timeout: 1500, + }); + const lower = log.toLowerCase(); + if (/state\.md|loop| t riage |changelog-drafter|post-merge|daily triage|audit/i.test(lower)) { + const firstMatch = log.trim().split('\n')[0] || ''; + evidence.push(`git:${firstMatch.slice(0, 60)}`); + } + } + catch { + // git not available or not a repo — ignore gracefully + } + // 5. Check LOOP.md or a state for explicit "Last run" human-readable proof + try { + const loopP = path.join(root, 'LOOP.md'); + if (await fileExists(loopP)) { + const txt = await readFile(loopP, 'utf8'); + if (/last run|cadence|scheduled|automation/i.test(txt)) + evidence.push('LOOP.md:active'); + } + } + catch { } + return { present: evidence.length > 0, evidence: Array.from(new Set(evidence)).slice(0, 4) }; +} export function computeScore(signals) { let score = 10; if (signals.stateFile.present) @@ -110,12 +180,16 @@ export function computeScore(signals) { score += 2; if (signals.cost.budgetSkill) score += 2; + if (signals.loopActivity.present) + score += 6; score = Math.min(100, Math.max(0, score)); const costReady = signals.cost.budgetDoc && signals.cost.runLog && signals.cost.loopMdBudget; + const hasRealActivity = signals.loopActivity.present; + const l3Ready = costReady && hasRealActivity; let level = 'L0'; - if (score >= 78 && signals.verifier.present && signals.stateFile.present && costReady) + if (score >= 78 && signals.verifier.present && signals.stateFile.present && l3Ready) level = 'L3'; else if (score >= 58 && signals.triage.present) level = 'L2'; @@ -123,15 +197,17 @@ export function computeScore(signals) { level = 'L1'; else level = 'L0'; - const assessment = score >= 82 && costReady + const assessment = score >= 82 && l3Ready ? 'Strong loop readiness — good candidate for L3 with explicit gates.' : score >= 82 && !costReady ? 'Strong signals but missing cost observability (loop-budget.md, loop-run-log.md, LOOP.md budget) — add before L3.' - : score >= 62 - ? 'Good foundation — add missing verifier + safety docs for L3.' - : score >= 42 - ? 'Early loop setup — focus on L1 state + triage before enabling actions.' - : 'Not loop-ready — start with a starter from this repo (minimal-loop or pr-babysitter).'; + : score >= 82 && !hasRealActivity + ? 'Strong structure but no proven loop runs yet — run one L1 cycle and commit state before L3.' + : score >= 62 + ? 'Good foundation — add missing verifier + safety docs for L3.' + : score >= 42 + ? 'Early loop setup — focus on L1 state + triage before enabling actions.' + : 'Not loop-ready — start with a starter from this repo (minimal-loop or pr-babysitter).'; return { score, level, assessment }; } export async function auditProject(target) { @@ -154,7 +230,8 @@ export async function auditProject(target) { skillNames.includes('ci-triage') || skillNames.includes('dependency-triage') || skillNames.includes('post-merge-scan') || - skillNames.includes('changelog-scan'); + skillNames.includes('changelog-scan') || + skillNames.includes('issue-triage'); let loopMdContent = ''; if (loopMd) { loopMdContent = await readFile(path.join(root, 'LOOP.md'), 'utf8'); @@ -215,6 +292,7 @@ export async function auditProject(target) { break; } } + const loopActivity = await detectLoopActivity(root); const signals = { stateFile: { present: statePaths.length > 0, paths: statePaths }, loopConfig: { present: loopMd, path: loopMd ? 'LOOP.md' : undefined }, @@ -230,6 +308,7 @@ export async function auditProject(target) { worktreeEvidence: { present: worktreeEvidence }, registry: { present: registryPresent }, cost: { budgetDoc, runLog, loopMdBudget, budgetSkill }, + loopActivity, }; if (!signals.stateFile.present) { findings.push({ level: 'fail', message: 'No state file (STATE.md or pattern-specific state).' }); @@ -319,6 +398,13 @@ export async function auditProject(target) { else { findings.push({ level: 'ok', message: 'loop-budget skill present.' }); } + if (!signals.loopActivity.present) { + findings.push({ level: 'warn', message: 'No evidence of actual loop runs detected (no "Last run" entries in state, loop-related git activity, or scheduled workflows yet).' }); + recommendations.push('Run one loop (report-only), update + commit STATE.md (or pattern state). This turns structure into proven usage.'); + } + else { + findings.push({ level: 'ok', message: `Loop activity detected — real usage signals present (${signals.loopActivity.evidence.length} sources).` }); + } const { score, level, assessment } = computeScore(signals); const costReady = signals.cost.budgetDoc && signals.cost.runLog && @@ -329,6 +415,12 @@ export async function auditProject(target) { message: 'Score qualifies for L3 but cost observability is incomplete — capped at L2 until budget + run log + LOOP.md budget exist.', }); } + if (score >= 78 && signals.verifier.present && signals.stateFile.present && costReady && !signals.loopActivity.present) { + findings.push({ + level: 'warn', + message: 'Score qualifies for L3 but no proven loop activity yet — capped at L2 until you run and commit at least one loop cycle.', + }); + } return { target: root, score, diff --git a/tools/loop-audit/dist/cli.js b/tools/loop-audit/dist/cli.js index 4dc76cf..6134c0b 100644 --- a/tools/loop-audit/dist/cli.js +++ b/tools/loop-audit/dist/cli.js @@ -8,7 +8,7 @@ const md = args.includes('--md'); const suggest = args.includes('--suggest') || args.includes('--fix'); const help = args.includes('--help') || args.includes('-h'); if (help) { - console.log(`loop-audit — Loop Readiness Score CLI (v1.1+) + console.log(`loop-audit — Loop Readiness Score CLI (v1.4+) Usage: loop-audit [path] [options] @@ -19,14 +19,20 @@ Options: --suggest Show copy-from-template commands for missing pieces (recommended on first runs) --help, -h This help +New in v1.4: + • Dynamic "loop activity" detection (git history, "Last run" in STATE, scheduled workflows) + • Higher L3 bar requires proven usage, not just files + • Stronger recommendations when structure exists but no runs yet + Exit codes: 0 score >= 40 2 score < 40 (early stage or gate) Examples: loop-audit . - loop-audit starters/minimal-loop --suggest + loop-audit . --suggest npx @cobusgreyling/loop-audit . --json + npx @cobusgreyling/loop-audit starters/minimal-loop --suggest bash scripts/before-after-demo.sh `); process.exit(0); @@ -73,6 +79,9 @@ try { console.log(' npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok'); console.log(' npx @cobusgreyling/loop-cost --pattern daily-triage --level L1'); console.log(''); + console.log(' # IMPORTANT (v1.4): After scaffolding, actually RUN a loop (report-only) and commit the updated STATE.md.'); + console.log(' # This creates the "loopActivity" evidence that pushes you toward real L2/L3 scores.'); + console.log(''); console.log('See docs/loop-design-checklist.md and patterns/ for full guidance.'); } if (result.score < 40) diff --git a/tools/loop-audit/package.json b/tools/loop-audit/package.json index 1a8e98a..987b4bf 100644 --- a/tools/loop-audit/package.json +++ b/tools/loop-audit/package.json @@ -1,7 +1,7 @@ { "name": "@cobusgreyling/loop-audit", - "version": "1.3.0", - "description": "Loop Readiness Score — audit a project for loop engineering readiness (L0-L3). Supports --suggest for copy commands.", + "version": "1.4.0", + "description": "Loop Readiness Score — audit a project for loop engineering readiness (L0-L3). Dynamic activity detection + --suggest. npx @cobusgreyling/loop-audit . --suggest", "type": "module", "bin": { "loop-audit": "./dist/cli.js" diff --git a/tools/loop-audit/src/auditor.ts b/tools/loop-audit/src/auditor.ts index 015efc3..52f178d 100644 --- a/tools/loop-audit/src/auditor.ts +++ b/tools/loop-audit/src/auditor.ts @@ -1,5 +1,6 @@ import { readdir, readFile, stat } from 'node:fs/promises'; import path from 'node:path'; +import { execSync } from 'node:child_process'; export interface LoopSignals { stateFile: { present: boolean; paths: string[] }; @@ -21,6 +22,7 @@ export interface LoopSignals { loopMdBudget: boolean; budgetSkill: boolean; }; + loopActivity: { present: boolean; evidence: string[] }; } export interface Finding { @@ -45,6 +47,7 @@ const STATE_FILES = [ 'post-merge-state.md', 'dependency-sweeper-state.md', 'changelog-drafter-state.md', + 'issue-triage-state.md', ]; const LOOP_SKILL_NAMES = [ @@ -58,6 +61,7 @@ const LOOP_SKILL_NAMES = [ 'rebase-and-clean', 'changelog-scan', 'draft-release-notes', + 'issue-triage', ]; const SAFETY_FILES = ['safety.md', 'docs/safety.md', 'SECURITY.md']; @@ -111,6 +115,74 @@ async function findSkills(root: string): Promise { return found; } +async function detectLoopActivity(root: string): Promise<{ present: boolean; evidence: string[] }> { + const evidence: string[] = []; + const stateCandidates = [...STATE_FILES, 'STATE.md']; + + // 1. Look for "Last run" timestamps or dated entries inside state files (strong real-usage signal) + for (const sf of stateCandidates) { + try { + const p = path.join(root, sf); + if (await fileExists(p)) { + const txt = await readFile(p, 'utf8'); + if (/last\s*run|last updated|^\s*-\s*\d{4}-\d{2}-\d{2}/im.test(txt) || /triage|loop run|changelog drafter/i.test(txt)) { + evidence.push(`state:${sf}`); + } + } + } catch {} + } + + // 2. Presence of run log artifacts or dedicated log templates being used + const logHints = ['loop-run-log', 'run-log', 'loop.log', 'audit-report']; + try { + const entries = await readdir(root, { withFileTypes: true }); + for (const e of entries) { + if (e.isFile() && logHints.some(h => e.name.toLowerCase().includes(h))) { + evidence.push(`log:${e.name}`); + } + } + } catch {} + + // 3. Workflow or LOOP evidence of scheduled execution + try { + const wfDir = path.join(root, '.github', 'workflows'); + if (await fileExists(wfDir)) { + const wfs = await readdir(wfDir); + if (wfs.some(w => /triage|changelog|daily|loop|audit|pr-babysit/i.test(w))) { + evidence.push('github:loop-workflows'); + } + } + } catch {} + + // 4. Light git history scan for loop-related commits (best dynamic proof) + try { + const log = execSync('git log --oneline -25 -- .', { + cwd: root, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + timeout: 1500, + }); + const lower = log.toLowerCase(); + if (/state\.md|loop| t riage |changelog-drafter|post-merge|daily triage|audit/i.test(lower)) { + const firstMatch = log.trim().split('\n')[0] || ''; + evidence.push(`git:${firstMatch.slice(0, 60)}`); + } + } catch { + // git not available or not a repo — ignore gracefully + } + + // 5. Check LOOP.md or a state for explicit "Last run" human-readable proof + try { + const loopP = path.join(root, 'LOOP.md'); + if (await fileExists(loopP)) { + const txt = await readFile(loopP, 'utf8'); + if (/last run|cadence|scheduled|automation/i.test(txt)) evidence.push('LOOP.md:active'); + } + } catch {} + + return { present: evidence.length > 0, evidence: Array.from(new Set(evidence)).slice(0, 4) }; +} + export function computeScore(signals: LoopSignals): { score: number; level: 'L0' | 'L1' | 'L2' | 'L3'; assessment: string } { let score = 10; @@ -132,6 +204,7 @@ export function computeScore(signals: LoopSignals): { score: number; level: 'L0' if (signals.cost.runLog) score += 3; if (signals.cost.loopMdBudget) score += 2; if (signals.cost.budgetSkill) score += 2; + if (signals.loopActivity.present) score += 6; score = Math.min(100, Math.max(0, score)); @@ -139,23 +212,27 @@ export function computeScore(signals: LoopSignals): { score: number; level: 'L0' signals.cost.budgetDoc && signals.cost.runLog && signals.cost.loopMdBudget; + const hasRealActivity = signals.loopActivity.present; + const l3Ready = costReady && hasRealActivity; let level: 'L0' | 'L1' | 'L2' | 'L3' = 'L0'; - if (score >= 78 && signals.verifier.present && signals.stateFile.present && costReady) level = 'L3'; + if (score >= 78 && signals.verifier.present && signals.stateFile.present && l3Ready) level = 'L3'; else if (score >= 58 && signals.triage.present) level = 'L2'; else if (score >= 38 && signals.stateFile.present) level = 'L1'; else level = 'L0'; const assessment = - score >= 82 && costReady + score >= 82 && l3Ready ? 'Strong loop readiness — good candidate for L3 with explicit gates.' : score >= 82 && !costReady ? 'Strong signals but missing cost observability (loop-budget.md, loop-run-log.md, LOOP.md budget) — add before L3.' - : score >= 62 - ? 'Good foundation — add missing verifier + safety docs for L3.' - : score >= 42 - ? 'Early loop setup — focus on L1 state + triage before enabling actions.' - : 'Not loop-ready — start with a starter from this repo (minimal-loop or pr-babysitter).'; + : score >= 82 && !hasRealActivity + ? 'Strong structure but no proven loop runs yet — run one L1 cycle and commit state before L3.' + : score >= 62 + ? 'Good foundation — add missing verifier + safety docs for L3.' + : score >= 42 + ? 'Early loop setup — focus on L1 state + triage before enabling actions.' + : 'Not loop-ready — start with a starter from this repo (minimal-loop or pr-babysitter).'; return { score, level, assessment }; } @@ -183,7 +260,8 @@ export async function auditProject(target: string): Promise { skillNames.includes('ci-triage') || skillNames.includes('dependency-triage') || skillNames.includes('post-merge-scan') || - skillNames.includes('changelog-scan'); + skillNames.includes('changelog-scan') || + skillNames.includes('issue-triage'); let loopMdContent = ''; if (loopMd) { @@ -246,6 +324,8 @@ export async function auditProject(target: string): Promise { } } + const loopActivity = await detectLoopActivity(root); + const signals: LoopSignals = { stateFile: { present: statePaths.length > 0, paths: statePaths }, loopConfig: { present: loopMd, path: loopMd ? 'LOOP.md' : undefined }, @@ -261,6 +341,7 @@ export async function auditProject(target: string): Promise { worktreeEvidence: { present: worktreeEvidence }, registry: { present: registryPresent }, cost: { budgetDoc, runLog, loopMdBudget, budgetSkill }, + loopActivity, }; if (!signals.stateFile.present) { @@ -357,6 +438,13 @@ export async function auditProject(target: string): Promise { findings.push({ level: 'ok', message: 'loop-budget skill present.' }); } + if (!signals.loopActivity.present) { + findings.push({ level: 'warn', message: 'No evidence of actual loop runs detected (no "Last run" entries in state, loop-related git activity, or scheduled workflows yet).' }); + recommendations.push('Run one loop (report-only), update + commit STATE.md (or pattern state). This turns structure into proven usage.'); + } else { + findings.push({ level: 'ok', message: `Loop activity detected — real usage signals present (${signals.loopActivity.evidence.length} sources).` }); + } + const { score, level, assessment } = computeScore(signals); const costReady = @@ -371,6 +459,13 @@ export async function auditProject(target: string): Promise { }); } + if (score >= 78 && signals.verifier.present && signals.stateFile.present && costReady && !signals.loopActivity.present) { + findings.push({ + level: 'warn', + message: 'Score qualifies for L3 but no proven loop activity yet — capped at L2 until you run and commit at least one loop cycle.', + }); + } + return { target: root, score, diff --git a/tools/loop-audit/src/cli.ts b/tools/loop-audit/src/cli.ts index 4541200..0370df2 100644 --- a/tools/loop-audit/src/cli.ts +++ b/tools/loop-audit/src/cli.ts @@ -10,7 +10,7 @@ const suggest = args.includes('--suggest') || args.includes('--fix'); const help = args.includes('--help') || args.includes('-h'); if (help) { - console.log(`loop-audit — Loop Readiness Score CLI (v1.1+) + console.log(`loop-audit — Loop Readiness Score CLI (v1.4+) Usage: loop-audit [path] [options] @@ -21,14 +21,20 @@ Options: --suggest Show copy-from-template commands for missing pieces (recommended on first runs) --help, -h This help +New in v1.4: + • Dynamic "loop activity" detection (git history, "Last run" in STATE, scheduled workflows) + • Higher L3 bar requires proven usage, not just files + • Stronger recommendations when structure exists but no runs yet + Exit codes: 0 score >= 40 2 score < 40 (early stage or gate) Examples: loop-audit . - loop-audit starters/minimal-loop --suggest + loop-audit . --suggest npx @cobusgreyling/loop-audit . --json + npx @cobusgreyling/loop-audit starters/minimal-loop --suggest bash scripts/before-after-demo.sh `); process.exit(0); @@ -74,6 +80,9 @@ try { console.log(' npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok'); console.log(' npx @cobusgreyling/loop-cost --pattern daily-triage --level L1'); console.log(''); + console.log(' # IMPORTANT (v1.4): After scaffolding, actually RUN a loop (report-only) and commit the updated STATE.md.'); + console.log(' # This creates the "loopActivity" evidence that pushes you toward real L2/L3 scores.'); + console.log(''); console.log('See docs/loop-design-checklist.md and patterns/ for full guidance.'); } diff --git a/tools/loop-audit/test/auditor.test.mjs b/tools/loop-audit/test/auditor.test.mjs index 6a3f3c7..c20e5f7 100644 --- a/tools/loop-audit/test/auditor.test.mjs +++ b/tools/loop-audit/test/auditor.test.mjs @@ -21,6 +21,7 @@ function emptySignals() { worktreeEvidence: { present: false }, registry: { present: false }, cost: { budgetDoc: false, runLog: false, loopMdBudget: false, budgetSkill: false }, + loopActivity: { present: false, evidence: [] }, }; } @@ -50,7 +51,7 @@ test('computeScore: full L2 signals', () => { assert.ok(score >= 58 && score < 78); }); -test('computeScore: L3 requires verifier and high score', () => { +test('computeScore: L3 requires verifier, high score, cost observability, and activity', () => { const s = emptySignals(); s.stateFile = { present: true, paths: ['STATE.md'] }; s.triage = { present: true }; @@ -64,6 +65,7 @@ test('computeScore: L3 requires verifier and high score', () => { s.worktreeEvidence = { present: true }; s.registry = { present: true }; s.cost = { budgetDoc: true, runLog: true, loopMdBudget: true, budgetSkill: true }; + s.loopActivity = { present: true, evidence: ['git:state update', 'state:STATE.md'] }; const { level, score } = computeScore(s); assert.equal(level, 'L3'); assert.ok(score >= 78); @@ -82,6 +84,23 @@ test('computeScore: L3 blocked without cost observability', () => { s.mcp = { present: true }; s.worktreeEvidence = { present: true }; s.registry = { present: true }; + s.loopActivity = { present: true, evidence: ['state:STATE.md'] }; + const { level } = computeScore(s); + assert.equal(level, 'L2'); +}); + +test('computeScore: high structure without activity caps at L2', () => { + const s = emptySignals(); + s.stateFile = { present: true, paths: ['STATE.md'] }; + s.triage = { present: true }; + s.loopConfig = { present: true, path: 'LOOP.md' }; + s.agentsMd = { present: true }; + s.skills = { count: 3, loopSkills: ['loop-triage', 'minimal-fix', 'loop-verifier'] }; + s.verifier = { present: true }; + s.safety = { loopMdMentionsSafety: true, safetyDocPresent: true }; + s.github = { present: true, workflows: true }; + s.registry = { present: true }; + s.cost = { budgetDoc: true, runLog: true, loopMdBudget: true, budgetSkill: true }; const { level } = computeScore(s); assert.equal(level, 'L2'); }); diff --git a/tools/loop-cost/package.json b/tools/loop-cost/package.json index e9b6c09..9b39765 100644 --- a/tools/loop-cost/package.json +++ b/tools/loop-cost/package.json @@ -34,6 +34,10 @@ "url": "git+https://github.com/cobusgreyling/loop-engineering.git", "directory": "tools/loop-cost" }, + "homepage": "https://cobusgreyling.github.io/loop-engineering/", + "bugs": { + "url": "https://github.com/cobusgreyling/loop-engineering/issues" + }, "publishConfig": { "access": "public" }, diff --git a/tools/loop-cost/registry.json b/tools/loop-cost/registry.json index 53ac6e6..3ed5aa3 100644 --- a/tools/loop-cost/registry.json +++ b/tools/loop-cost/registry.json @@ -247,6 +247,48 @@ "suggested_daily_cap": 100000, "early_exit_required": false } + }, + { + "id": "issue-triage", + "name": "Issue Triage", + "file": "issue-triage.md", + "goal": "Discover, deduplicate, prioritize and label incoming issues/discussions so the team always has a clean actionable queue. Excellent low-risk companion to Daily Triage.", + "cadence": "2h-1d", + "risk": "low", + "tools": [ + "grok", + "claude-code", + "codex", + "github-actions" + ], + "skills": [ + "issue-triage", + "loop-verifier" + ], + "state": "issue-triage-state.md", + "phases": [ + "discover", + "dedupe", + "score", + "propose-labels", + "human-review" + ], + "human_gates": [ + "security", + "p0-p1", + "ambiguous-duplicates", + "stale-closures" + ], + "starter": "starters/minimal-loop", + "week_one_mode": "L1", + "token_cost": "low", + "cost": { + "tokens_noop": 3000, + "tokens_report": 30000, + "tokens_action": 60000, + "suggested_daily_cap": 80000, + "early_exit_required": false + } } ] } \ No newline at end of file diff --git a/tools/loop-init/README.md b/tools/loop-init/README.md index f0fa247..b08c566 100644 --- a/tools/loop-init/README.md +++ b/tools/loop-init/README.md @@ -2,6 +2,8 @@ Scaffold loop engineering starters into your project by pattern and tool. +**npx @cobusgreyling/loop-init . --pattern daily-triage --tool grok** works immediately. + ## Install & Run ```bash @@ -12,6 +14,8 @@ npx @cobusgreyling/loop-init . -p dependency-sweeper --dry-run See [docs/RELEASE.md](../../docs/RELEASE.md) for npm publish tags. The published package bundles `starters/` and `templates/` from this monorepo. +After scaffolding, always run `npx @cobusgreyling/loop-audit . --suggest` and actually execute the first report-only loop to generate activity signals. + ## Patterns | Pattern | Default state file | diff --git a/tools/loop-init/dist/cli.js b/tools/loop-init/dist/cli.js index a61b669..f190e08 100644 --- a/tools/loop-init/dist/cli.js +++ b/tools/loop-init/dist/cli.js @@ -13,6 +13,7 @@ const PATTERN_STARTERS = { 'dependency-sweeper': 'dependency-sweeper', 'post-merge-cleanup': 'post-merge-cleanup', 'changelog-drafter': 'changelog-drafter', + 'issue-triage': 'minimal-loop', // reuses daily-triage starter + new skill can be added manually or via templates later }; const TOOL_SUFFIX = { grok: '', @@ -33,6 +34,7 @@ const STATE_FILES = { 'dependency-sweeper': 'dependency-sweeper-state.md', 'post-merge-cleanup': 'post-merge-state.md', 'changelog-drafter': 'changelog-drafter-state.md', + 'issue-triage': 'issue-triage-state.md', }; /** Mirrors patterns/registry.yaml cost caps — used when scaffolding observability files. */ const PATTERN_BUDGET = { @@ -42,6 +44,7 @@ const PATTERN_BUDGET = { 'dependency-sweeper': { name: 'Dependency Sweeper', maxRunsPerDay: 4, dailyCap: 500_000, maxSpawnsL1: 0, maxSpawnsL2: 3 }, 'post-merge-cleanup': { name: 'Post-Merge Cleanup', maxRunsPerDay: 1, dailyCap: 200_000, maxSpawnsL1: 0, maxSpawnsL2: 2 }, 'changelog-drafter': { name: 'Changelog Drafter', maxRunsPerDay: 1, dailyCap: 100_000, maxSpawnsL1: 0, maxSpawnsL2: 2 }, + 'issue-triage': { name: 'Issue Triage', maxRunsPerDay: 12, dailyCap: 80_000, maxSpawnsL1: 0, maxSpawnsL2: 1 }, }; function parseArgs(argv) { let pattern = 'daily-triage'; @@ -235,6 +238,11 @@ function firstLoopCommand(pattern, tool) { claude: '/loop 1d $changelog-scan + draft-release-notes — write RELEASE_NOTES_DRAFT.md and update state. Human approves before publish.', codex: 'Automation daily: changelog-scan + draft-release-notes → RELEASE_NOTES_DRAFT.md. Human review.', }, + 'issue-triage': { + 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.', + claude: '/loop 2h $issue-triage — update issue-triage-state.md. Suggest labels on allowlisted areas only. Report mode week one.', + codex: 'Automation 2h: issue-triage → issue-triage-state.md. Propose only.', + }, }; return cmds[pattern][tool]; } @@ -253,6 +261,7 @@ Patterns: dependency-sweeper post-merge-cleanup changelog-drafter (new low-risk release notes pattern) + issue-triage (new low-risk issue queue health companion to daily triage) Options: -p, --pattern Pattern to scaffold diff --git a/tools/loop-init/package.json b/tools/loop-init/package.json index 5a15c1c..85e175a 100644 --- a/tools/loop-init/package.json +++ b/tools/loop-init/package.json @@ -1,6 +1,6 @@ { "name": "@cobusgreyling/loop-init", - "version": "1.1.0", + "version": "1.2.0", "description": "Scaffold loop engineering starters into your project by pattern and tool.", "type": "module", "bin": { @@ -35,6 +35,10 @@ "url": "git+https://github.com/cobusgreyling/loop-engineering.git", "directory": "tools/loop-init" }, + "homepage": "https://cobusgreyling.github.io/loop-engineering/", + "bugs": { + "url": "https://github.com/cobusgreyling/loop-engineering/issues" + }, "publishConfig": { "access": "public" }, diff --git a/tools/loop-init/registry.yaml b/tools/loop-init/registry.yaml index bd1c12d..072c202 100644 --- a/tools/loop-init/registry.yaml +++ b/tools/loop-init/registry.yaml @@ -126,4 +126,25 @@ patterns: tokens_report: 35000 tokens_action: 80000 suggested_daily_cap: 100000 - early_exit_required: false \ No newline at end of file + early_exit_required: false + + - id: issue-triage + name: Issue Triage + file: issue-triage.md + goal: Discover, deduplicate, prioritize and label incoming issues/discussions so the team always has a clean actionable queue. Excellent low-risk companion to Daily Triage. + cadence: 2h-1d + risk: low + tools: [grok, claude-code, codex, github-actions] + skills: [issue-triage, loop-verifier] + state: issue-triage-state.md + phases: [discover, dedupe, score, propose-labels, human-review] + human_gates: [security, p0-p1, ambiguous-duplicates, stale-closures] + starter: starters/minimal-loop + week_one_mode: L1 + token_cost: low + cost: + tokens_noop: 3000 + tokens_report: 30000 + tokens_action: 60000 + suggested_daily_cap: 80000 + early_exit_required: false diff --git a/tools/loop-init/src/cli.ts b/tools/loop-init/src/cli.ts index 4abaefa..b73d747 100644 --- a/tools/loop-init/src/cli.ts +++ b/tools/loop-init/src/cli.ts @@ -14,7 +14,8 @@ type Pattern = | 'ci-sweeper' | 'dependency-sweeper' | 'post-merge-cleanup' - | 'changelog-drafter'; + | 'changelog-drafter' + | 'issue-triage'; type Tool = 'grok' | 'claude' | 'codex'; @@ -25,6 +26,7 @@ const PATTERN_STARTERS: Record = { 'dependency-sweeper': 'dependency-sweeper', 'post-merge-cleanup': 'post-merge-cleanup', 'changelog-drafter': 'changelog-drafter', + 'issue-triage': 'minimal-loop', // reuses daily-triage starter + new skill can be added manually or via templates later }; const TOOL_SUFFIX: Record = { @@ -49,6 +51,7 @@ const STATE_FILES: Record = { 'dependency-sweeper': 'dependency-sweeper-state.md', 'post-merge-cleanup': 'post-merge-state.md', 'changelog-drafter': 'changelog-drafter-state.md', + 'issue-triage': 'issue-triage-state.md', }; /** Mirrors patterns/registry.yaml cost caps — used when scaffolding observability files. */ @@ -62,6 +65,7 @@ const PATTERN_BUDGET: Record< 'dependency-sweeper': { name: 'Dependency Sweeper', maxRunsPerDay: 4, dailyCap: 500_000, maxSpawnsL1: 0, maxSpawnsL2: 3 }, 'post-merge-cleanup': { name: 'Post-Merge Cleanup', maxRunsPerDay: 1, dailyCap: 200_000, maxSpawnsL1: 0, maxSpawnsL2: 2 }, 'changelog-drafter': { name: 'Changelog Drafter', maxRunsPerDay: 1, dailyCap: 100_000, maxSpawnsL1: 0, maxSpawnsL2: 2 }, + 'issue-triage': { name: 'Issue Triage', maxRunsPerDay: 12, dailyCap: 80_000, maxSpawnsL1: 0, maxSpawnsL2: 1 }, }; function parseArgs(argv: string[]) { @@ -285,6 +289,11 @@ function firstLoopCommand(pattern: Pattern, tool: Tool): string { claude: '/loop 1d $changelog-scan + draft-release-notes — write RELEASE_NOTES_DRAFT.md and update state. Human approves before publish.', codex: 'Automation daily: changelog-scan + draft-release-notes → RELEASE_NOTES_DRAFT.md. Human review.', }, + 'issue-triage': { + 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.', + claude: '/loop 2h $issue-triage — update issue-triage-state.md. Suggest labels on allowlisted areas only. Report mode week one.', + codex: 'Automation 2h: issue-triage → issue-triage-state.md. Propose only.', + }, }; return cmds[pattern][tool]; } @@ -305,6 +314,7 @@ Patterns: dependency-sweeper post-merge-cleanup changelog-drafter (new low-risk release notes pattern) + issue-triage (new low-risk issue queue health companion to daily triage) Options: -p, --pattern Pattern to scaffold