feat(loop): declarative domain loops — @decocms/loop CLI + domain-lint skill#5184
feat(loop): declarative domain loops — @decocms/loop CLI + domain-lint skill#5184viktormarinho wants to merge 3 commits into
Conversation
…t skill Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gh calls, harden error paths - bin now points at bun-bundled dist/cli.js (inlines @decocms/shared/std; no runtime deps; engines: bun) — publishable standalone - one batched 'gh pr list' for all domains (statusUi was 1 call/domain/15s) - run() lock reuses the same query; cleanup in finally no longer throws - tick survives a failing domain; TUI survives a failing gh refresh - domain names validated as safe path/branch components - DOMAINS.md parser extracted pure + unit tests; owner anchored to last cell - plist values XML-escaped; cron on|off tolerates already-on/off - README: Bun requirement + security model (merged DOMAIN.md = capability grant) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Applied the /review-pr remediation (7 parallel critics, findings deduped and verified — 5 false positives rejected after checking against the actual diff/behavior). Notable: |
|
Compared this against our studio-bot (the maintenance-PR bot that's been running on a cron for a while now, ~81% merge rate over 220+ PRs). The declaration + red-team lint idea is genuinely good — better than what we have — and using GitHub as the only state is leaner than our JSONL store. A few suggestions ranked by how hard we learned each lesson running ours: 1. Don't let the agent open the PR — the CLI should, after deterministic checks. The reconcile prompt says "stay strictly inside the declared scope" and "0 or 1 PR", but enforcement is prose: the agent runs with 2. The lock has a gap, and crashed runs leak "running" state forever. The open-PR lock only engages after the agent opens a PR; a run takes 10–30 min, so a second 3. One failing domain starves the rest of 4. The Tools section claims to be "a permission boundary" — it isn't. With 5. Surface unprocessed rejections in 6. Three copies of SKILL.md. 1–3 are the ones I'd actually push on before this runs unattended; 4–6 take or leave. |
bun stays a build-time tool only: build.ts bundles to dist/cli.js with --target node and a node shebang. Verified under plain node v23. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Bootstrap for domain loops: operate repo domains declaratively — each domain has a versioned declaration (
domains/<name>/DOMAIN.md), a reconciler agent turns drift into small PRs, and a human owner reviews and refines the declaration. Kubernetes-style reconciliation applied to code:reconcile(domain) → 0 or 1 PR, state lives in git/GitHub, rejected PRs must become declaration edits.What's in the box
packages/loop(@decocms/loop) — zero-dep CLI:init— scaffolddomains/+ install thedomain-lintskill into the target repolint <domain>— semantic lint of a declaration: mechanical checks + red-team ("what useless PR does this text authorize?")run <domain>— one reconcile: open-PR lock (labelloop:<domain>), throwaway worktree off main,claude -p, fix ONE violation, PR or nothingtick— cron target: reconcile every domain you own (idempotent via the lock)status/status --ui— inbox: in-flight PRs (checks/review state), running agents, schedule state;--uiis a live-refreshing TUIcron setup|on|off— schedulestickon the OS scheduler (macOS launchd; survives sleep, bakes PATH into the job)src/reconcile-prompt.ts— versioned with the package, not installed anywhere.cursor/skills/domain-lint— the semantic-lint skill (3 passes: mechanical, red-team, authoring/restructuring)domains/DOMAINS.md+ template — the index; a merged row = a live domainapps/web/src/i18n/placeholder-parity.test.ts— unit test encoding the placeholder contract (pt-br placeholders ⊆ en placeholders); found and encodes the real{plural}case inorgs.members.connectionCountAlready proven in production
The pilot declaration (
domains/i18n/, shipped in #5170) went through 8 red-team rounds ofloop lint— each round found real holes (TODO-marker escape hatch, non-convergent audit loops, spec self-editing,.tstoasts invisible to.tsxgreps, deletablesatisfiesclauses). The first realloop run i18n:Testing
bun x tsc --noEmit -p packages/loop✓bun test apps/web/src/i18n✓ (7 tests, incl. the new parity test at its post-refactor path)bun run fmt/bun run lint/bun run knip✓ (no new warnings)status,status --ui,tick(no-op path),cron(read path),init, 8×lint, 3×run(stale-stop, lock-skip, full reconcile → fix(i18n): translate assignee picker strings #5170)Notes
domains/i18n/itself ships in fix(i18n): translate assignee picker strings #5170 (the reconciler's own PR) — no file overlap between the two PRs, either merge order works.cron setupis macOS-only for now (launchd); other platforms get a printed crontab suggestion.🤖 Generated with Claude Code
Summary by cubic
Introduce declarative domain loops with the
@decocms/loopCLI and adomain-lintskill to declare, lint, and reconcile repo domains via small, evidence-backed PRs. The CLI now ships as a single Node-target bundle (no runtime deps; Bun is build-time only).New Features
@decocms/loopCLI:init,lint,run,tick,status,status --ui,cron setup|on|off.src/reconcile-prompt.ts): fix one violation per run; opens 0–1 PR labeledloop:<domain>; uses a throwaway worktree off the default branch.domain-lintskill (installed byinit): mechanical checks, red‑team for loopholes, and authoring/restructuring.domains/DOMAINS.mdindex anddomains/DOMAIN.template.md.apps/web/src/i18n/placeholder-parity.test.tsenforces pt‑br placeholders ⊆ en.DOMAINS.mdparsing anchored to the last cell with unit tests; cron plist XML-escaped and on/off idempotent.Migration
npx @decocms/loop init(orbunx @decocms/loop init), copydomains/DOMAIN.template.mdtodomains/<name>/DOMAIN.md, and fill it in.npx @decocms/loop lint <name>to tighten the declaration; thennpx @decocms/loop run <name>to reconcile.loop cron setup [minutes](macOS only) or add your own crontab/systemd entry callingloop tick.Written for commit 402348d. Summary will update on new commits.