habit-hooks 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +233 -0
- package/dist/baseline/commands.d.ts +12 -0
- package/dist/baseline/commands.js +131 -0
- package/dist/baseline/commands.js.map +1 -0
- package/dist/baseline/file-hash.d.ts +2 -0
- package/dist/baseline/file-hash.js +25 -0
- package/dist/baseline/file-hash.js.map +1 -0
- package/dist/baseline/filter.d.ts +8 -0
- package/dist/baseline/filter.js +31 -0
- package/dist/baseline/filter.js.map +1 -0
- package/dist/baseline/store.d.ts +18 -0
- package/dist/baseline/store.js +106 -0
- package/dist/baseline/store.js.map +1 -0
- package/dist/checks/comment-check.d.ts +3 -0
- package/dist/checks/comment-check.js +83 -0
- package/dist/checks/comment-check.js.map +1 -0
- package/dist/checks/eslint-wrap.d.ts +2 -0
- package/dist/checks/eslint-wrap.js +104 -0
- package/dist/checks/eslint-wrap.js.map +1 -0
- package/dist/checks/jscpd-wrap.d.ts +6 -0
- package/dist/checks/jscpd-wrap.js +158 -0
- package/dist/checks/jscpd-wrap.js.map +1 -0
- package/dist/checks/knip-resolve.d.ts +4 -0
- package/dist/checks/knip-resolve.js +49 -0
- package/dist/checks/knip-resolve.js.map +1 -0
- package/dist/checks/knip-schema.d.ts +32 -0
- package/dist/checks/knip-schema.js +24 -0
- package/dist/checks/knip-schema.js.map +1 -0
- package/dist/checks/knip-wrap.d.ts +4 -0
- package/dist/checks/knip-wrap.js +127 -0
- package/dist/checks/knip-wrap.js.map +1 -0
- package/dist/cli/baseline-commands.d.ts +2 -0
- package/dist/cli/baseline-commands.js +61 -0
- package/dist/cli/baseline-commands.js.map +1 -0
- package/dist/cli/emit.d.ts +7 -0
- package/dist/cli/emit.js +8 -0
- package/dist/cli/emit.js.map +1 -0
- package/dist/cli/init/detect.d.ts +8 -0
- package/dist/cli/init/detect.js +20 -0
- package/dist/cli/init/detect.js.map +1 -0
- package/dist/cli/init/git-hook.d.ts +6 -0
- package/dist/cli/init/git-hook.js +48 -0
- package/dist/cli/init/git-hook.js.map +1 -0
- package/dist/cli/init/install-commands.d.ts +6 -0
- package/dist/cli/init/install-commands.js +55 -0
- package/dist/cli/init/install-commands.js.map +1 -0
- package/dist/cli/init/package-scripts.d.ts +6 -0
- package/dist/cli/init/package-scripts.js +55 -0
- package/dist/cli/init/package-scripts.js.map +1 -0
- package/dist/cli/init/prompts.d.ts +9 -0
- package/dist/cli/init/prompts.js +33 -0
- package/dist/cli/init/prompts.js.map +1 -0
- package/dist/cli/init/reporters.d.ts +11 -0
- package/dist/cli/init/reporters.js +40 -0
- package/dist/cli/init/reporters.js.map +1 -0
- package/dist/cli/init/run.d.ts +12 -0
- package/dist/cli/init/run.js +159 -0
- package/dist/cli/init/run.js.map +1 -0
- package/dist/cli/init/scaffold-baseline.d.ts +4 -0
- package/dist/cli/init/scaffold-baseline.js +11 -0
- package/dist/cli/init/scaffold-baseline.js.map +1 -0
- package/dist/cli/init/scaffold-config.d.ts +13 -0
- package/dist/cli/init/scaffold-config.js +42 -0
- package/dist/cli/init/scaffold-config.js.map +1 -0
- package/dist/cli/init/scaffold-eslint-config.d.ts +2 -0
- package/dist/cli/init/scaffold-eslint-config.js +12 -0
- package/dist/cli/init/scaffold-eslint-config.js.map +1 -0
- package/dist/cli/init/scaffold-jscpd-config.d.ts +2 -0
- package/dist/cli/init/scaffold-jscpd-config.js +12 -0
- package/dist/cli/init/scaffold-jscpd-config.js.map +1 -0
- package/dist/cli/init/scaffold-knip-config.d.ts +2 -0
- package/dist/cli/init/scaffold-knip-config.js +12 -0
- package/dist/cli/init/scaffold-knip-config.js.map +1 -0
- package/dist/cli/init/skill.d.ts +7 -0
- package/dist/cli/init/skill.js +36 -0
- package/dist/cli/init/skill.js.map +1 -0
- package/dist/cli/init/snippet.d.ts +1 -0
- package/dist/cli/init/snippet.js +18 -0
- package/dist/cli/init/snippet.js.map +1 -0
- package/dist/cli/init/templates/eslint-config.d.ts +2 -0
- package/dist/cli/init/templates/eslint-config.js +35 -0
- package/dist/cli/init/templates/eslint-config.js.map +1 -0
- package/dist/cli/init/templates/jscpd-config.d.ts +2 -0
- package/dist/cli/init/templates/jscpd-config.js +9 -0
- package/dist/cli/init/templates/jscpd-config.js.map +1 -0
- package/dist/cli/init/templates/knip-config.d.ts +2 -0
- package/dist/cli/init/templates/knip-config.js +8 -0
- package/dist/cli/init/templates/knip-config.js.map +1 -0
- package/dist/cli/init-command.d.ts +2 -0
- package/dist/cli/init-command.js +33 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +101 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +4 -0
- package/dist/config/defaults.js +172 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/jiti-loader.d.ts +1 -0
- package/dist/config/jiti-loader.js +13 -0
- package/dist/config/jiti-loader.js.map +1 -0
- package/dist/config/load.d.ts +8 -0
- package/dist/config/load.js +53 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/merge.d.ts +3 -0
- package/dist/config/merge.js +90 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/schema.d.ts +32 -0
- package/dist/config/schema.js +4 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/validate.d.ts +2 -0
- package/dist/config/validate.js +128 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/detect/package-json.d.ts +1 -0
- package/dist/detect/package-json.js +15 -0
- package/dist/detect/package-json.js.map +1 -0
- package/dist/detect/tool.d.ts +10 -0
- package/dist/detect/tool.js +73 -0
- package/dist/detect/tool.js.map +1 -0
- package/dist/eslint-runner.d.ts +7 -0
- package/dist/eslint-runner.js +34 -0
- package/dist/eslint-runner.js.map +1 -0
- package/dist/git/exec.d.ts +8 -0
- package/dist/git/exec.js +48 -0
- package/dist/git/exec.js.map +1 -0
- package/dist/git/resolve-scope.d.ts +15 -0
- package/dist/git/resolve-scope.js +89 -0
- package/dist/git/resolve-scope.js.map +1 -0
- package/dist/git/scope.d.ts +7 -0
- package/dist/git/scope.js +58 -0
- package/dist/git/scope.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/loader.d.ts +6 -0
- package/dist/prompts/loader.js +27 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/prompts/packaged-dir.d.ts +1 -0
- package/dist/prompts/packaged-dir.js +10 -0
- package/dist/prompts/packaged-dir.js.map +1 -0
- package/dist/prompts/registry.d.ts +3 -0
- package/dist/prompts/registry.js +68 -0
- package/dist/prompts/registry.js.map +1 -0
- package/dist/reporter.d.ts +7 -0
- package/dist/reporter.js +114 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rules/registry.d.ts +3 -0
- package/dist/rules/registry.js +37 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/runner.d.ts +15 -0
- package/dist/runner.js +151 -0
- package/dist/runner.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wrap/notices.d.ts +13 -0
- package/dist/wrap/notices.js +29 -0
- package/dist/wrap/notices.js.map +1 -0
- package/dist/wrap/resolve.d.ts +5 -0
- package/dist/wrap/resolve.js +9 -0
- package/dist/wrap/resolve.js.map +1 -0
- package/dist/wrap/run.d.ts +15 -0
- package/dist/wrap/run.js +26 -0
- package/dist/wrap/run.js.map +1 -0
- package/dist/wrap/shell.d.ts +14 -0
- package/dist/wrap/shell.js +44 -0
- package/dist/wrap/shell.js.map +1 -0
- package/package.json +46 -0
- package/src/prompts/comment-non-essential.md +7 -0
- package/src/prompts/eslint-boundaries-dependencies.md +9 -0
- package/src/prompts/eslint-complexity.md +9 -0
- package/src/prompts/eslint-eqeqeq.md +3 -0
- package/src/prompts/eslint-fatal.md +7 -0
- package/src/prompts/eslint-max-lines-per-function.md +9 -0
- package/src/prompts/eslint-max-lines.md +9 -0
- package/src/prompts/eslint-max-params.md +9 -0
- package/src/prompts/eslint-no-duplicate-imports.md +1 -0
- package/src/prompts/eslint-no-unused-vars.md +3 -0
- package/src/prompts/eslint-no-var.md +1 -0
- package/src/prompts/eslint-no-warning-comments.md +3 -0
- package/src/prompts/eslint-prefer-const.md +3 -0
- package/src/prompts/eslint-typescript-eslint-no-explicit-any.md +3 -0
- package/src/prompts/eslint-typescript-eslint-no-inferrable-types.md +3 -0
- package/src/prompts/eslint-typescript-eslint-no-non-null-assertion.md +3 -0
- package/src/prompts/jscpd-duplication.md +9 -0
- package/src/prompts/knip-classMembers.md +7 -0
- package/src/prompts/knip-dependencies.md +9 -0
- package/src/prompts/knip-exports.md +9 -0
- package/src/prompts/knip-files.md +9 -0
- package/src/prompts/knip-types.md +9 -0
- package/src/prompts/uncoached.md +3 -0
- package/src/skills/habit-hooks-review/SKILL.md +108 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
`any` opts out of the type checker — every property access, call, and assignment becomes unchecked. The bug it hides next will be silent.
|
|
2
|
+
|
|
3
|
+
Prefer a precise type. When the shape really is unknown (parsed JSON, external input), use `unknown` and narrow with a type guard at the boundary. Casting through `as Foo` only moves the problem.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
`const count: number = 0` repeats what TypeScript already knows. Drop the annotation; the inferred type is identical and the declaration reads more cleanly.
|
|
2
|
+
|
|
3
|
+
Keep annotations where they document an interface contract (function signatures, exported constants where the literal type would be too narrow), not where they restate the obvious.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
The `!` operator tells TypeScript "trust me, this is not null" — and at runtime nobody is checking. When the assumption breaks you get a `Cannot read properties of undefined` with no clue why.
|
|
2
|
+
|
|
3
|
+
Prove the value is present: an `if`-guard with an early return, an `assert`, `??` with a sensible default, or restructure so the optional case is impossible at the call site.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Duplicated blocks are the cheapest visible sign of a missing abstraction. The right fix is rarely "extract a helper" — it's usually to name the concept that both copies are reaching for.
|
|
2
|
+
|
|
3
|
+
Read both occurrences side by side and ask: (1) Are these two copies of the same idea, or two different ideas that happen to look alike? (2) If they are the same idea, what is its name? (3) Does the duplicated block belong to a domain concept that does not exist yet — a value object, a strategy, a small class?
|
|
4
|
+
|
|
5
|
+
Avoid mechanical extraction. Pulling the matching lines into a function with five parameters and a couple of conditionals leaves the real shape untouched and adds a worse name on top. If extracting feels awkward, the underlying abstraction is wrong — step back and find the right one.
|
|
6
|
+
|
|
7
|
+
A concrete technique: try to write one sentence that names what both copies do. If the sentence reads naturally, that sentence is the name of the abstraction you are missing. If you can only describe the block as "the code that does X, Y, and Z", the duplication is hiding two or three smaller abstractions, not one.
|
|
8
|
+
|
|
9
|
+
If the two copies actually diverge in important ways and a shared abstraction would distort either side, document the decision (a short note or a test that pins both behaviours) and snooze the duplication — duplication is the lesser evil compared to a wrong abstraction.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
An unused class member is dead weight: either delete it or wire it up. There is no third option that does not lie to a future reader.
|
|
2
|
+
|
|
3
|
+
Delete by default. If the member is part of a planned-but-unbuilt API, document that in code with `@public`/`@internal` JSDoc tags so knip can be configured to ignore it — do not leave the dead code naked.
|
|
4
|
+
|
|
5
|
+
If you find yourself wanting to keep the member "just in case", that is a signal you have not committed to a direction. Pick one: delete it now, or write the test that exercises it so it stops being unused.
|
|
6
|
+
|
|
7
|
+
A concrete technique: search the codebase for the method name. If the only hit is the definition itself, the method is genuinely dead. If there are hits in tests but no production caller, the test is testing the test — delete both.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
A `package.json` dependency with no detected import is either genuinely unused, or used through a path the tool cannot statically see (config-loaded plugins, peer requirements of another tool, dynamic `require`). The right action depends on which one — guess wrong and you either ship dead bytes or break a build.
|
|
2
|
+
|
|
3
|
+
Ask first: does anything load this package by name from a config file? Common culprits — `typescript-eslint` pulled in by an ESLint flat config, `postcss` plugins loaded from `postcss.config.js`, `vite` plugins, framework adapters. These show up as "unlisted" too because knip sees them imported from a file it does not scan. The fix is to teach knip about the config (extend its `plugins` config) or list the dependency in `ignoreDependencies` with a one-line note explaining why.
|
|
4
|
+
|
|
5
|
+
Otherwise the dependency is genuinely unused. Uninstall it (`pnpm remove <pkg>`), run the build and tests, and commit. Carrying unused dependencies inflates lockfiles, slows installs, and widens the supply-chain attack surface.
|
|
6
|
+
|
|
7
|
+
Avoid mechanical fixes. Adding a stray `import` somewhere to "use" the package only hides the question. Blanket-ignoring all flagged deps is worse — every entry in `ignoreDependencies` should be a deliberate decision with a reason attached.
|
|
8
|
+
|
|
9
|
+
A concrete technique: for each flagged package, search the repo for its name as a string (not just as an import). Hits in config files mean it is config-loaded — document it. Zero hits means it is truly unused — remove it.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
An `export` that no consumer references is either internal-by-accident or a deliberate public surface the tool does not know about. Either way the current state is misleading — the keyword tells readers "someone outside this module depends on this", and that is not true.
|
|
2
|
+
|
|
3
|
+
Ask first: is this symbol part of a deliberate public API? Library entry points, framework hooks, plugins, types re-exported for downstream consumers — these are real exports knip just cannot see. The fix is configuration: add the file (or the symbol's containing entry) to `entry` in `knip.json` so the tool stops asking.
|
|
4
|
+
|
|
5
|
+
Otherwise the export is internal-by-accident. Drop the `export` keyword. The symbol stays — it is still used inside its own module — it just stops pretending to be part of the module's public surface. Tests that imported it directly should reach in via the public API or move into the same module.
|
|
6
|
+
|
|
7
|
+
Avoid mechanical fixes. Adding a no-op re-export from `index.ts` to satisfy knip does not address the smell; it just makes the implicit public surface look intentional. Suppressing the rule per-symbol scatters the public-API decision across the codebase instead of capturing it in one config file.
|
|
8
|
+
|
|
9
|
+
A concrete technique: for each flagged export, ask "if I remove the `export` keyword, what breaks?" If nothing breaks, the export was a lie. If something outside the module breaks, you have just discovered an undocumented public dependency — write it down in `knip.json` and treat that file as a public-API surface from now on.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
A file that no one imports is either an entry point the tool does not know about, or genuinely orphaned code. Pick one — leaving it in the repo as "probably used somewhere" rots into a maintenance trap.
|
|
2
|
+
|
|
3
|
+
Ask first: is this file entry-point-like? CLI scripts, server entries, test setup, build hooks, framework-discovered routes — these are called by something other than `import`. If so, the fix is configuration, not deletion: add the path (or a glob covering it) to `entry` in `knip.json` so the tool can see why it exists.
|
|
4
|
+
|
|
5
|
+
Otherwise the file is orphaned. Delete it. If it is a "kept for reference" or "we might need this again" file, that is a confession that nobody owns it; version control is your reference, not the working tree.
|
|
6
|
+
|
|
7
|
+
Avoid mechanical fixes. Adding a one-line `import` from somewhere else just to silence knip creates a fake consumer and hides the real status of the file. Wrapping the whole file in a `knip-ignore` comment without first deciding which category it falls into preserves the ambiguity.
|
|
8
|
+
|
|
9
|
+
A concrete technique: try to delete the file in a scratch branch and run the build and tests. If everything passes, the file was orphaned and you have your answer. If something breaks, you have just located the implicit entry point that needs to be made explicit in `knip.json`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
A `type` or `interface` exported with no consumer is either internal-by-accident or an undeclared public type surface. Either way the keyword lies: it tells readers "something outside this module depends on this shape", when nothing does.
|
|
2
|
+
|
|
3
|
+
Ask first: is this type part of a deliberate public API? Return-types and option-bags that flow through an exported function, types re-exported from a package entry, types consumed by a downstream plugin or framework — these are real public surface knip just cannot see. The fix is configuration: add the file (or the symbol's containing entry) to `entry` in `knip.json` so the tool stops asking.
|
|
4
|
+
|
|
5
|
+
Otherwise the type is internal-by-accident. Drop the `export` keyword. The type stays — it is still used inside its own module — it just stops pretending to be part of the module's public surface. If a test was importing the type directly, that usually means the test is reaching past the public API; move the test to the same module, or assert against the function's inferred return type instead.
|
|
6
|
+
|
|
7
|
+
Avoid mechanical fixes. Re-exporting the type from `index.ts` to satisfy knip does not address the smell; it just makes the implicit public surface look intentional. Suppressing the rule per-symbol scatters the public-API decision across the codebase instead of capturing it in one config file.
|
|
8
|
+
|
|
9
|
+
A concrete technique: for each flagged type, ask "if I remove the `export` keyword, what breaks?" If nothing breaks, the export was a lie. If something outside the module breaks, you have just discovered an undocumented public type — write it down in `knip.json` and treat that file as a public-API surface from now on.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
These rules fired in your project but habit-hooks does not ship a tuned coaching prompt for them yet. The violations above are the raw output from the underlying tool (ESLint, knip, jscpd) — read them as you would read any lint message.
|
|
2
|
+
|
|
3
|
+
If a particular uncoached rule fires often and you have a clear stance on how to address it, write your own prompt: drop a markdown file named after the slugified rule id (e.g. `eslint-no-console.md`) into the prompts directory configured in `habit-hooks.config`. Habit Hooks will pick it up automatically and surface it the same way it does for built-in prompts.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: habit-hooks-review
|
|
3
|
+
description: "Spawn a reviewer sub-agent to assess a change set against habit-hooks's coding principles. Use AFTER habit-hooks reports clean — habit-hooks catches structural smells; this catches what it cannot (correctness, tests, design, missed edge cases)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Habit Hooks Review
|
|
7
|
+
|
|
8
|
+
You are reading this because `habit-hooks` reported clean and you (the implementing agent) need a second pair of eyes on the change set before declaring the work done.
|
|
9
|
+
|
|
10
|
+
A green habit-hooks run is necessary, not sufficient. habit-hooks catches structural smells — oversized functions, high complexity, `any`, dead bindings, stale `TODO`s, comments standing in for unclear code. It cannot see:
|
|
11
|
+
|
|
12
|
+
- Correctness bugs (logic errors, off-by-one, wrong branch taken).
|
|
13
|
+
- Design issues (wrong abstraction, leaky boundaries, missing seam).
|
|
14
|
+
- Test coverage gaps (a happy path test masquerading as full coverage, untested error paths).
|
|
15
|
+
- Missed edge cases (empty input, concurrent calls, mid-iteration mutation).
|
|
16
|
+
- Naming clarity (a name that reads fine in isolation but is wrong for the role it plays).
|
|
17
|
+
- Missing abstractions that do not trip a threshold (two functions that should share a type, three call sites that should share a helper).
|
|
18
|
+
|
|
19
|
+
This skill spawns a reviewer sub-agent to look for exactly those things.
|
|
20
|
+
|
|
21
|
+
## How to invoke
|
|
22
|
+
|
|
23
|
+
Use the `Task` tool to spawn a `general-purpose` sub-agent with the brief below. Do **not** review the change yourself first — the value of this skill is the fresh read. Include the diff scope (commit range, branch, or staged files) in the brief so the reviewer knows what to look at.
|
|
24
|
+
|
|
25
|
+
## The brief
|
|
26
|
+
|
|
27
|
+
Paste this verbatim into the Task tool's `prompt`, filling in the change-set scope at the top:
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
You are reviewing a change set against the principles below. The implementing agent has already run `habit-hooks` and it reports clean — so structural smells (function size, parameter count, complexity, file length, `any`, unused vars, etc.) are already covered. **Do not re-flag anything habit-hooks catches.** Focus on what habit-hooks cannot see.
|
|
32
|
+
|
|
33
|
+
**Change set under review:** `<describe scope: commit range, branch diff, or staged files>`
|
|
34
|
+
|
|
35
|
+
**Ground rules:**
|
|
36
|
+
|
|
37
|
+
- PASS-when-clean is the right answer. Do not manufacture issues to look thorough.
|
|
38
|
+
- If the change is small and well-shaped, say so. A two-line PASS is a valid outcome.
|
|
39
|
+
- Cite `file:line` for every finding. No vague "consider revisiting the design" — point at something.
|
|
40
|
+
- Assume the change was written TDD-first. If tests are missing or asymmetric (only happy paths, only the new code), say so.
|
|
41
|
+
|
|
42
|
+
**Principles to review against:**
|
|
43
|
+
|
|
44
|
+
- **KISS** — is there a simpler approach that does the same job?
|
|
45
|
+
- **Single responsibility** — does each new function/class have one reason to change?
|
|
46
|
+
- **Naming clarity** — does every name reveal intent? Can a future reader understand it in five seconds?
|
|
47
|
+
- **Readability** — is the change comfortable to navigate, or does it ask the reader to hold too many ideas?
|
|
48
|
+
- **No shortcuts** — flag `eslint-disable`, `@ts-ignore`, `as any`, swallowed catches, magic numbers without context. Even if the linter is happy, those are debts.
|
|
49
|
+
- **Correctness** — read the logic, not just the shape. Look for off-by-one, wrong-branch, swallowed errors, race conditions, mid-iteration mutation.
|
|
50
|
+
- **Edge cases** — empty input, single-element input, max/min, error paths.
|
|
51
|
+
- **Test coverage** — is each new behaviour exercised? Are failure modes tested, not just happy paths?
|
|
52
|
+
|
|
53
|
+
**Categorise findings:**
|
|
54
|
+
|
|
55
|
+
- **Blocking** — must be fixed before merge. Correctness bugs, broken tests, gate failures, missing test for new behaviour.
|
|
56
|
+
- **Worth flagging** — design or clarity issues the author should weigh. Not necessarily blocking, but worth a conversation.
|
|
57
|
+
- **Nits** — small polish items. Hard cap: **two**. If you have more than two nits, drop the weakest ones.
|
|
58
|
+
|
|
59
|
+
**Confirm the gate:**
|
|
60
|
+
|
|
61
|
+
Run (or read the most recent output of) all four scripts: `typecheck`, `lint`, `test`, `build`. Detect the package manager from the lockfile in the project root (`pnpm-lock.yaml` → `pnpm`, `yarn.lock` → `yarn`, `bun.lock` or `bun.lockb` → `bun`, otherwise `npm`) and invoke accordingly — pnpm/yarn/bun take the script name directly (`pnpm typecheck`), npm needs `run` (`npm run typecheck`).
|
|
62
|
+
|
|
63
|
+
All four must exit 0. Report each exit code in the output.
|
|
64
|
+
|
|
65
|
+
**Output format (return this verbatim):**
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
## Verdict
|
|
69
|
+
<PASS | CHANGES NEEDED>
|
|
70
|
+
|
|
71
|
+
## Findings
|
|
72
|
+
|
|
73
|
+
### Blocking
|
|
74
|
+
- <file:line — one-line summary. One short paragraph of detail.>
|
|
75
|
+
- (or: "None.")
|
|
76
|
+
|
|
77
|
+
### Worth flagging
|
|
78
|
+
- <file:line — one-line summary. One short paragraph of detail.>
|
|
79
|
+
- (or: "None.")
|
|
80
|
+
|
|
81
|
+
### Nits (max 2)
|
|
82
|
+
- <file:line — one-line summary.>
|
|
83
|
+
- (or: "None.")
|
|
84
|
+
|
|
85
|
+
## Gate output
|
|
86
|
+
- typecheck: exit <code>
|
|
87
|
+
- lint: exit <code>
|
|
88
|
+
- test: exit <code>
|
|
89
|
+
- build: exit <code>
|
|
90
|
+
|
|
91
|
+
## Specific calls
|
|
92
|
+
<Anything that does not fit the categories above — design questions for the author, a follow-up worth scheduling, a pattern worth a CLAUDE.md note. Keep it short or omit the section entirely.>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## After the reviewer returns
|
|
98
|
+
|
|
99
|
+
- If **PASS**: relay the verdict and finish the task.
|
|
100
|
+
- If **CHANGES NEEDED**: do not silently fix everything. Surface the findings to the user, agree which to act on, and only then implement. Blocking items must be resolved; worth-flagging items are a conversation, not an assignment.
|
|
101
|
+
|
|
102
|
+
## Why this is a separate skill from `code-style-review`
|
|
103
|
+
|
|
104
|
+
`code-style-review` is invoked by a human, on a file or a region, to start a discussion before refactoring. It lists problems for the human to triage and prioritise.
|
|
105
|
+
|
|
106
|
+
`habit-hooks-review` is invoked by an agent, after a clean automated gate, to get a second-pass quality read on a finished change set. The shape of the output is different — verdict-driven, scoped to the diff, framed for an agent that is about to declare done.
|
|
107
|
+
|
|
108
|
+
The two skills are complementary, not interchangeable. Do not use `code-style-review` as a stand-in here: it will not check the gate, will not categorise by blocking severity, and will not return a verdict.
|