@hopla/claude-setup 1.17.1 → 1.19.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +18 -7
- package/agents/system-reviewer.md +4 -4
- package/cli.js +142 -1
- package/commands/archive.md +137 -0
- package/commands/execute.md +1 -1
- package/commands/guides/ai-optimized-codebase.md +1 -1
- package/commands/guides/mcp-integration.md +2 -2
- package/commands/guides/remote-coding.md +1 -1
- package/commands/guides/validation-pyramid.md +1 -1
- package/commands/init-project.md +38 -17
- package/commands/plan-feature.md +21 -3
- package/commands/system-review.md +11 -15
- package/commands/validate.md +1 -1
- package/hooks/session-prime.js +12 -4
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +17 -1
- package/skills/code-review/checklist.md +1 -1
- package/skills/execution-report/SKILL.md +1 -1
- package/skills/execution-report/report-structure.md +2 -2
- package/skills/git/commit.md +2 -2
- package/skills/git/pr.md +1 -1
- package/skills/hook-audit/SKILL.md +135 -0
- package/skills/hook-audit/checklist.md +210 -0
- package/skills/hook-audit/tests/fixtures/use-bad.ts.example +53 -0
- package/skills/hook-audit/tests/fixtures/use-good.ts.example +64 -0
- package/skills/hook-audit/tests/manual-test.sh +73 -0
- package/skills/prime/SKILL.md +2 -2
- package/skills/refactoring/SKILL.md +1 -1
- package/skills/subagent-execution/SKILL.md +2 -2
- package/skills/worktree/SKILL.md +2 -2
package/commands/plan-feature.md
CHANGED
|
@@ -104,7 +104,7 @@ Based on research, define:
|
|
|
104
104
|
- **Roles / permissions / multi-tenant access:** `admin` / `member` / `viewer` / `owner`, internal vs external users, buyer vs seller. Anything where access or behavior depends on who the actor is.
|
|
105
105
|
- **Color-coded UI states with semantic meaning:** green=ok / amber=warning / red=error, or any project-specific color → state mapping. The mapping itself is a domain assumption.
|
|
106
106
|
- **Business rules:** "when X happens, then Y is forbidden / required / triggered". Any conditional behavior that encodes a policy decision rather than a technical constraint.
|
|
107
|
-
- **Project-specific vocabulary harvest:** read the project's root `CLAUDE.md
|
|
107
|
+
- **Project-specific vocabulary harvest:** read the project's root `AGENTS.md` (or `CLAUDE.md` as fallback). If a `Domain`, `Glossary`, `Vocabulary`, or similar section exists, harvest its terms. Any of those terms appearing in the user's request triggers the heuristic. **Fallback when no such section exists:** rely on the category-based triggers above.
|
|
108
108
|
- **Sentinel check:** if the planner wrote sentences in the plan that describe behavior using nouns or adjectives that are NOT in the user's verbatim request, those are inferred meanings — surface them as Domain Assumptions.
|
|
109
109
|
- When uncertain, err on inclusion: a `Domain Assumptions` subsection with 1-2 bullets is cheaper than a misaligned implementation surfaced during manual smoke.
|
|
110
110
|
|
|
@@ -144,7 +144,7 @@ Key files the executing agent must read before starting:
|
|
|
144
144
|
|
|
145
145
|
## Domain Assumptions (if applicable)
|
|
146
146
|
|
|
147
|
-
> Include this section when the feature uses domain vocabulary (entity states, lifecycle, grades/tiers, roles, color-coded states, business rules, or any project-specific terms in the project's CLAUDE.md). **Skip entirely** (do not write "N/A") when no domain semantics are involved.
|
|
147
|
+
> Include this section when the feature uses domain vocabulary (entity states, lifecycle, grades/tiers, roles, color-coded states, business rules, or any project-specific terms in the project's AGENTS.md or CLAUDE.md). **Skip entirely** (do not write "N/A") when no domain semantics are involved.
|
|
148
148
|
|
|
149
149
|
Each bullet is a user-confirmable assumption the planner made about meaning, behavior, or business rule. The user MUST confirm or correct each bullet BEFORE execution begins.
|
|
150
150
|
|
|
@@ -152,6 +152,23 @@ Each bullet is a user-confirmable assumption the planner made about meaning, beh
|
|
|
152
152
|
- [Assumption 2 — derived/computed value: e.g., "The `expired` badge shows when `expiresAt < today` AND `status != 'archived'` — confirm formula."]
|
|
153
153
|
- [Assumption 3 — grade / tier / business rule: e.g., "Tier B users have read access to the audit log but cannot export it — confirm boundary."]
|
|
154
154
|
|
|
155
|
+
## Requirements Delta (if applicable)
|
|
156
|
+
|
|
157
|
+
> Include this section when the feature **changes documented system behavior** — adds, modifies, or removes a user-visible capability or business rule. Pure refactors, performance fixes, and infrastructure changes can omit this section entirely (do NOT write "N/A"). The delta is consumed by `/hopla:archive` to fold the change into `.agents/specs/canonical/`. If the project does not yet maintain canonical specs, this section is still valuable as a structured summary for the executing agent and reviewers.
|
|
158
|
+
>
|
|
159
|
+
> When a corresponding `.agents/specs/<feature>.md` already includes a `## Requirements Delta` (created by the `brainstorm` skill), reference it here instead of duplicating: `See spec: .agents/specs/<feature>.md`.
|
|
160
|
+
|
|
161
|
+
### ADDED Requirements
|
|
162
|
+
- REQ-<DOMAIN>-<NNN>: <short title>
|
|
163
|
+
- Scenario: <name> — Given <state>, When <action>, Then <outcome>
|
|
164
|
+
|
|
165
|
+
### MODIFIED Requirements
|
|
166
|
+
- REQ-<DOMAIN>-<NNN>: <short title> (replaces previous version)
|
|
167
|
+
- <description of how the requirement changes>
|
|
168
|
+
|
|
169
|
+
### REMOVED Requirements
|
|
170
|
+
- REQ-<DOMAIN>-<NNN>: <short title> (deprecated — reason)
|
|
171
|
+
|
|
155
172
|
## Implementation Tasks
|
|
156
173
|
|
|
157
174
|
> All fields are required. Use `N/A` if a field does not apply — never leave a field blank.
|
|
@@ -245,7 +262,7 @@ Before saving the draft, review the plan against these criteria:
|
|
|
245
262
|
|
|
246
263
|
- [ ] Every task has a specific file path (no vague "update the component")
|
|
247
264
|
- [ ] Every task has: Action, File, Pattern, Details, Gotcha, Validate — no field left empty
|
|
248
|
-
- [ ] Validation Checklist has **exact commands** from `CLAUDE.md` or `package.json` (not vague "run lint")
|
|
265
|
+
- [ ] Validation Checklist has **exact commands** from `AGENTS.md` / `CLAUDE.md` or `package.json` (not vague "run lint")
|
|
249
266
|
- [ ] The plan is complete enough that another agent can execute it without this conversation
|
|
250
267
|
- [ ] No ambiguous requirements left unresolved
|
|
251
268
|
- [ ] **Data audit complete:** All data sources audited per `.agents/guides/data-audit.md`, with all findings (null cases, value semantics, derived value propagation) documented in Context References and Gotchas
|
|
@@ -263,6 +280,7 @@ Before saving the draft, review the plan against these criteria:
|
|
|
263
280
|
- [ ] **N+1 query check:** For every task that writes database queries or API calls, verify: is any call inside a loop? Could it be batched? Are there duplicate existence checks before mutations?
|
|
264
281
|
- [ ] **UX iteration budget declared:** If the feature touches UI per the Phase 4 heuristic, the plan includes `Expected UX iterations: N` (with N a positive integer ≥ 1) in Out of Scope or Notes for Executing Agent. If UI is NOT involved, the line is correctly absent (no `N/A`, no empty placeholder).
|
|
265
282
|
- [ ] **Domain Assumptions surfaced:** If the feature uses domain vocabulary per the Phase 4 heuristic, the plan includes a `## Domain Assumptions` subsection BEFORE `## Implementation Tasks`, with each bullet phrased as a user-confirmable statement. If no domain vocabulary is involved, the section is correctly absent (no `N/A`, no empty placeholder).
|
|
283
|
+
- [ ] **Requirements Delta declared (when behavior changes):** If the feature adds, modifies, or removes a user-visible capability or business rule, the plan includes a `## Requirements Delta` subsection with one or more of `### ADDED Requirements`, `### MODIFIED Requirements`, `### REMOVED Requirements`. If the change is a pure refactor/perf/infra fix with no behavior change, the section is correctly absent (no `N/A`, no empty placeholder). Requirement IDs follow the project's `REQ-<DOMAIN>-<NNN>` convention.
|
|
266
284
|
|
|
267
285
|
## Phase 7: Save Draft and Enter Review Loop
|
|
268
286
|
|
|
@@ -22,7 +22,8 @@ ls .agents/system-reviews/
|
|
|
22
22
|
|
|
23
23
|
Look for a file that matches the feature name derived from **$1** (the plan filename). If a matching review exists:
|
|
24
24
|
- Skip Steps 1–6
|
|
25
|
-
-
|
|
25
|
+
- Notify the user: "✅ A system review already exists at `.agents/system-reviews/[feature]-review.md`. If you haven't closed the lifecycle yet, run `/hopla:archive $1` to fold delta-specs into canonical specs and move the plan to `done/`."
|
|
26
|
+
- Exit.
|
|
26
27
|
|
|
27
28
|
If no matching review exists, continue with Step 1.
|
|
28
29
|
|
|
@@ -90,7 +91,7 @@ For each bug category in this implementation, check if the same category appeare
|
|
|
90
91
|
|
|
91
92
|
### Unresolved Improvements
|
|
92
93
|
Cross-reference improvement suggestions from previous reviews. If a suggestion was made before and NOT yet applied:
|
|
93
|
-
- Check if the suggested CLAUDE.md update was made
|
|
94
|
+
- Check if the suggested AGENTS.md / CLAUDE.md update was made
|
|
94
95
|
- Check if the suggested command update was made
|
|
95
96
|
- Check if the suggested guide was created
|
|
96
97
|
- List any improvements that were suggested 2+ reviews ago and are still pending
|
|
@@ -99,7 +100,7 @@ Cross-reference improvement suggestions from previous reviews. If a suggestion w
|
|
|
99
100
|
|
|
100
101
|
Suggest specific actions based on patterns found:
|
|
101
102
|
|
|
102
|
-
- **
|
|
103
|
+
- **AGENTS.md updates** (or `CLAUDE.md` for legacy projects) — universal patterns or anti-patterns to document
|
|
103
104
|
- **Plan command updates** — instructions that need clarification or missing steps
|
|
104
105
|
- **Execute command updates** — steps to add to the execution checklist
|
|
105
106
|
- **New commands** — manual processes repeated 3+ times that should be automated
|
|
@@ -136,13 +137,13 @@ root_cause: [unclear plan | missing context | missing validation | other]
|
|
|
136
137
|
|
|
137
138
|
### Pattern Compliance
|
|
138
139
|
- [ ] Followed codebase architecture
|
|
139
|
-
- [ ] Used patterns documented in CLAUDE.md
|
|
140
|
+
- [ ] Used patterns documented in AGENTS.md / CLAUDE.md
|
|
140
141
|
- [ ] Applied testing patterns correctly
|
|
141
142
|
- [ ] Met validation requirements
|
|
142
143
|
|
|
143
144
|
### System Improvement Actions
|
|
144
145
|
|
|
145
|
-
**Update CLAUDE.md:**
|
|
146
|
+
**Update AGENTS.md (or CLAUDE.md for legacy projects):**
|
|
146
147
|
- [ ] [specific addition or change]
|
|
147
148
|
|
|
148
149
|
**Update Plan Command:**
|
|
@@ -175,20 +176,15 @@ After the analysis, use this to prioritize actions:
|
|
|
175
176
|
|
|
176
177
|
| Pattern | Action |
|
|
177
178
|
|---|---|
|
|
178
|
-
| Issue happens across ALL features | Update CLAUDE.md |
|
|
179
|
+
| Issue happens across ALL features | Update AGENTS.md (or CLAUDE.md) |
|
|
179
180
|
| Issue happens for a CLASS of features | Update on-demand reference guide or command |
|
|
180
181
|
| Same manual step done 3+ times | Create a new command |
|
|
181
182
|
| Plan was ambiguous in the same spot twice | Update plan-feature command |
|
|
182
183
|
| First time seeing this issue | Note it, don't over-engineer yet |
|
|
183
184
|
|
|
184
|
-
## Step 7:
|
|
185
|
+
## Step 7: Suggest Next
|
|
185
186
|
|
|
186
|
-
|
|
187
|
+
Do **not** move or delete any files. The lifecycle closure (moving the plan to `done/`, folding delta-specs into canonical specs, deleting the ephemeral code review) is the responsibility of `/hopla:archive`.
|
|
187
188
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
mv "$1" .agents/plans/done/
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Then notify the user:
|
|
194
|
-
> "✅ System review saved to `.agents/system-reviews/[feature]-review.md`. Plan archived to `.agents/plans/done/[plan-name].md` — the active plans folder is now clean."
|
|
189
|
+
Notify the user:
|
|
190
|
+
> "✅ System review saved to `.agents/system-reviews/[feature]-review.md`. To close the lifecycle of this plan, run `/hopla:archive $1` — it will fold any delta-specs into the canonical specs and move the plan to `done/`."
|
package/commands/validate.md
CHANGED
|
@@ -16,7 +16,7 @@ If a `.claude/commands/validate.md` exists at the project root, use the commands
|
|
|
16
16
|
|
|
17
17
|
Execute levels **1–4** from `commands/guides/validation-pyramid.md` (same repo). Do not skip levels. Do not proceed if a level fails — fix it first.
|
|
18
18
|
|
|
19
|
-
Use the exact commands from the project's `CLAUDE.md` "Development Commands" section. If a `.claude/commands/validate.md` exists at the project root, use the commands defined there instead.
|
|
19
|
+
Use the exact commands from the project's `AGENTS.md` (or `CLAUDE.md` as fallback) "Development Commands" section. If a `.claude/commands/validate.md` exists at the project root, use the commands defined there instead.
|
|
20
20
|
|
|
21
21
|
## Step 3: Summary Report
|
|
22
22
|
|
package/hooks/session-prime.js
CHANGED
|
@@ -97,11 +97,19 @@ async function main() {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Project rules excerpt — prefer canonical AGENTS.md; fall back to CLAUDE.md.
|
|
101
|
+
// Both paths reuse the same excerpt helper since the format is identical Markdown.
|
|
102
|
+
const agentsMdPath = path.join(process.cwd(), "AGENTS.md");
|
|
101
103
|
const claudeMdPath = path.join(process.cwd(), "CLAUDE.md");
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
let rulesPath = null;
|
|
105
|
+
if (fs.existsSync(agentsMdPath)) {
|
|
106
|
+
rulesPath = { path: agentsMdPath, label: "AGENTS.md" };
|
|
107
|
+
} else if (fs.existsSync(claudeMdPath)) {
|
|
108
|
+
rulesPath = { path: claudeMdPath, label: "CLAUDE.md" };
|
|
109
|
+
}
|
|
110
|
+
if (rulesPath) {
|
|
111
|
+
const excerpt = excerptClaudeMd(fs.readFileSync(rulesPath.path, "utf8"));
|
|
112
|
+
lines.push(`Project rules (${rulesPath.label} excerpt):\n${excerpt}`);
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
// Auto-discover available skills
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ Explore the problem space and arrive at a validated design BEFORE creating an im
|
|
|
14
14
|
## Process
|
|
15
15
|
|
|
16
16
|
### Step 1: Explore Context
|
|
17
|
-
- Read the project's CLAUDE.md, README, and relevant source files
|
|
17
|
+
- Read the project's AGENTS.md (or CLAUDE.md as fallback), README, and relevant source files
|
|
18
18
|
- Understand the existing architecture, patterns, and conventions
|
|
19
19
|
- Identify what already exists that relates to this feature
|
|
20
20
|
- Check `.agents/plans/` for any related previous work
|
|
@@ -61,6 +61,20 @@ Which approach and why
|
|
|
61
61
|
## Design
|
|
62
62
|
Conceptual design details
|
|
63
63
|
|
|
64
|
+
## Requirements Delta
|
|
65
|
+
> **Include only when the feature changes documented system behavior** — i.e. it adds, modifies, or removes a user-visible capability or business rule. Pure refactors, perf fixes, and infra changes can omit this section. The delta is consumed by `/hopla:archive` to fold the change into `.agents/specs/canonical/`.
|
|
66
|
+
|
|
67
|
+
### ADDED Requirements
|
|
68
|
+
- REQ-<DOMAIN>-<NNN>: <short title>
|
|
69
|
+
- Scenario: <name> — Given <state>, When <action>, Then <outcome>
|
|
70
|
+
|
|
71
|
+
### MODIFIED Requirements
|
|
72
|
+
- REQ-<DOMAIN>-<NNN>: <short title> (replaces previous version)
|
|
73
|
+
- <description of how the requirement changes>
|
|
74
|
+
|
|
75
|
+
### REMOVED Requirements
|
|
76
|
+
- REQ-<DOMAIN>-<NNN>: <short title> (deprecated — reason)
|
|
77
|
+
|
|
64
78
|
## Files Affected
|
|
65
79
|
- List of files to create/modify
|
|
66
80
|
|
|
@@ -75,6 +89,8 @@ Conceptual design details
|
|
|
75
89
|
Run `/hopla:plan-feature` to create the implementation plan from this design
|
|
76
90
|
```
|
|
77
91
|
|
|
92
|
+
> **Requirement IDs:** use a stable convention like `REQ-<DOMAIN>-<NNN>` (e.g. `REQ-AUTH-002`). The domain prefix should match the canonical spec filename (`auth.md` → `REQ-AUTH-*`). When in doubt about IDs in a brand-new project, leave them as `REQ-AUTH-TBD` and pick numbers when the first canonical spec is created.
|
|
93
|
+
|
|
78
94
|
### Step 6: Review Loop
|
|
79
95
|
Present the design document for user review.
|
|
80
96
|
- Accept feedback using the `<? ... >` comment syntax
|
|
@@ -35,7 +35,7 @@ Apply every category to every changed file. Severity guidance is in the parent `
|
|
|
35
35
|
|
|
36
36
|
## 5. Pattern Adherence
|
|
37
37
|
|
|
38
|
-
- Follows project conventions from `CLAUDE.md`
|
|
38
|
+
- Follows project conventions from `AGENTS.md` (or `CLAUDE.md` as fallback)
|
|
39
39
|
- Consistent with existing codebase style
|
|
40
40
|
|
|
41
41
|
## 6. Route & Middleware Ordering
|
|
@@ -39,7 +39,7 @@ Use the full structure documented in `report-structure.md` (same directory). It
|
|
|
39
39
|
- Divergences from plan
|
|
40
40
|
- Scope assessment
|
|
41
41
|
- Skipped items
|
|
42
|
-
- Technical patterns discovered (with ready-to-paste CLAUDE.md entry)
|
|
42
|
+
- Technical patterns discovered (with ready-to-paste AGENTS.md / CLAUDE.md entry)
|
|
43
43
|
- Recommendations
|
|
44
44
|
|
|
45
45
|
Read `report-structure.md` before writing so every section is filled correctly.
|
|
@@ -75,7 +75,7 @@ New gotchas, patterns, or conventions learned during this implementation that sh
|
|
|
75
75
|
|
|
76
76
|
- **Pattern/Gotcha:** [description]
|
|
77
77
|
- **Where it applies:** [what type of feature or context triggers this]
|
|
78
|
-
- **Ready-to-paste CLAUDE.md entry:** [Write the EXACT text that should be added to the project's CLAUDE.md to prevent this gotcha in future features. Format it as a bullet point under the appropriate section. If it belongs in a guide instead, write the exact text for the guide. Do not write vague suggestions like "document this" — write the actual content so the system reviewer can apply it directly.]
|
|
78
|
+
- **Ready-to-paste AGENTS.md / CLAUDE.md entry:** [Write the EXACT text that should be added to the project's AGENTS.md (or CLAUDE.md for legacy projects) to prevent this gotcha in future features. Format it as a bullet point under the appropriate section. If it belongs in a guide instead, write the exact text for the guide. Do not write vague suggestions like "document this" — write the actual content so the system reviewer can apply it directly.]
|
|
79
79
|
|
|
80
80
|
If nothing new was discovered, write "No new patterns discovered."
|
|
81
81
|
|
|
@@ -85,4 +85,4 @@ Based on this implementation, what should change for next time?
|
|
|
85
85
|
|
|
86
86
|
- Plan command improvements: [suggestions]
|
|
87
87
|
- Execute command improvements: [suggestions]
|
|
88
|
-
- CLAUDE.md additions: [suggestions]
|
|
88
|
+
- AGENTS.md / CLAUDE.md additions: [suggestions]
|
package/skills/git/commit.md
CHANGED
|
@@ -47,14 +47,14 @@ Wait for explicit approval before running `git commit`.
|
|
|
47
47
|
|
|
48
48
|
## Step 5: Version Bump (if configured)
|
|
49
49
|
|
|
50
|
-
Before committing, check the project's `CLAUDE.md` for a `## Versioning` section. If it exists:
|
|
50
|
+
Before committing, check the project's `AGENTS.md` (or `CLAUDE.md` as fallback) for a `## Versioning` section. If it exists:
|
|
51
51
|
|
|
52
52
|
1. Read the versioning configuration (command, trigger, files)
|
|
53
53
|
2. Check if the **trigger condition** matches (e.g., specific branches, always, etc.)
|
|
54
54
|
3. If it matches, run the version bump command
|
|
55
55
|
4. Stage the version files alongside the other changes
|
|
56
56
|
|
|
57
|
-
If no `## Versioning` section exists in the project's `CLAUDE.md`, skip this step entirely.
|
|
57
|
+
If no `## Versioning` section exists in the project's `AGENTS.md` / `CLAUDE.md`, skip this step entirely.
|
|
58
58
|
|
|
59
59
|
## Step 6: Execute Commit
|
|
60
60
|
|
package/skills/git/pr.md
CHANGED
|
@@ -15,7 +15,7 @@ git diff --stat origin/$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>
|
|
|
15
15
|
|
|
16
16
|
Read the following if they exist:
|
|
17
17
|
- The plan file in `.agents/plans/` related to this feature
|
|
18
|
-
- `CLAUDE.md` — for project context
|
|
18
|
+
- `AGENTS.md` (or `CLAUDE.md` as fallback) — for project context
|
|
19
19
|
|
|
20
20
|
## Step 2: Determine Base Branch
|
|
21
21
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hook-audit
|
|
3
|
+
description: "Static audit of new React hooks against documented bug-class catalog (memoization, stale-id guards, error-match strictness, cache+dedup integrity). Use after creating any file matching `src/hooks/use*.ts` and BEFORE commit. Trigger words: 'audit hook', 'check hook', 'hook review'. Auto-callable from execute skill's Level 1.5 gate."
|
|
4
|
+
allowed-tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> 🌐 **Language:** All user-facing output must match the user's language. Code, paths, and commands stay in English.
|
|
8
|
+
|
|
9
|
+
Mechanical static audit of React hook files (`src/hooks/use*.ts`) against the four recurring bug classes catalogued in `checklist.md` (same directory). This skill is FAST (<5s per file) and structural — it does not interpret intent. For semantic / interpretive review, use `code-review` instead.
|
|
10
|
+
|
|
11
|
+
**When to invoke:**
|
|
12
|
+
- Right after creating or significantly modifying a `src/hooks/use*.ts` file
|
|
13
|
+
- BEFORE committing the hook
|
|
14
|
+
- Auto-callable from `commands/execute.md` Level 1.5 gate (companion plan: `plan-feature-04c-improvements.md`)
|
|
15
|
+
|
|
16
|
+
## Step 1: Identify Target File(s)
|
|
17
|
+
|
|
18
|
+
Two modes:
|
|
19
|
+
|
|
20
|
+
1. **Single-file mode** — argument provided:
|
|
21
|
+
```bash
|
|
22
|
+
/hopla:hook-audit src/hooks/useFoo.ts
|
|
23
|
+
```
|
|
24
|
+
Treat the argument as a single path. If the file does not exist, exit with `File not found: <path>`.
|
|
25
|
+
|
|
26
|
+
2. **Auto-detect mode** — no argument:
|
|
27
|
+
```bash
|
|
28
|
+
git diff --name-only HEAD | grep -E '^src/hooks/use[A-Z][A-Za-z0-9_]*\.ts$'
|
|
29
|
+
```
|
|
30
|
+
Collect the matching files. If the result is empty:
|
|
31
|
+
```bash
|
|
32
|
+
git ls-files --others --exclude-standard | grep -E '^src/hooks/use[A-Z][A-Za-z0-9_]*\.ts$'
|
|
33
|
+
```
|
|
34
|
+
to pick up new untracked hook files. If both are empty, exit cleanly with `No hook files to audit.`
|
|
35
|
+
|
|
36
|
+
Scope is strict: only `src/hooks/use*.ts`. Files matching `src/lib/use*` or `electron/use*` are NOT audited (see Out of Scope in the plan).
|
|
37
|
+
|
|
38
|
+
## Step 2: Load the Checklist
|
|
39
|
+
|
|
40
|
+
Read `checklist.md` in this skill's directory. It documents each rule with:
|
|
41
|
+
- Rule code (P-5 / S-8 / E-1 / D-1)
|
|
42
|
+
- Severity
|
|
43
|
+
- Detection pattern (regex or grep command)
|
|
44
|
+
- Fix suggestion (with code example)
|
|
45
|
+
- Canonical reference (file from consumer-project history)
|
|
46
|
+
|
|
47
|
+
## Step 3: Apply Each Rule
|
|
48
|
+
|
|
49
|
+
For each target file, run the detection command for every rule (see `checklist.md` for the exact commands). Collect findings.
|
|
50
|
+
|
|
51
|
+
Detection commands must be portable across BSD grep (macOS) and GNU grep (Linux). Use `grep -E` for ERE syntax. Avoid GNU-only flags (`-P`, `-z`).
|
|
52
|
+
|
|
53
|
+
Each finding records:
|
|
54
|
+
- File path
|
|
55
|
+
- Line number (use `grep -nE` to get line numbers)
|
|
56
|
+
- Rule code
|
|
57
|
+
- One-line description
|
|
58
|
+
- Fix suggestion (from `checklist.md`)
|
|
59
|
+
- Canonical reference
|
|
60
|
+
|
|
61
|
+
## Step 4: Output Report
|
|
62
|
+
|
|
63
|
+
Group findings by file. Format per finding:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
[severity] [rule-code] file:line — description
|
|
67
|
+
Fix: suggestion
|
|
68
|
+
Reference: canonical-file.ts (commit-hash)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
End with a one-line summary:
|
|
72
|
+
```
|
|
73
|
+
N issues found across M files. K rules clean.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If no issues found, exit with exactly:
|
|
77
|
+
```
|
|
78
|
+
Hook audit clean — 4 rules checked.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Gate output budget:** When invoked from `execute.md` Level 1.5 gate, the report must be under 50 lines. If more than 5 issues per file, show a count + the top 3 findings with detail; collapse the rest as `+N more (same rule)`.
|
|
82
|
+
|
|
83
|
+
## Step 5: Exit Code
|
|
84
|
+
|
|
85
|
+
This skill is markdown — it does not exit directly. When the calling agent invokes the skill from `execute.md`'s gate (via Bash), the calling Bash invocation should:
|
|
86
|
+
- Exit `0` when the summary line shows `Hook audit clean`
|
|
87
|
+
- Exit `1` when any issues are reported
|
|
88
|
+
|
|
89
|
+
The gate (defined in `plan-feature-04c-improvements.md`) decides whether to fail the commit or merely warn based on severity. This skill's job is to REPORT — not to enforce.
|
|
90
|
+
|
|
91
|
+
## Example Output
|
|
92
|
+
|
|
93
|
+
**Clean run** (0 issues):
|
|
94
|
+
```
|
|
95
|
+
$ /hopla:hook-audit src/hooks/useGradingDefinitions.ts
|
|
96
|
+
Hook audit clean — 4 rules checked.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**3-issue run:**
|
|
100
|
+
```
|
|
101
|
+
$ /hopla:hook-audit src/hooks/useFooLookup.ts
|
|
102
|
+
|
|
103
|
+
[high] [P-5] src/hooks/useFooLookup.ts:142 — Hook returns object literal without useMemo
|
|
104
|
+
Fix: Wrap the return in useMemo({ ... }, [field1, field2, ...])
|
|
105
|
+
Reference: useGradingDefinitions.ts:123-127
|
|
106
|
+
|
|
107
|
+
[high] [S-8] src/hooks/useFooLookup.ts:88 — `setLoading(false)` inside stale-id guard
|
|
108
|
+
Fix: Move setLoading(false) out of `if (currentFooRef.current === foo)` block in the finally
|
|
109
|
+
Reference: useSickwLookup.ts (commit 228cd6a)
|
|
110
|
+
|
|
111
|
+
[medium] [E-1] src/hooks/useFooLookup.ts:101 — Error matching uses substring `.includes('imei')`
|
|
112
|
+
Fix: Replace with exact match or anchored regex (`/^imei[: ]/`)
|
|
113
|
+
Reference: useImeiInOtherShipment.ts post-fix
|
|
114
|
+
|
|
115
|
+
3 issues found across 1 file. 1 rule clean (D-1).
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Source Incidents
|
|
119
|
+
|
|
120
|
+
This skill exists because the same four bug classes recurred across consumer projects. Each rule in `checklist.md` cites the canonical fixed sibling.
|
|
121
|
+
|
|
122
|
+
| Occurrence | File | Rule(s) | Fix commit |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| 1 | PhoneTest `useSickwLookup` | S-8 | 228cd6a |
|
|
125
|
+
| 2 | PhoneTest `useIfreeicloudLookup` | S-8 | 06cc692 |
|
|
126
|
+
| 3 | ifreeicloud-library `useIfreeicloudLookup` (second pass) | P-5, S-8 | — |
|
|
127
|
+
| 4 | 04c `useImeiInOtherShipment`, `useActiveShipment` | P-5, S-8, E-1 | 952cab1 |
|
|
128
|
+
|
|
129
|
+
Recurrence count = motivation for a dedicated mechanical pre-flight skill, separate from the broader `code-review`.
|
|
130
|
+
|
|
131
|
+
## Next Step
|
|
132
|
+
|
|
133
|
+
After the audit, the calling agent (execute, or the user via slash command) should:
|
|
134
|
+
- If clean: proceed to commit.
|
|
135
|
+
- If issues found: fix per the suggestions, then re-run this skill until clean. For larger refactors, escalate to `code-review` for full semantic review.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Hook Audit Checklist
|
|
2
|
+
|
|
3
|
+
Four mechanical rules. Each rule has a portable detection command (BSD + GNU grep), a fix suggestion, and a canonical reference from consumer-project history.
|
|
4
|
+
|
|
5
|
+
> **Maintainer note on canonical references:** The cited commit hashes and file names come from a specific consumer project (PhoneTest-Desktop-App). When this skill is used in other consumer projects, the references may not resolve locally — that's fine, they are documentation of the pattern's origin, not a hard dependency. Update them as patterns evolve (revisit when promoting to v2).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Rule P-5 — Hook return must be memoized
|
|
10
|
+
|
|
11
|
+
**Severity:** high
|
|
12
|
+
|
|
13
|
+
**What it catches:** A hook function that ends with `return { ... };` (object literal) NOT wrapped in `useMemo`. Every render returns a new object, which invalidates downstream `useMemo` / `useEffect` deps that consume the hook's return, causing re-render storms and stale-closure bugs.
|
|
14
|
+
|
|
15
|
+
**Scope filter (apply BEFORE flagging):** The rule applies only to the FINAL return statement of a function whose name starts with `use` and that calls React hooks (`useState`, `useRef`, `useMemo`, `useEffect`, etc.). Plain helper functions inside the same file (e.g. `async function fakeFetch(...)`) are NOT subject to this rule even if they return an object literal.
|
|
16
|
+
|
|
17
|
+
**Detection (portable grep):**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Step 1 — collect candidate `return { ... }` lines (heuristic; may include helpers)
|
|
21
|
+
grep -nE 'return[[:space:]]*\{' "$FILE"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For each match, read the surrounding 20 lines and apply two filters:
|
|
25
|
+
1. Is the enclosing function a React hook (named `useX`, uses React hooks)? If no → skip.
|
|
26
|
+
2. Is the `return { ... }` block already inside a `useMemo(() => ({ ... }), [...])` wrapper? If yes → skip.
|
|
27
|
+
|
|
28
|
+
Only flag matches that fail BOTH filters. The wrapper pattern looks like:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
return useMemo(() => ({
|
|
32
|
+
field1,
|
|
33
|
+
field2,
|
|
34
|
+
}), [field1, field2]);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If the `return { ... }` is NOT inside a `useMemo`, flag it.
|
|
38
|
+
|
|
39
|
+
**Fix:**
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// Before
|
|
43
|
+
return {
|
|
44
|
+
foo,
|
|
45
|
+
bar,
|
|
46
|
+
doSomething,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// After
|
|
50
|
+
return useMemo(() => ({
|
|
51
|
+
foo,
|
|
52
|
+
bar,
|
|
53
|
+
doSomething,
|
|
54
|
+
}), [foo, bar, doSomething]);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Include every field used in the object literal in the dep array. Missing deps cause stale values.
|
|
58
|
+
|
|
59
|
+
**Canonical reference:** `useGradingDefinitions.ts:123-127` (consumer projects). Look for the well-formed `return useMemo(() => ({ ... }), [...])` shape there as the model.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Rule S-8 — `setLoading(false)` must not be gated by stale-id guard
|
|
64
|
+
|
|
65
|
+
**Severity:** high
|
|
66
|
+
|
|
67
|
+
**What it catches:** Inside a `finally` block, `setLoading(false)` is wrapped in an `if (currentXRef.current === X)` guard. The guard correctly prevents stale results from overwriting newer ones, but ALSO blocks `setLoading(false)` from running when the request is superseded — leaving the UI stuck in "loading" forever when the user rapidly switches inputs.
|
|
68
|
+
|
|
69
|
+
**Detection (portable grep):**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Find any line with `setLoading(false)` inside the file, then read 5 lines above to check for an `if (current*Ref.current === ...)` guard
|
|
73
|
+
grep -nE 'setLoading\(false\)' "$FILE"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For each match, read 5–10 lines above and confirm whether the call is enclosed by:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
if (currentSomethingRef.current === somethingId) {
|
|
80
|
+
// ... result handling ...
|
|
81
|
+
setLoading(false); // BUG: blocked when request is superseded
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If the `setLoading(false)` is INSIDE the guard, flag it. If it's OUTSIDE the guard (i.e. after the closing brace), the file is clean for this rule.
|
|
86
|
+
|
|
87
|
+
**Fix:**
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// Before — bug: setLoading blocked when superseded
|
|
91
|
+
} finally {
|
|
92
|
+
if (currentImeiRef.current === imei) {
|
|
93
|
+
setData(result);
|
|
94
|
+
setLoading(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// After — setLoading runs unconditionally; only data update is guarded
|
|
99
|
+
} finally {
|
|
100
|
+
if (currentImeiRef.current === imei) {
|
|
101
|
+
setData(result);
|
|
102
|
+
}
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Canonical reference:** `useSickwLookup.ts` after commit `228cd6a` (PhoneTest history). The fix moved `setLoading(false)` out of the guard.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Rule E-1 — Error matching must be anchored, not substring
|
|
112
|
+
|
|
113
|
+
**Severity:** medium
|
|
114
|
+
|
|
115
|
+
**What it catches:** Error message detection that uses `.includes('imei')` or similar substring patterns. A substring match can fire on unrelated error messages that happen to contain the token (e.g. `"server error: imei lookup deferred"` would match an `imei`-specific error handler intended only for `"invalid imei format"`).
|
|
116
|
+
|
|
117
|
+
**Detection (portable grep):**
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
grep -nE "\.message\??\.includes\([\"'][a-z]+[\"']\)" "$FILE"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This regex matches the antipattern `error.message.includes('foo')` (optional chaining tolerated). Each hit is a finding.
|
|
124
|
+
|
|
125
|
+
**Fix:**
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// Before — substring match (too permissive)
|
|
129
|
+
if (error.message.includes('imei')) {
|
|
130
|
+
// ...
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// After — anchored regex (precise)
|
|
134
|
+
if (/^(invalid )?imei( format)?\b/i.test(error.message)) {
|
|
135
|
+
// ...
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Alternative — exact match if the message is a known constant
|
|
139
|
+
if (error.message === 'Invalid IMEI') {
|
|
140
|
+
// ...
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Canonical reference:** Consumer-project review checklist Section 1, pattern documented after the `useImeiInOtherShipment.ts` error-overshadow incident.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Rule D-1 — Module-level cache must have in-flight dedup
|
|
149
|
+
|
|
150
|
+
**Severity:** medium
|
|
151
|
+
|
|
152
|
+
**What it catches:** A hook file declares a module-level `cache` Map for response caching but does NOT also declare an `inFlight` Map. Without dedup, two parallel calls for the same key race and produce duplicate network requests.
|
|
153
|
+
|
|
154
|
+
**Detection (portable grep):**
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Find module-level `const cache = new Map`
|
|
158
|
+
grep -nE '^const cache[[:space:]]*=[[:space:]]*new Map' "$FILE"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
If the file matches, check for the companion `inFlight` map:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
grep -nE '^const inFlight[[:space:]]*=[[:space:]]*new Map' "$FILE"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
If `cache` is present but `inFlight` is absent, flag it. Both declarations must be at module scope (no leading whitespace).
|
|
168
|
+
|
|
169
|
+
**Fix:**
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// Both maps at module scope
|
|
173
|
+
const cache = new Map<string, ResultShape>();
|
|
174
|
+
const inFlight = new Map<string, Promise<ResultShape>>();
|
|
175
|
+
|
|
176
|
+
async function fetchOnce(key: string): Promise<ResultShape> {
|
|
177
|
+
const cached = cache.get(key);
|
|
178
|
+
if (cached) return cached;
|
|
179
|
+
|
|
180
|
+
const inflight = inFlight.get(key);
|
|
181
|
+
if (inflight) return inflight;
|
|
182
|
+
|
|
183
|
+
const promise = doFetch(key).then((result) => {
|
|
184
|
+
cache.set(key, result);
|
|
185
|
+
inFlight.delete(key);
|
|
186
|
+
return result;
|
|
187
|
+
});
|
|
188
|
+
inFlight.set(key, promise);
|
|
189
|
+
return promise;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The companion `inFlight` map ensures parallel calls for the same key share a single in-flight Promise instead of racing.
|
|
194
|
+
|
|
195
|
+
**Canonical reference:** Consumer-project lookup hooks pattern (e.g. `useSickwLookup.ts` post-`228cd6a`).
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Portability Notes
|
|
200
|
+
|
|
201
|
+
All detection commands use BRE/ERE syntax supported by both:
|
|
202
|
+
- BSD grep (macOS default — `/usr/bin/grep`)
|
|
203
|
+
- GNU grep (most Linux distributions)
|
|
204
|
+
|
|
205
|
+
Avoid:
|
|
206
|
+
- `-P` (Perl-compatible — GNU only)
|
|
207
|
+
- `-z` (null-separated — GNU only)
|
|
208
|
+
- Lookahead/lookbehind (`(?=...)`, `(?<=...)`)
|
|
209
|
+
|
|
210
|
+
Use `grep -nE` for line numbers + ERE. Use `grep -E` instead of `egrep` (the latter is deprecated on some systems).
|