{"slug": "show-hn-lazy-clean-keep-coding-agents-from-overengineering", "title": "Show HN: Lazy-clean Keep coding agents from overengineering", "summary": "Developer JustasMonkev released lazy-clean, a standalone skill package for coding agents that injects a YAGNI-based \"lazy-senior-dev\" ruleset at session and subagent start and runs a heuristic slop checker on every TypeScript/JavaScript file Claude writes or edits. The package ships five wiring tiers, with only the Claude Code and Codex tiers running the checker automatically via hooks/lazy-clean.json; OpenCode, Cursor, Copilot, and AGENTS.md readers get the ruleset without automation. The rulesets cover TypeScript, JavaScript, Java, Python, Ruby, Rust, and Go, while the bundled zero-dependency checker remains TS/JS-only and reads source text rather than invoking the compiler.", "body_md": "One skill package for coding agents: write the least code that works, then delete the slop that crept in anyway.\n\n- **While writing** — a lazy-senior-dev ruleset (YAGNI ladder, risk gate) is injected at session start, into subagents, and adjustable via`/lazy` commands.\n- **After writing** — a heuristic slop checker runs on every TS/JS file Claude writes or edits, and hands findings back as review context.\n\n(A renamed hard fork of two upstream projects; this package is standalone and self-contained.)\n\nFor upstream comparisons and selective ports, follow the\n[update procedure](https://github.com/JustasMonkev/lazy-clean/blob/main/skills/lazy-clean/references/upstream-updates.md) and record\nthe reviewed revisions and local differences in [UPSTREAM.md](https://github.com/JustasMonkev/lazy-clean/blob/main/UPSTREAM.md).\n\nSame ruleset, five levels of wiring — pick whatever your agent supports.\n\n| Tier | Platforms | What you get | Files | \n|---|---|---|---|\n| Full hooks | Claude Code, Codex | Compact ruleset injected at session and subagent start, `/lazy` level switching, slop-check auto-run after every Write/Edit | `hooks/lazy-clean.json` via`.claude-plugin/plugin.json` and`.codex-plugin/plugin.json` | \n| Plugin | OpenCode | Ruleset injected every turn plus eight slash commands | `.opencode/` +`opencode.json` +`hooks/` +`skills/` — the plugin loads the shared builder from`hooks/` , so copying only`.opencode/` gives you a plugin that fails to load | \n| Rules file | Cursor, Copilot | Always-on ruleset for all seven supported languages; run the TS/JS-only checker by hand after TS/JS changes | `.cursor/rules/lazy-clean.mdc` ,`.github/copilot-instructions.md` | \n| `AGENTS.md` | Everything else that reads it — Codex, Zed, Amp, Jules | The compact ruleset plus the post-edit checker step | `AGENTS.md` | \n| Skills only | Anything that reads `~/.claude/skills` | Every skill on demand, no automation | `skills/` | \n\nOnly the Claude Code and Codex tiers run the checker automatically. Everywhere else run it yourself, from wherever the skills live — `node ~/.claude/skills/slop-check/scripts/check.mjs <changed files>` after the skills-only install below, or `node skills/slop-check/scripts/check.mjs <changed files>` from a checkout.\n\nQoder and VS Code Copilot are detected by the hooks and get the right output shape, but neither is wired up here as its own tier. Upstream also ships adapters this fork skips (MCP server, pi extension, Hermes, Devin, openclaw). Add them from upstream if you need them.\n\nThe rulesets cover TypeScript, JavaScript, Java, Python, Ruby, Rust, and Go. The agent detects the ones your project actually uses, reads their pinned or installed versions, and keeps advice compatible with them. If a needed version fact cannot be checked, it says so instead of guessing; latest-release research is only used when current-version advice is requested.\n\nThe bundled checker reads source text rather than calling the compiler, so it\nworks the same on every TypeScript version, including TypeScript 7. It stays TS/JS-only; the other five languages get the manual review, because a zero-dependency scanner is not a parser.\nHTML is not scanned either: extract inline `<script>` code to a temporary `.js`\nor report a manual review. Zero files checked is no coverage, not a clean result.\n\nTypeScript/JavaScript and Python also get a pre-finish reference with before/after\nexamples: [TS/JS checks](https://github.com/JustasMonkev/lazy-clean/blob/main/skills/lazy/references/simplification-checks.md) cover\nTypeScript 6/7 (the native `tsc`: removed tsconfig options, new defaults, and the\nTypeScript 6 alias API-based tools still need), module shape, exports, import-time side effects, discriminated unions, and\nboundary parsing; [Python checks](https://github.com/JustasMonkev/lazy-clean/blob/main/skills/lazy/references/python-checks.md) cover\nversion gates, the project's configured linter and type checker, module shape,\ndataclasses and `Protocol`, and the idioms that silently change behavior\n(mutable defaults, `if not x`, bare `except:`). The rules files inline the key\npoints for agents that never open a reference.\n\nThe rulesets think before coding, state material assumptions when they matter, match existing style, cut only task-owned orphans, and define a verifiable goal. One-caller helpers stay when they carry real value (a domain name, tricky logic, an isolated side effect, test or readability value, or a framework contract). Non-trivial changed logic needs behavior, edge, and failure coverage; mutation evidence is optional when existing red-green checks already prove the risky regression, and never justifies a new dependency.\n\n`lazy-verify` is an explicitly invoked skill and OpenCode command, not a lazy\nintensity or hook. **Fix** checks for the approved assertion failure before and\na pass after. **Preserve** checks the same approved behavior on both snapshots.\nNeither a successful project command nor a clean static scan is assertion-level\nevidence; execution, behavior, supporting checks, gate and applicability are\nreported separately.\n\nThe dependency-free Node >=18 bridge is self-contained under\n[`skills/lazy-verify/`](https://github.com/JustasMonkev/lazy-clean/blob/main/skills/lazy-verify/SKILL.md), including skills-only copies.\nRules-only users can invoke `node /installed/skills/lazy-verify/scripts/verify.mjs`\ndirectly; a native slash command depends on the client. It needs an explicitly\nselected, reviewed local engine and policy. No engine is installed/downloaded\nautomatically. A separate approved Node executable can run a Node 24 engine\nwithout changing lazy-clean's runtime or private package status.\n\n**No real engine is qualified yet.** Bridge fixtures do not establish behavioral\ncorrectness or release readiness. Planned engine qualification is Linux/macOS\non Node 24; Windows target execution returns an explicit unsupported result.\nCore Node 18/22 and Windows checks remain unchanged. Existing usage works without\nverification installed; missing prerequisites cannot pass a required gate.\n\nUse explicit base/head selectors (`worktree` for actual uncommitted edits), a\nreviewed contract and `--trust-code`. Local execution is not a security sandbox.\n`report` renders recorded evidence without executing it; `replay` uses fresh\npolicy selection and saved exact inputs. Generated `.lazy-verify/runs/` output\nis private/ignored in this repository; contracts remain reviewable. See the\n[installed protocol reference](https://github.com/JustasMonkev/lazy-clean/blob/main/skills/lazy-verify/references/contract.md) for\ncommands, policy schema, limits and exits, and the\n[RFC](https://github.com/JustasMonkev/lazy-clean/blob/main/docs/specs/lazy-verify-integration.md) for engine/release acceptance gates.\n\nInvoke `/layz-test <files or change>` (Codex: `@layz-test`) to map behaviors,\nfind reproducible bugs, run applicable checks with existing tools, and add missing\nregression coverage.\nThe report ends with manual steps and why automation cannot settle each check;\nmissing dependencies or browsers are listed as blocked automation. Passing\nchecks apply only to the stated scope. This instruction-only skill adds no\nrunner or dependencies and does not change lazy mode.\n\nNo npm packages or plugin needed. Install with GitHub CLI to keep source metadata for future updates:\n\n```\ngh skill install JustasMonkev/lazy-clean --all --agent claude-code --scope user\n```\n\nFor Codex, use `--agent codex` instead. Omit `--scope user` for a project-local\ninstallation. GitHub CLI selects the latest tagged release, or the default\nbranch when there are no releases.\n\nCheck and apply updates to that installation:\n\n```\ngh skill update --dir ~/.claude/skills --dry-run \\\n  layz-test lazy lazy-audit lazy-clean lazy-debt lazy-gain lazy-help \\\n  lazy-review lazy-verify slop-check\ngh skill update --dir ~/.claude/skills \\\n  layz-test lazy lazy-audit lazy-clean lazy-debt lazy-gain lazy-help \\\n  lazy-review lazy-verify slop-check\n```\n\nFor user-scope Codex skills, use `--dir ~/.agents/skills`. For project-scope\nClaude Code use `--dir .claude/skills`; for project-scope Codex use\n`--dir .agents/skills`. These explicit directories also avoid scanning this\ncheckout's source `skills/` directory, which has no installation metadata.\n\nIf you previously copied these skills manually, back up any local edits, then reinstall once with metadata:\n\n```\ngh skill install JustasMonkev/lazy-clean --all --agent claude-code --scope user --force\n```\n\nThese commands install and update this repository's skills only.\n\nWithout GitHub CLI, manual copying still works, but does not enable\n`gh skill update`:\n\n```\ncp -R /path/to/lazy-clean/skills/* ~/.claude/skills/\n```\n\nThat gives you all 10 skills (`lazy-clean`, `lazy`, `lazy-review`, `lazy-audit`, `lazy-debt`, `lazy-gain`, `lazy-help`, `slop-check`, `lazy-verify`, `layz-test`). Claude picks them up by description or by `/lazy-clean` etc.; `lazy-help`, `lazy-gain`, and `lazy-verify` are slash-only in Claude Code (`disable-model-invocation`), so their descriptions cost no context. The checker script travels inside the `slop-check` skill and runs with plain `node` — zero dependencies.\n\nWhat you DON'T get in skills-only mode: the automatic parts (ruleset injected every session, checker auto-run after every edit). Those need the hooks — install as a plugin for that:\n\nLocal checkout:\n\n```\nclaude --plugin-dir /path/to/lazy-clean\n```\n\nOr add the checkout as a marketplace and enable it with `/plugin`:\n\n```\nclaude plugin marketplace add /path/to/lazy-clean\n```\n\nRequires `node` 18+ on `PATH`. No dependencies to install.\n\n| Event | What happens | \n|---|---|\n| `SessionStart` | startup/clear initialize the default; resume/compact restore the session level | \n| `SubagentStart` | same ruleset injected into the subagent, except read-only Explore agents | \n| `UserPromptSubmit` | `/lazy …` commands parsed, level flag updated | \n| `PostToolUse` on`Write` /`Edit` /`MultiEdit` | `skills/slop-check/scripts/check.mjs` runs on the edited file | \n\nEvery subagent except the read-only Explore agents gets the ruleset (about 2,000 tokens on each of its requests). To choose which `agent_type` s get it, set `LAZY_SUBAGENT_MATCHER` to a case-insensitive regex; `.` injects it into Explore too.\n\nThe checker only looks at `.ts .tsx .mts .cts .js .jsx .mjs .cjs`; anything else is skipped silently. Files written through `Bash` — heredocs, `sed -i`, codemods — are not seen by the hook at all; run the checker on those yourself.\n\nFindings are **advisory** — they arrive as `additionalContext`, never as a block, and the hook always exits 0. A failed checker run reports `check failed`; it does not claim the edit was clean. It reports findings on the lines attributed to that edit and counts findings elsewhere in the file whose origin is unknown. Those other findings may come from earlier edits in the same task; the final scan still covers them. Triage them per `skills/slop-check/SKILL.md`: fix real slop and report checked invariants for retained findings in the final response. Do not add code comments; remove nonessential comments from code you touch. Keep required license notices and functional tool directives. Never add `SAFETY:` or `lazy:` markers. Verify necessary functional suppressions and explain the evidence in the final response.\n\n```\nnode skills/slop-check/scripts/check.mjs [paths...] [--json] [--summary] [--since=<ref>] [--disable=<rule-id>,...] [--explain=<rule-id>]\n```\n\nWith no paths it scans the current directory. Exit code 1 means findings, 2 means a path could not be read, 0 means clean — so a failed scan is never mistaken for a clean one.\n\n`--since=<ref>` keeps only findings on lines the diff against `<ref>` added. That is the whole adoption story for an existing codebase: there is no baseline file to generate or refresh, because git already holds the baseline.\n\n```\nnode skills/slop-check/scripts/check.mjs --since=HEAD          # before committing\nnode skills/slop-check/scripts/check.mjs --since=origin/main   # in CI\n```\n\nFindings are grouped by whether the fix needs judgment: mechanical ones have a single correct answer, review ones are heuristics where \"this is deliberate, leaving it\" is a legitimate reply. A message shared by several findings is printed once, on the first. `--summary` replaces the finding list with the per-rule tally, which is the number that tells you whether a codebase is worth a full pass. The run summary line still prints; `--json` is the machine-readable form.\n\nAssertion tallies display `type assertion review`. The legacy rule ID `require-safety-comment-for-type-assertion` remains accepted by `--explain`, `--disable`, and existing directives, and remains the ID in JSON output.\n\nEmoji, sequencing comments, change-note comments, and apparently obvious documentation comments are review findings. Retain symbols required by a specification; put useful contracts and rationale in the final response. Existing `SAFETY:` and `slop-check-ignore` directives remain supported for compatibility, but new ones are not a remedy for findings.\n\n`--explain=<rule-id>` prints one rule's reasoning — why it fires, a slop/instead pair, and when the rule is wrong — and runs no scan. Read it before rewriting code a finding landed on that you believe is correct:\n\n```\nnode skills/slop-check/scripts/check.mjs --explain=no-json-clone\n```\n\nArray performance findings are review prompts too: `no-reduce-accumulator-copy`\ndetects repeated copies of reducer accumulators, including spread, and\n`no-array-filter-map` checks adjacent eager passes on locally evidenced arrays.\nThese checks are conservative and have no autofix. Before rewriting, preserve\naccumulator ownership, callback order and indexes, and sparse-array behavior.\n\nBefore finishing any TS/JS task, run `node skills/slop-check/scripts/check.mjs --since=HEAD`\nfrom the repo root even if edit hooks ran. It includes new untracked files and\nshell edits without fragile shell path splitting. Use the task base ref for\nalready-committed changes. Triage only the task's scope, not unrelated user edits.\nWithout Git, pass each changed path as a separate quoted argument.\n\nThe main and subagent prompts use the same compact rules. They include a brief\nthink → plan → check loop for multi-step work and a strong cut pass over the\ntask-owned diff without forcing net-negative feature changes. Every build\nsurface (the `lazy` and `lazy-clean` skills, the injected ruleset, the rules\nfiles, and OpenCode's `/lazy`) ends with the same four-item finish checklist, checked against the diff rather\nthan memory: requested needs done and nothing unasked added, each changed line\ntraceable, tests that ran, and language checks applied. Detailed\n[risk checks](https://github.com/JustasMonkev/lazy-clean/blob/main/skills/lazy/references/risk-checks.md) are loaded for non-trivial\ncode changes, not every task. All levels preserve requested scope, existing\ninput formats, and the repo's test tools; none treats one-line code as a goal.\n\n[Run the AI behavior checks](https://github.com/JustasMonkev/lazy-clean/blob/main/benchmarks/README.md) to compare fourteen core tasks with the\nrules off/on, three trials each. A separate six-task SOLID set checks module\nboundaries and contracts; see [the design experiment](https://github.com/JustasMonkev/lazy-clean/blob/main/benchmarks/solid-guidance.md). Compare correctness first, then time, cost,\nand size for paired passing runs. This fork makes no measured improvement\nclaim without real comparable runs. `npm test` checks the benchmark machinery\nwithout calling an AI service.\n\nFor a genuine false positive, verify the invariant and explain the evidence in the final response. Do not add suppression comments to make the scan clean. Remove ineffective existing ignores and address the underlying findings.\n\nExisting justified `slop-check-ignore` directives remain supported for compatibility. They cover their line and the next and accept several rule IDs separated by commas. Existing `slop-check-ignore-file` directives in a file's first 10 lines cover the whole file. `--disable=<rule-id>,...` turns rules off for one run; it does not verify that a finding is harmless.\n\nAn existing ignore with no reason after `--`, an unknown rule ID, or a file-level directive below line 10 suppresses nothing and is reported as `no-unjustified-ignore`. The run summary counts suppressions; a clean result with suppressions is not evidence that the ignored code was checked.\n\nSkills available: `lazy`, `lazy-audit`, `lazy-debt`, `lazy-gain`, `lazy-help`, `lazy-review`, `slop-check`, `lazy-clean` (the main workflow), `lazy-verify`, `layz-test`.\n\n```\n/lazy lite     # gentle\n/lazy full     # default\n/lazy ultra    # YAGNI extremist\n/lazy          # report current level\n```\n\n`/lazy default <lite|full|ultra|off>` sets the default for new sessions. When the\nhost supplies a session ID, each session retains its own level (including `off`)\nacross resume, compaction, and process restarts. Changing the default does not\nchange already initialized sessions. Subagents and the badge use the same session\nstate when the host supplies that identity.\n\nOn OpenCode upgrades, the first session needing initial state inherits the legacy global mode. Successful migration removes the global flag; later sessions use the configured default. Existing scoped modes are never overwritten.\n\nHosts without session IDs retain the legacy global flag: concurrent chats cannot be isolated there. On no-ID OpenCode, changing the default clears that global override and affects the current chat too; the command reports this explicitly. Session files are not expired by age, because an old session can still be resumed.\n\nThe Bash and PowerShell statusline launchers use the shared Node state/config reader. The plugin ships a statusline script that shows the active level (`[LAZY]`, `[LAZY:ULTRA]`). It is not wired up automatically: on first session the hook offers to add a `statusLine` entry to your `settings.json` pointing at `hooks/lazy-statusline.sh` (or `.ps1` on Windows), and it makes that offer at most once.\n\nHide the badge while keeping lazy active with `LAZY_HIDE_STATUS=1`, or `\"hideStatus\": true` in `~/.config/lazy/config.json` (`%APPDATA%\\lazy\\config.json` on Windows).\n\n- Lazy only: `/lazy off` (or say \"stop lazy\" / \"normal mode\"). It stays off in that session, including after resume/compaction; new sessions use the default.\n- Slop-check only: remove the `PostToolUse` entry from`hooks/lazy-clean.json` .\n- Everything: disable the plugin in `/plugin` , or drop the`--plugin-dir` flag.\n\n```\nnpm test              # rule, CLI, and hook suites — no dependencies\nnpm run slop-check    # the checker over this repo, which it has to survive\n```\n\n", "url": "https://wpnews.pro/news/show-hn-lazy-clean-keep-coding-agents-from-overengineering", "canonical_source": "https://github.com/JustasMonkev/lazy-clean", "published_at": "2026-10-03 14:27:05+00:00", "updated_at": "2026-10-03 14:36:08.440342+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["lazy-clean", "JustasMonkev", "Claude Code", "Codex", "OpenCode", "Cursor", "GitHub Copilot", "TypeScript"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-lazy-clean-keep-coding-agents-from-overengineering", "markdown": "https://wpnews.pro/news/show-hn-lazy-clean-keep-coding-agents-from-overengineering.md", "text": "https://wpnews.pro/news/show-hn-lazy-clean-keep-coding-agents-from-overengineering.txt", "jsonld": "https://wpnews.pro/news/show-hn-lazy-clean-keep-coding-agents-from-overengineering.jsonld"}}