Skip to main content

lint-setup

Compares the Go linter setup of this repository with the LDD recommended setup, and agrees a tighter setup with the user before any edit. WHEN: the user asks to compare, align or set up the Go linter config with the LDD recommended setup, or runs /go-ldd-setup. Shows, per rule, the recommended value, the project's value and the finding count at each; the user decides per rule; files change only after the user agrees to the edit plan. Not for fixing lint findings.

Source facts

Repository
weka/linter-driven-development
Last source activity
October 25, 2026 at 16:25
Detected SKILL.md language
English
Stars
7
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
5 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
lint-setup
description
Compares the Go linter setup of this repository with the LDD recommended setup, and agrees a tighter setup with the user before any edit. WHEN: the user asks to compare, align or set up the Go linter config with the LDD recommended setup, or runs /go-ldd-setup. Shows, per rule, the recommended value, the project's value and the finding count at each; the user decides per rule; files change only after the user agrees to the edit plan. Not for fixing lint findings.
allowed-tools
["Read","Grep","Glob","Edit","Write","WebSearch","WebFetch","Bash(bash *ldd-lint.sh *)","Bash(git *)","Bash(golangci-lint run *)","Bash(golangci-lint linters *)","Bash(golangci-lint config *)","Bash(ruff check *)","Bash(npx --no-install eslint *)"]
<objective> Compare the linter setup of each project with the recommended setup of this plugin. Agree a tighter setup with the user. Then make only the agreed edits. This skill is a conversation with the user, not a report. The user may not know linter internals: say each finding in plain words. The twelve rules do not change. A value the user relaxes changes the linter only; the review keeps the limits of the rules. </objective> <promise> **Follow these steps in this order, and edit nothing before step 6.** - An edit is any change to a file in the repository: a linter config, a task file, a CI workflow, a hook config, a suppressions file, `.ldd-lint.yaml`. A temporary file outside the repository is not an edit. - `allowed-tools` lets you read, search, edit, write, run the doctor, run `git` and run the check commands of the linters with no prompt. Installs and all other Bash calls ask the user first. Thus only this text guards the edits: the user's yes to the full plan in step 6 is the one approval of every edit. - Before step 6, do not run a linter with a fix flag. A fix setting in a config (for example `fix = true` or `issues.fix: true`) counts as a fix flag: turn it off on the command line, as the linter reference shows. Do not run a command that writes a suppressions file or a baseline file into the repository. Do not run `git add`, `git commit`, `git checkout`, `git stash`, `git reset` or `git clean`. Do not install a package. - **Headless:** when no user turn can come after step 5 (a headless run, or a run as a subagent), print the table of step 5 and the plan of step 6, and stop. Edit nothing. </promise> <paths> - The script: `<this skill dir>/../../scripts/ldd-lint.sh`. Call it as one direct command, with the path not in quotes, for example `bash <this skill dir>/../../scripts/ldd-lint.sh doctor --root <dir> --tsv`. The allowed pattern is `bash *ldd-lint.sh *`. Do not put quotes around the path, and do not put a variable, a `cd` or a pipe around the command: the pattern then does not match, and each call asks the user. - The recommended configs: `<this skill dir>/../../setup/`. - The linter reference: `<this skill dir>/<linter>.md`, where `<linter>` is column 2 of the doctor's rows. It holds the recipes per linter: the counts, the "new issues only" setup, the strict file, the superset check, the CI callers and the hooks. Read it when a step names it. - The research reference: `<this skill dir>/research.md`. Read it only when a step below sends you there. </paths> ## 1. Find the projects 1. Arguments name the projects: one directory each. With no argument, use one project per linter config root. In a monorepo, pick the projects from context: the directories that hold a linter config and source files, the request of the user, and the files the user works on. 2. Tell the user the projects you picked, and why. 3. Read the record of each project first: `.ldd-lint.yaml` in the project root, if it exists. A rule in its `agreed:` list is a decision the team made. Do not ask about it again, unless step 2 shows drift for it. ## 2. Check the setup of each project 1. Run the doctor once per project: `bash <this skill dir>/../../scripts/ldd-lint.sh doctor --root <dir> --tsv`. Each row on stdout is: language, linter, rule, state, recommended, current, source. A `doctor:` line on stderr is a note. 2. Branch on the exit code: - **0:** the doctor checked the project. Continue. - **2:** read the stderr line. - `no language in scope has a recommended linter config`: read the research reference, and do the research for this project. - `no source file in scope`: tell the user. Stop for this project. No research. - `no recommended configs at <dir>`: the plugin install is broken. Quote the line, tell the user to reinstall the plugin, and stop. - Any other line is a wrong call of the script: an argument it does not take, or a path that does not exist. Correct the call once. When the same line comes back, quote it and stop for this project. - **3:** another installed plugin checks every language of this project. Show the doctor's line `doctor: <language> — <plugin> is installed and checks this language; run /<prefix>-setup`, and tell the user to run that command. Stop for this project. No research. 3. Show each `doctor: <language> — …` note to the user in plain words. Two notes change the next steps: - `<linter> did not check the config: <message>`: the linter's own check of the config did not run, for example because it cannot fetch its schema with no network. Before you show a table, tell the user that this check did not run, and why, from the message. A config that the check rejects can still give rows. - `the linter reads <path>, outside this repository`: the project has no config of its own for that linter, so each teammate's machine can give another one. Treat the project as a project with no repository config, and propose to add one in the plan. Until the project has its own config, `agree` refuses a rule line for that linter, and `agree --linters` and `agree --fingerprint` leave that linter out with a note. 4. **`cannot verify` of a linter stops that project only.** A row whose rule is `-` and whose state starts with `cannot verify:` means that the doctor cannot check that linter. Do not work around it. Stop for this project. Name the cause from the row and the fix, and continue with the other projects: - the linter is not installed: name the install command that the project uses (its lock file, its tool list or its README). Do not install it. - the linter is not on PATH: tell the user to put the project's linter on PATH, for example to activate the project's virtual environment, and to run the setup again. - the config does not load (the linter rejects the config, or cannot run with it): quote the message of the linter, and name the file and the key. - the config fails the linter's own check: quote the message, and name the key. - the agreed config does not exist: the `lint_config` item of the record names a file that is gone. The fix: restore the file, or remove that item and run the setup again. - the linter is installed in a project below, not here: this is not a broken setup. Run the doctor with `--root` on that project, and add it to the projects. A row of a named rule whose state starts with `cannot verify:` (for example `cannot verify: the linter reports no <key>`) stops that rule only. The installed linter cannot report that setting. In the table of step 5, show the rule as "the installed linter cannot report this setting; upgrade it", and go on with the other rules. `agree` refuses a record line for that rule. 5. Find the drift. An agreed rule shows drift when its `current` ends in `; agreed: <value>` (the setting changed), or when stderr says `doctor: <path> changed since the setup was agreed` for a config of its linter. When the linter reference has a drift step, do it now. Ask about these rules again in step 5. 6. A stderr line `doctor: .ldd-lint.yaml: no row for <linter> <rule>; the line is ignored` names a record line for a rule that the doctor does not check: a typo, or a rule that the recommended config does not have now. Show it, and ask in step 5 whether to remove the line. ## 3. Find what the team already has 1. Read the task file of the project: a Taskfile, a Makefile, the `scripts` of `package.json`, a `justfile`, or the file that the project uses. Read the CI workflows and the hook config (for example lefthook, pre-commit, husky, lint-staged). 2. **Find each task by the command it runs, not by its name.** Find the lint task and the lint-fix task: a task that runs the linter with a fix flag is the lint-fix task, whatever its name. A task named `lint` can be the fix task. 3. Find a "new issues only" setup that the team already uses, and the base branch it uses. The linter reference names the forms. Tell the user "you already have this", and keep it. 4. Find the base branch: the branch the team merges into. Read it from the CI triggers, from an existing "new issues only" setting, or from `git symbolic-ref refs/remotes/origin/HEAD`. It is not always `main`. 5. Find each CI step and each hook that runs the linter. Compare it with the known callers in the linter reference. For a caller that is not in that list, read the research reference, and do the research for that caller. ## 4. Count the findings 1. **Say the time cost first.** Before the first run, tell the user how long one run takes on this project, and that the count needs two runs per linter. Use the duration of the lint job in CI when you can see it, else the number of source files (`git ls-files`). 2. **Do two runs per linter** with the recipe in the linter reference: - run A: every recommended value; - run B: the project's own config. Both runs check the whole project, with no "new issues only" filter. 3. **Put temporary configs outside the repository**, in a temporary directory. The paths and the exclusions in a temporary config must still resolve to the same files as in the original config. 4. Count the findings per rule in each run. When a run cannot count a rule (for example, a linter plugin is not installed), say "cannot count" and why. 5. A run for one more value happens only when the user asks for that value. ## 5. Agree, per project, in one table 1. **Show one table per project.** One row per rule that needs a decision: - a rule that is not agreed and is in the state `missing`, `limit looser`, `options differ` or `named`; - a rule that is not agreed and whose recommended value is `see <file>: <key>`, whatever its state. The doctor cannot compare these values. Read the value at both places: the linter reference says how. Leave the rule out only when the two values are equal, or when the project's value is stricter; - each agreed rule with drift (a drift row), whatever its state. A drift row also shows the value the team agreed and the value now; - a rule whose row cannot verify (step 2.4): it shows "the installed linter cannot report this setting; upgrade it", and it gets no choice. Say each row in plain words: what the rule limits, the project's value, the recommended value, and the finding count at each. For example: "Function complexity — your limit: 10; recommended: 8; 214 functions fail at 8; 31 at 10." When a value is `see <file>: <key>`, read the value at that place. A key that is not there means the linter's default. 2. **Give a default choice per row.** The choices are: - `recommended`: take the recommended value; - `keep`: keep the project's value; - `<value>`: use another value; - `off`: leave the rule off; - `org`: the organization owns this value; - on a drift row only, `accept`: agree the value that the config holds now. `keep` on a drift row restores the agreed value in the config, and its record line stays. The default is `accept` for a drift row, and `recommended` for each other row. A rule that item 1 gives no row needs no decision: a rule in the state `runs` or `limit stricter` whose values you know, or a rule in the state `agreed` with no drift. List those rules in one line under the table. 3. The user answers the exceptions by rule in one reply, for example "all defaults; complexity keep; arguments org". Ask for a short reason for each `keep`, `<value>`, `off` and `org`: the record keeps it. 4. **Propose a ladder step from the counts:** 1. Tighten the original config. One config for everybody. Propose it when the findings at the agreed values are few, or the team accepts a cleanup. 2. Tighten the original config, and check new issues only. Still one config: the old findings stay until a change touches that code. Propose it when the findings are many. The linter reference gives the form, the base branch it needs and its cost. 3. A strict file that only LDD uses: `<the linter's file>.strict.<ext>`. **Never recommend step 3 from the counts.** Ask whether the team accepts changes to the shared config, and offer step 3 only when it does not. **Step 3 is the strict file and a task that runs it, as one agreement.** Without the task, the lint-fixer does not find the file, so never offer the file alone. The per-rule choices apply in each step. 5. Headless: print the tables and the plan of step 6 with the default choices, and stop. ## 6. Show the edit plan, then edit 1. **Show the full plan as one diff per file**: the linter configs, the tasks, the CI and hook edits, the installs, and the record. Change no file that is not in the plan. 2. Tasks: **never rename a task, and never change what an existing task does.** Add a task only where no task runs the linter in the agreed way. Give it a name that fits the names in the file. A task that you add accepts a scope: a Taskfile through `{{.CLI_ARGS}}` (`task <name> -- ./pkg/a ./pkg/b`), a Makefile through `ARGS`. With no scope, it checks all that the agreed mode allows. 3. CI: keep a native action; do not replace it with a task. Where a caller does not agree with the agreed setup, show the edit. The linter reference names the cases. 4. **Hooks: name each hook that runs a lint task, with what changes for each teammate's commit.** For example: "lefthook runs `task lint` on each commit; from now on, a commit fails on a new complexity finding in the files it changes." 5. An install (a linter, a linter plugin, a helper tool) is a line in the plan. Do the install only when the user agrees to that line. 6. The record: show the keys and one line per decision. The fingerprint lines and the linters lines come from the edited files, so the plan says "written after the edits" for them. 7. **Edit only after an explicit yes to this plan.** A question or a change request is not a yes: show the changed plan and ask again. Every change goes into the plan before the yes, so the user agrees once, to the whole plan. 8. After the yes, apply the whole plan in one pass: do items 1 to 7 below and step 7, and ask no new question. Stop only when a step fails (item 10). Edit in this order: 1. The linter configs, the tasks, the CI and the hooks, and the agreed installs. 2. **The config that `agree` compares.** When the plan changes the `lint_config` of a linter (a new strict file, a strict file with another name, or a return from ladder step 3 to the original config), the record still names the old file. Then add `--config <the config that the plan keeps>` to each `agree` call of items 3 to 5 for that linter, relative to the project root. The plan keeps the strict file, or the original config when it drops the strict file. When the doctor reports two or more linters for the project, `--config` needs `--linter <linter>`: the linter of that config. Without `--config`, `agree` compares the file of the old item: a file that the plan deletes stops `agree`, and a file that the plan keeps but does not use gives the wrong values. 3. One record line per decision, from the script: `bash <this skill dir>/../../scripts/ldd-lint.sh agree --root <dir> --linter <linter> --rule <rule> --decision <d> --reason '<reason>'`. Put the reason in single quotes, and write each apostrophe in it as `'\''`, so that the shell does not change the text. Use `--decision current` for `keep`, for `accept` and for `<value>` (after the config holds that value), `off` for `off`, and `org` for `org`. A rule in the state `missing` has no setting: use `off`. A `recommended` choice needs no line; on a drift row, it removes the old line. `keep` on a drift row keeps the old line. Add `--config` (item 2) only to the lines of the linter of that config: the `--linter` of a rule line also names the linter of `--config`. 4. The fingerprint lines, after the last config edit: `bash <this skill dir>/../../scripts/ldd-lint.sh agree --root <dir> --fingerprint`, with `--config` (item 2) when it applies. Add `--linter <linter>` only together with `--config`, in a run with two or more linters. `agree` prints a line only for a linter whose rows show `see <file>: <key>` in place of a value. The rows of each other linter show a changed value, so that linter needs no fingerprint. An empty output is not an error. 5. The linters lines, for every project that gets a record, after the last config edit: `bash <this skill dir>/../../scripts/ldd-lint.sh agree --root <dir> --linters`, with `--config` (item 2) when it applies, so that the doctor judges that linter on the config that the plan keeps. Add `--linter <linter>` only together with `--config`, in a run with two or more linters. One such run prints the lines of all linters: copy each line once. `agree` prints one line for each linter that this plugin reports and checked. The status line counts the rows of these linters only. A setup that took every recommended value has no other item that names its linter. When `agree` prints a note on stderr for a linter, show the note. That linter has no line, so the status line stays silent for it until the user fixes its setup and you run `agree --linters` again. When `agree` prints no line, write no `linters:` key (write `agreed: []`), and say the same for the project. 6. With a strict file, its line: `bash <this skill dir>/../../scripts/ldd-lint.sh agree --root <dir> --lint-config <linter> <strict file>`. 7. **Write the record last.** Copy each line that `agree` printed unchanged, under its key: `agreed:`, `lint_config:`, `lint_fingerprint:` or `linters:`. With no `agreed` line, write `agreed: []`. Write only the key lines and the copied lines. Keep the other keys of the file (for example `file_max_lines`). Keep the line of each agreed rule that this run did not ask about, and replace the line of each rule that the user decided again. Replace the fingerprint lines of the linters that this run checked. Replace the `linters:` items of the linters that `agree --linters` printed. Remove the `lint_config` item of a linter whose strict file the plan drops, and replace the item of a linter whose strict file the plan renames. Keep every other item: another plugin owns the items of a linter that this plugin does not report. 9. `agree` changes each ` | ` in a reason to ` / `. When a printed line is not the line in the plan, show the difference before you write the record. 10. **When an `agree` call exits 2 or is refused, stop, and write no record.** Never create or change `.ldd-lint.yaml` with a line that `agree` did not print: not with Write, not with Edit, not with a shell command. A line that you write yourself can differ by one character from what the doctor compares, and then the status line fires forever or stays silent forever. Show the command and the line that `agree` printed on stderr, or the refusal. For example, an edit can break a config, so that the linter cannot verify it: fix the edit with the user, or revert it with the user, and then run `agree` again. The record, for example: ```yaml lint_config: - "<linter> <strict file>" lint_fingerprint: - "<linter> <config file> <hash>" linters: - "<linter>" agreed: - "<linter> <rule> = <value> | <reason>" ``` ## 7. Verify 1. Run the doctor again on each project that you edited. 2. Each row of an agreed rule must show `runs` or `agreed`, and stderr must have no `changed since the setup was agreed` line. Else name the row that does not, and why. 3. With a strict file, do the superset check of the linter reference. Say that it checks the current tree only, and does not prove the superset for all code. 4. Tell the user what changed, file by file, and the findings that remain at the agreed values. To fix them, the user can run `/go-ldd-quickfix`.
View on GitHub