@hopla/claude-setup 1.17.1 → 2.1.1
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 +2 -1
- package/.claude-plugin/plugin.json +4 -3
- package/CHANGELOG.md +123 -0
- package/LICENSE +21 -0
- package/README.md +65 -39
- package/agents/system-reviewer.md +4 -4
- package/cli.js +288 -8
- package/commands/archive.md +137 -0
- package/commands/execute.md +1 -1
- package/commands/guides/ai-optimized-codebase.md +5 -1
- package/commands/guides/data-audit.md +4 -0
- package/commands/guides/hooks-reference.md +4 -0
- package/commands/guides/mcp-integration.md +6 -2
- package/commands/guides/remote-coding.md +5 -1
- package/commands/guides/review-checklist.md +4 -0
- package/commands/guides/scaling-beyond-engineering.md +4 -0
- package/commands/guides/validation-pyramid.md +5 -1
- package/commands/guides/write-skill.md +4 -0
- 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/prompt-route.js +244 -91
- package/hooks/session-prime.js +12 -4
- package/hooks/statusline.js +3 -5
- package/hooks/tsc-check.js +40 -3
- package/package.json +16 -6
- package/skills/brainstorm/SKILL.md +18 -2
- package/skills/code-review/SKILL.md +5 -0
- package/skills/code-review/checklist.md +1 -1
- package/skills/debug/SKILL.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/performance/SKILL.md +1 -1
- package/skills/prime/SKILL.md +2 -2
- package/skills/refactoring/SKILL.md +1 -1
- package/skills/subagent-execution/SKILL.md +2 -2
- package/skills/verify/SKILL.md +1 -1
- package/skills/worktree/SKILL.md +2 -2
package/commands/init-project.md
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Initialize a new project with
|
|
2
|
+
description: Initialize a new project with AGENTS.md (+ CLAUDE.md alias) and .agents/ structure
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
> **Language:** All user-facing output must match the user's language. Code, paths, and commands stay in English.
|
|
6
6
|
|
|
7
|
-
Set up the Layer 1 planning foundation for this project: a project-specific `
|
|
7
|
+
Set up the Layer 1 planning foundation for this project: a project-specific `AGENTS.md` with rules and architecture decisions (the canonical, tool-agnostic source of truth), a thin `CLAUDE.md` alias so Claude Code auto-loads the rules, plus the `.agents/` directory structure.
|
|
8
8
|
|
|
9
|
-
> Layer 1 = Global Rules (~/.claude/CLAUDE.md) + Project Rules (CLAUDE.md) + PRD
|
|
9
|
+
> Layer 1 = Global Rules (~/.claude/CLAUDE.md) + Project Rules (AGENTS.md, with CLAUDE.md alias) + PRD
|
|
10
|
+
|
|
11
|
+
> **Why AGENTS.md as the canonical file:** AGENTS.md is the tool-agnostic convention adopted by most AI coding assistants (Cursor, Copilot, Continue, Codex, etc.). Keeping a thin CLAUDE.md alias preserves Claude Code's auto-discovery without duplicating content. If the project already has an AGENTS.md or CLAUDE.md, treat that as the canonical file and only create the missing alias.
|
|
10
12
|
|
|
11
13
|
## Step 1: Read Existing Context
|
|
12
14
|
|
|
13
15
|
Before asking anything, check what already exists:
|
|
14
|
-
- Any existing `CLAUDE.md` at project root
|
|
16
|
+
- Any existing `AGENTS.md` or `CLAUDE.md` at project root (AGENTS.md takes precedence as canonical; CLAUDE.md without AGENTS.md is treated as canonical until migrated)
|
|
15
17
|
- `README.md` — extract stack and project overview
|
|
16
18
|
- `package.json`, `pyproject.toml`, or equivalent — extract stack and scripts
|
|
17
19
|
- Entry point files (`main.py`, `src/main.ts`, `app.py`, etc.)
|
|
18
20
|
|
|
19
|
-
If
|
|
21
|
+
If an `AGENTS.md` or `CLAUDE.md` already exists at the project root, tell the user and ask if they want to update it or start fresh. If only `CLAUDE.md` exists (legacy layout), offer to migrate: rename to `AGENTS.md` and create a thin `CLAUDE.md` alias.
|
|
20
22
|
|
|
21
23
|
## Step 2: Understand the Product
|
|
22
24
|
|
|
@@ -166,11 +168,23 @@ Once the stack is confirmed, ask only what's NOT already known from the PRD or c
|
|
|
166
168
|
2. **Project description** — skip if already in PRD
|
|
167
169
|
3. **Reference Guides** (optional) — Are there specific task types that need step-by-step guidance? (e.g. "When adding a page", "When creating an API route"). If none, skip — guides can always be added later.
|
|
168
170
|
|
|
169
|
-
## Step 5: Generate CLAUDE.md
|
|
171
|
+
## Step 5: Generate AGENTS.md (+ CLAUDE.md alias)
|
|
172
|
+
|
|
173
|
+
Save the full project rules to `AGENTS.md` at the project root (canonical, tool-agnostic source of truth). Then create a thin `CLAUDE.md` alias so Claude Code auto-discovers the rules without duplicating content.
|
|
174
|
+
|
|
175
|
+
**`CLAUDE.md` alias contents (always identical, regardless of stack):**
|
|
176
|
+
|
|
177
|
+
```markdown
|
|
178
|
+
# [Project Name] — Project Rules
|
|
179
|
+
|
|
180
|
+
The canonical project rules live in [`AGENTS.md`](./AGENTS.md). This file is a thin alias kept for Claude Code's auto-discovery.
|
|
181
|
+
|
|
182
|
+
@AGENTS.md
|
|
183
|
+
```
|
|
170
184
|
|
|
171
|
-
|
|
185
|
+
> The `@AGENTS.md` directive instructs Claude Code to inline the AGENTS.md contents into context. Other AI assistants read AGENTS.md directly.
|
|
172
186
|
|
|
173
|
-
|
|
187
|
+
**`AGENTS.md` contents — for default stack projects**, use these pre-filled values:
|
|
174
188
|
|
|
175
189
|
```markdown
|
|
176
190
|
# [Project Name] — Development Rules
|
|
@@ -315,7 +329,9 @@ Read: `.agents/guides/[guide-name].md`
|
|
|
315
329
|
This guide covers: [bullet list]
|
|
316
330
|
```
|
|
317
331
|
|
|
318
|
-
**For custom stack projects**, fill in the values collected during Step 2.2 following the same structure.
|
|
332
|
+
**For custom stack projects**, fill in the values collected during Step 2.2 / Step 3.1 following the same structure.
|
|
333
|
+
|
|
334
|
+
> Both default and custom flows write to `AGENTS.md`. The `CLAUDE.md` alias is the same in both cases.
|
|
319
335
|
|
|
320
336
|
## Step 5.5: Generate Reference Guides
|
|
321
337
|
|
|
@@ -407,7 +423,7 @@ After implementing, verify:
|
|
|
407
423
|
|
|
408
424
|
**Important:** Guides must contain concrete, project-specific information — not generic advice. If the user's answers don't have enough detail for a section, ask a follow-up before writing the guide.
|
|
409
425
|
|
|
410
|
-
Also update the `
|
|
426
|
+
Also update the `AGENTS.md` Section 7 (Task-Specific Reference Guides) to reference each guide created:
|
|
411
427
|
|
|
412
428
|
```markdown
|
|
413
429
|
## 7. Task-Specific Reference Guides
|
|
@@ -428,9 +444,11 @@ Create the following directories (with `.gitkeep` where needed):
|
|
|
428
444
|
```
|
|
429
445
|
.agents/
|
|
430
446
|
├── plans/ <- /hopla:plan-feature saves here (commit)
|
|
431
|
-
│ ├── done/ <- /hopla:
|
|
447
|
+
│ ├── done/ <- /hopla:archive moves completed plans here (commit)
|
|
432
448
|
│ └── backlog/ <- /hopla:execute Scope Guard defers ideas here (commit)
|
|
433
449
|
├── specs/ <- brainstorm skill saves design docs here (commit)
|
|
450
|
+
│ ├── canonical/ <- canonical "current behavior" specs by domain (commit; populated incrementally by /hopla:archive)
|
|
451
|
+
│ └── archived/ <- /hopla:archive moves completed design specs here (commit)
|
|
434
452
|
├── guides/ <- on-demand reference guides (commit)
|
|
435
453
|
├── rca/ <- /hopla:rca saves root cause analysis docs here (commit)
|
|
436
454
|
├── execution-reports/ <- the `execution-report` skill saves here (commit — needed for cross-session learning)
|
|
@@ -439,6 +457,8 @@ Create the following directories (with `.gitkeep` where needed):
|
|
|
439
457
|
└── code-reviews/ <- the `code-review` skill saves here (do NOT commit — ephemeral, consumed by code-review-fix)
|
|
440
458
|
```
|
|
441
459
|
|
|
460
|
+
> **`specs/canonical/` is opt-in.** It is populated only as `/hopla:archive` is used. Until the first archive runs, the directory simply stays empty. Projects that prefer to keep all behavior knowledge in code + AGENTS.md can ignore it.
|
|
461
|
+
|
|
442
462
|
**Policy — `audits/` vs `code-reviews/`:**
|
|
443
463
|
|
|
444
464
|
- `code-reviews/` is **ephemeral working state**. Every run overwrites/adds files; `code-review-fix` consumes them and they become stale fast. Never commit.
|
|
@@ -475,13 +495,14 @@ If yes, create `.claude/commands/validate.md`.
|
|
|
475
495
|
|
|
476
496
|
## Step 8: Confirm and Save
|
|
477
497
|
|
|
478
|
-
Show the draft `
|
|
498
|
+
Show the draft `AGENTS.md` to the user and ask:
|
|
479
499
|
> "Does this accurately reflect the project's rules? Any corrections before I save it?"
|
|
480
500
|
|
|
481
501
|
Once confirmed:
|
|
482
|
-
1. Save `
|
|
483
|
-
2. Create
|
|
484
|
-
3.
|
|
485
|
-
4.
|
|
502
|
+
1. Save `AGENTS.md` to the project root
|
|
503
|
+
2. Create `CLAUDE.md` at the project root with the standard alias content (see Step 5) — skip if a meaningful `CLAUDE.md` already exists; in that case ask the user whether to overwrite with the alias stub or leave it untouched
|
|
504
|
+
3. Create `.agents/` directory structure
|
|
505
|
+
4. Update `.gitignore`
|
|
506
|
+
5. If no PRD exists yet, tell the user: "Project initialized. Run `/hopla:create-prd` next to define the product scope, or `/hopla:plan-feature` to start planning a feature."
|
|
486
507
|
If a PRD already exists, tell the user: "Project initialized. Run `/hopla:plan-feature` to start planning the first feature."
|
|
487
|
-
|
|
508
|
+
6. Suggest running the `git` skill (say "commit") to save everything
|
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/prompt-route.js
CHANGED
|
@@ -1,94 +1,242 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// UserPromptSubmit hook: scan the user's prompt for skill keywords and inject
|
|
3
|
-
// a short routing hint.
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
3
|
+
// a short routing hint. Reads skill catalog from disk at runtime — no
|
|
4
|
+
// hardcoded skill list — so adding/removing/renaming a skill needs no edit here.
|
|
5
|
+
//
|
|
6
|
+
// Matching strategy (hybrid):
|
|
7
|
+
// 1. If a SKILL.md declares `triggers: [regex, ...]` in its frontmatter,
|
|
8
|
+
// use those patterns literally.
|
|
9
|
+
// 2. Otherwise auto-derive patterns from the skill name (word-boundary,
|
|
10
|
+
// case-insensitive) and the first significant words of the description.
|
|
11
|
+
//
|
|
12
|
+
// Skill discovery walks `../skills/` recursively so nested skills
|
|
13
|
+
// (e.g. skills/guides/<name>/SKILL.md) participate without code changes.
|
|
14
|
+
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
|
|
18
|
+
const SKILLS_DIR = path.join(import.meta.dirname, "..", "skills");
|
|
19
|
+
const STOPWORDS = new Set([
|
|
20
|
+
"use", "when", "with", "that", "this", "from", "into", "your", "have",
|
|
21
|
+
"also", "user", "only", "before", "after", "during", "than", "such",
|
|
22
|
+
"more", "less", "most", "least", "some", "many", "much", "still", "just",
|
|
23
|
+
"what", "which", "would", "could", "should", "needs", "need", "make",
|
|
24
|
+
"made", "does", "done", "doing", "wants", "want", "next", "previous",
|
|
25
|
+
"very", "really", "always", "never", "must", "may", "might", "shall",
|
|
26
|
+
"trigger", "triggers", "phrase", "phrases", "skill", "skills", "do",
|
|
27
|
+
"not", "the", "and", "for", "are", "was", "were", "but", "any", "all",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
// Walk skills/ recursively. Return [{ name, descriptors, triggers, path }].
|
|
31
|
+
// Each entry already exposes either a literal triggers list (from frontmatter)
|
|
32
|
+
// or a derived list (from name + description tokens).
|
|
33
|
+
function loadSkillCatalog(skillsRoot) {
|
|
34
|
+
if (!fs.existsSync(skillsRoot)) return [];
|
|
35
|
+
const catalog = [];
|
|
36
|
+
|
|
37
|
+
function walk(dir) {
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const fullPath = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
const candidate = path.join(fullPath, "SKILL.md");
|
|
48
|
+
if (fs.existsSync(candidate)) {
|
|
49
|
+
const parsed = parseSkillFrontmatter(candidate);
|
|
50
|
+
if (parsed) catalog.push(parsed);
|
|
51
|
+
}
|
|
52
|
+
// Continue walking — skills may be nested under sub-directories.
|
|
53
|
+
walk(fullPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
walk(skillsRoot);
|
|
59
|
+
return catalog;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract `name`, `description`, optional `triggers` from a SKILL.md frontmatter.
|
|
63
|
+
// Tolerant parser — handles double/single-quoted scalars and YAML-style array
|
|
64
|
+
// `triggers: [...]` on a single line or with each item on its own line.
|
|
65
|
+
function parseSkillFrontmatter(filePath) {
|
|
66
|
+
let content;
|
|
67
|
+
try {
|
|
68
|
+
content = fs.readFileSync(filePath, "utf8");
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!content.startsWith("---")) return null;
|
|
73
|
+
const end = content.indexOf("\n---", 3);
|
|
74
|
+
if (end === -1) return null;
|
|
75
|
+
const block = content.slice(3, end);
|
|
76
|
+
|
|
77
|
+
const data = {};
|
|
78
|
+
const lines = block.split("\n");
|
|
79
|
+
let i = 0;
|
|
80
|
+
while (i < lines.length) {
|
|
81
|
+
const line = lines[i];
|
|
82
|
+
const m = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
83
|
+
if (!m) { i++; continue; }
|
|
84
|
+
const key = m[1];
|
|
85
|
+
let rest = m[2];
|
|
86
|
+
|
|
87
|
+
if (key === "triggers") {
|
|
88
|
+
// Inline form: triggers: [a, b] or block form across lines.
|
|
89
|
+
const triggers = parseYamlArray(rest, lines, i);
|
|
90
|
+
data.triggers = triggers.values;
|
|
91
|
+
i = triggers.nextIndex;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Strip surrounding quotes if any.
|
|
96
|
+
if ((rest.startsWith('"') && rest.endsWith('"')) ||
|
|
97
|
+
(rest.startsWith("'") && rest.endsWith("'"))) {
|
|
98
|
+
rest = rest.slice(1, -1);
|
|
99
|
+
}
|
|
100
|
+
data[key] = rest;
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!data.name) {
|
|
105
|
+
// Fall back to the parent directory name when frontmatter omits it.
|
|
106
|
+
data.name = path.basename(path.dirname(filePath));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
name: data.name,
|
|
111
|
+
description: data.description || "",
|
|
112
|
+
triggers: Array.isArray(data.triggers) ? data.triggers : null,
|
|
113
|
+
path: filePath,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Parses either `[a, b, c]` on the same line as `triggers:`, or a block form:
|
|
118
|
+
// triggers:
|
|
119
|
+
// - "regex one"
|
|
120
|
+
// - "regex two"
|
|
121
|
+
// Returns { values, nextIndex } so the caller knows where to resume scanning.
|
|
122
|
+
function parseYamlArray(inlineRest, lines, currentIndex) {
|
|
123
|
+
const inline = inlineRest.trim();
|
|
124
|
+
if (inline.startsWith("[") && inline.endsWith("]")) {
|
|
125
|
+
const body = inline.slice(1, -1).trim();
|
|
126
|
+
if (!body) return { values: [], nextIndex: currentIndex + 1 };
|
|
127
|
+
const values = splitYamlInlineList(body);
|
|
128
|
+
return { values, nextIndex: currentIndex + 1 };
|
|
129
|
+
}
|
|
130
|
+
// Block form
|
|
131
|
+
const values = [];
|
|
132
|
+
let j = currentIndex + 1;
|
|
133
|
+
while (j < lines.length) {
|
|
134
|
+
const ln = lines[j];
|
|
135
|
+
const m = ln.match(/^\s*-\s*(.*)$/);
|
|
136
|
+
if (!m) break;
|
|
137
|
+
let item = m[1].trim();
|
|
138
|
+
if ((item.startsWith('"') && item.endsWith('"')) ||
|
|
139
|
+
(item.startsWith("'") && item.endsWith("'"))) {
|
|
140
|
+
item = item.slice(1, -1);
|
|
141
|
+
}
|
|
142
|
+
values.push(item);
|
|
143
|
+
j++;
|
|
144
|
+
}
|
|
145
|
+
return { values, nextIndex: j };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Splits `"a, b", c, "d"` respecting double-quoted strings so commas inside
|
|
149
|
+
// quotes don't break the split.
|
|
150
|
+
function splitYamlInlineList(body) {
|
|
151
|
+
const out = [];
|
|
152
|
+
let current = "";
|
|
153
|
+
let inQuote = null;
|
|
154
|
+
for (let k = 0; k < body.length; k++) {
|
|
155
|
+
const ch = body[k];
|
|
156
|
+
if (inQuote) {
|
|
157
|
+
if (ch === inQuote) {
|
|
158
|
+
inQuote = null;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
current += ch;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (ch === '"' || ch === "'") {
|
|
165
|
+
inQuote = ch;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (ch === ",") {
|
|
169
|
+
const item = current.trim();
|
|
170
|
+
if (item) out.push(item);
|
|
171
|
+
current = "";
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
current += ch;
|
|
175
|
+
}
|
|
176
|
+
const tail = current.trim();
|
|
177
|
+
if (tail) out.push(tail);
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Build the regex list a skill matches against. When the SKILL.md provides
|
|
182
|
+
// `triggers:`, use those literal patterns. Otherwise auto-derive from:
|
|
183
|
+
// 1. The skill name (case-insensitive, word boundary).
|
|
184
|
+
// 2. Every single-quoted phrase in the description — descriptions in this
|
|
185
|
+
// project document trigger phrases as `'commit', 'pull request', ...`,
|
|
186
|
+
// so this captures idiomatic user wording the skill name alone misses.
|
|
187
|
+
// 3. The first 3 significant tokens of the description (length ≥ 4, not
|
|
188
|
+
// a stopword) — a fallback when no quoted phrases exist.
|
|
189
|
+
function buildPatterns(skill) {
|
|
190
|
+
if (skill.triggers && skill.triggers.length > 0) {
|
|
191
|
+
return skill.triggers.map(toRegex).filter(Boolean);
|
|
192
|
+
}
|
|
193
|
+
const patterns = [];
|
|
194
|
+
const description = skill.description || "";
|
|
195
|
+
|
|
196
|
+
if (skill.name) {
|
|
197
|
+
const escaped = skill.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
198
|
+
patterns.push(new RegExp(`\\b${escaped}\\b`, "i"));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Extract single-quoted phrases like 'commit', 'pull request'. Limited to
|
|
202
|
+
// 12 to bound the per-prompt regex work.
|
|
203
|
+
const quoted = [...description.matchAll(/'([^']{2,60})'/g)].map((m) => m[1]);
|
|
204
|
+
const seenPhrase = new Set();
|
|
205
|
+
for (const phrase of quoted) {
|
|
206
|
+
const norm = phrase.toLowerCase().trim();
|
|
207
|
+
if (!norm || seenPhrase.has(norm)) continue;
|
|
208
|
+
seenPhrase.add(norm);
|
|
209
|
+
const escaped = norm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
210
|
+
patterns.push(new RegExp(`\\b${escaped}\\b`, "i"));
|
|
211
|
+
if (seenPhrase.size >= 12) break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (seenPhrase.size === 0) {
|
|
215
|
+
// Fallback: significant tokens.
|
|
216
|
+
const tokens = description
|
|
217
|
+
.toLowerCase()
|
|
218
|
+
.split(/[^a-z0-9-]+/)
|
|
219
|
+
.filter((t) => t.length >= 4 && !STOPWORDS.has(t));
|
|
220
|
+
const seenTok = new Set();
|
|
221
|
+
for (const tok of tokens) {
|
|
222
|
+
if (seenTok.has(tok)) continue;
|
|
223
|
+
seenTok.add(tok);
|
|
224
|
+
const escaped = tok.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
225
|
+
patterns.push(new RegExp(`\\b${escaped}\\b`, "i"));
|
|
226
|
+
if (seenTok.size >= 3) break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return patterns;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function toRegex(source) {
|
|
233
|
+
if (!source || typeof source !== "string") return null;
|
|
234
|
+
try {
|
|
235
|
+
return new RegExp(source, "i");
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
92
240
|
|
|
93
241
|
async function main() {
|
|
94
242
|
const chunks = [];
|
|
@@ -106,9 +254,14 @@ async function main() {
|
|
|
106
254
|
const prompt = (input.prompt || "").slice(0, 4000);
|
|
107
255
|
if (!prompt) process.exit(0);
|
|
108
256
|
|
|
257
|
+
const catalog = loadSkillCatalog(SKILLS_DIR);
|
|
258
|
+
if (catalog.length === 0) process.exit(0);
|
|
259
|
+
|
|
109
260
|
const matched = [];
|
|
110
|
-
for (const
|
|
111
|
-
|
|
261
|
+
for (const skill of catalog) {
|
|
262
|
+
const patterns = buildPatterns(skill);
|
|
263
|
+
if (patterns.length === 0) continue;
|
|
264
|
+
if (patterns.some((p) => p.test(prompt))) matched.push(skill.name);
|
|
112
265
|
}
|
|
113
266
|
|
|
114
267
|
if (matched.length === 0) process.exit(0);
|
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/hooks/statusline.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Hopla statusline: branch · worktree indicator · uncommitted count · active plan.
|
|
3
|
-
// Wire it up by
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// "command": "node ~/.claude/plugins/marketplaces/hopla-marketplace/hooks/statusline.js"
|
|
7
|
-
// }
|
|
3
|
+
// Wire it up by running:
|
|
4
|
+
// hopla-claude-setup --setup-statusline
|
|
5
|
+
// Remove with: hopla-claude-setup --remove-statusline
|
|
8
6
|
|
|
9
7
|
import { execSync } from "child_process";
|
|
10
8
|
import fs from "fs";
|