@hopla/claude-setup 1.19.0 → 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.
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/claude-code-marketplace.json",
2
3
  "name": "hopla-marketplace",
3
4
  "description": "Hopla Tools plugin marketplace for Claude Code",
4
5
  "owner": {
@@ -9,7 +10,7 @@
9
10
  {
10
11
  "name": "hopla",
11
12
  "description": "Agentic coding system: PIV loop, TDD, debugging, brainstorming, subagent execution, and team workflows",
12
- "version": "1.19.0",
13
+ "version": "2.1.1",
13
14
  "source": "./",
14
15
  "author": {
15
16
  "name": "Hopla Tools",
@@ -1,13 +1,14 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
2
3
  "name": "hopla",
3
4
  "description": "Agentic coding system for Claude Code: PIV loop (Plan → Implement → Validate), TDD, debugging, brainstorming, subagent execution, and team workflows",
4
- "version": "1.19.0",
5
+ "version": "2.1.1",
5
6
  "author": {
6
7
  "name": "Hopla Tools",
7
8
  "email": "julio@hopla.tools"
8
9
  },
9
- "homepage": "https://github.com/hopla-tools/claude-setup",
10
- "repository": "https://github.com/hopla-tools/claude-setup",
10
+ "homepage": "https://github.com/HOPLAtools/claude-setup#readme",
11
+ "repository": "https://github.com/HOPLAtools/claude-setup",
11
12
  "license": "MIT",
12
13
  "keywords": [
13
14
  "claude",
package/CHANGELOG.md ADDED
@@ -0,0 +1,123 @@
1
+ # Changelog
2
+
3
+ All notable changes to **@hopla/claude-setup** are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [2.1.1] - 2026-05-12
10
+
11
+ ### Fixed
12
+ - `hopla-claude-setup --migrate` and `--uninstall` now clean up legacy guide duplicates left in `~/.claude/commands/guides/` by pre-plugin CLI versions (≤ v1.11.x). Removes only files whose name matches a plugin-shipped guide — custom user guides in the same directory are preserved. Empty `guides/` directories are then removed.
13
+
14
+ ### Notes
15
+ - This addresses the duplicate `/guides:*` entries (marked `(user)`) that appeared alongside `/hopla:guides:*` (marked `(hopla)`) in the slash-command autocomplete after upgrading from a pre-plugin install.
16
+
17
+ ## [2.1.0] - 2026-05-12
18
+
19
+ ### Added
20
+ - `hopla-claude-setup --setup-statusline` and `--remove-statusline` flags: the CLI now wires the Hopla statusline into `~/.claude/settings.json` automatically. `uninstall` removes it. Manual JSON snippet is documented as a fallback only.
21
+ - `node:test` test suite (`npm test`) — 34 unit + integration tests covering `cli.js` helpers (`parseSettingsFile`, `safeWrite`, the new `setupStatusline`/`removeStatusline`) and the three core hooks (`env-protect`, `tsc-check`, `prompt-route`). Zero external dependencies — uses Node's built-in test runner.
22
+ - GitHub Actions CI (`.github/workflows/ci.yml`): runs on every PR and push to main — JSON validation, version sync check, `npm test`, CLI dry-runs, and the `hook-audit` manual smoke test.
23
+ - `CONTRIBUTING.md` with local development, testing, and PR guidelines.
24
+ - `SECURITY.md` with the vulnerability disclosure process and the plugin's threat model (which hooks intercept which tool calls).
25
+ - `CODE_OF_CONDUCT.md` (Contributor Covenant v2.1).
26
+ - `.github/PULL_REQUEST_TEMPLATE.md` and `.github/ISSUE_TEMPLATE/{bug_report,feature_request}.md`.
27
+ - Frontmatter `description:` on the 9 guides in `commands/guides/` so they appear with hints in the slash-command autocomplete.
28
+
29
+ ### Changed
30
+ - **i18n cleanup** — the plugin is now language-agnostic. Spanish trigger phrases removed from the descriptions of `brainstorm`, `verify`, `performance`, `debug` skills. Users configure their preferred response language in their own `~/.claude/CLAUDE.md`.
31
+ - **`hooks/prompt-route.js` refactored to a hybrid matcher**: the hardcoded `SKILL_TRIGGERS` array is gone. The hook discovers skills at runtime by reading `SKILL.md` files; matching uses an optional `triggers:` frontmatter override, falling back to auto-derive from the skill name and quoted phrases in the description. New skills are matched without code changes.
32
+ - **`hooks/tsc-check.js` filters by file extension**: editing a `.md` file no longer triggers `tsc --noEmit`. Only `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, `.cts`, `.mjs`, `.cjs` files invoke the check.
33
+ - `cli.js`: `parseSettingsFile` and `safeWrite` are now exported for testing. The main dispatcher is gated by an `isMainModule` check so the file can be imported as a library by tests.
34
+ - `hooks/statusline.js` header comment points users at the new CLI flag rather than a hardcoded JSON snippet.
35
+ - README "Optional: Hopla statusline" section recommends the CLI flag; manual JSON setup kept as a fallback note.
36
+ - `CLAUDE.md` §5 (Testing) and §6 (Release flow) now describe `npm test` and the CI verification step.
37
+ - `hooks/prompt-route.js` `triggers:` override added to `skills/code-review/SKILL.md` so phrases like "review my code" (with words between "review" and "code") match the skill.
38
+
39
+ ### Removed
40
+ - Temporary `.DS_Store` artifact at repo root (already in `.gitignore`).
41
+
42
+ ### Notes for plugin authors
43
+ - Skill discovery in Claude Code v1.24 does **not** support nested `skills/<subdir>/<name>/SKILL.md` paths. The 9 reference guides remain in `commands/guides/*.md` and ship with `description:` frontmatter. A future migration to `skills/` will revisit this if the platform adds support.
44
+
45
+ ## [2.0.0] - 2026-05-12
46
+
47
+ ### Added
48
+ - `LICENSE` (MIT) in repo root for legal clarity in npm + GitHub.
49
+ - `description`, `repository`, `homepage`, `bugs` fields in `package.json` for proper npm registry display.
50
+ - `$schema` field in `plugin.json` and `marketplace.json` for JSON Schema validation in editors.
51
+ - This `CHANGELOG.md` (full history back to 1.13.0 below).
52
+ - `LICENSE` and `CHANGELOG.md` are now included in the published npm tarball via `files[]`.
53
+
54
+ ### Changed
55
+ - **Breaking:** repository slug in `plugin.json` corrected from `hopla-tools/claude-setup` (404) to `HOPLAtools/claude-setup` (the real GitHub org).
56
+ - `plugin.json` `homepage` differentiated from `repository`: now points to `#readme` anchor.
57
+ - README install snippet simplified: `/plugin marketplace add HOPLAtools/claude-setup` (one argument — the canonical form per Anthropic docs).
58
+ - All CLI references in README updated from `claude-setup` to `hopla-claude-setup`.
59
+
60
+ ### Removed
61
+ - **Breaking:** bin alias `claude-setup` removed from `package.json`. The package now exposes only `hopla-claude-setup`. Migration: replace any script that calls `claude-setup` with `hopla-claude-setup` — same flags, same behavior. Rationale: the generic name risked colliding with other npm packages distributed publicly.
62
+
63
+ ## [1.19.0] - 2026-05-11
64
+
65
+ ### Added
66
+ - New `hopla:hook-audit` skill — mechanical pre-commit checks for new React hooks against 4 documented bug classes (P-5 memoization, S-8 stale-id guard, E-1 error matching, D-1 cache + dedup integrity).
67
+
68
+ ### Changed
69
+ - Documentation aligned with the canonical `/plugin marketplace update <name>` flow introduced in Claude Code v1.24+. The older `cd … && git pull` dance is no longer prescribed.
70
+
71
+ ## [1.18.0] - 2026-05-11
72
+
73
+ ### Added
74
+ - OpenSpec wave 1: `AGENTS.md` (canonical project rules) with a CLAUDE.md alias path.
75
+ - `.agents/specs/canonical/` for living requirements documents.
76
+ - `/hopla:archive` command — folds delta-specs from completed plans into canonical specs and moves artifacts to archive locations.
77
+ - `claude-setup status` subcommand — read-only inspection of the current project's `.agents/` workflow (plans, specs, reviews, suggested next step). JSON output available via `--json`.
78
+
79
+ ## [1.17.1] - 2026-05-01
80
+
81
+ ### Changed
82
+ - `plan-feature` now enforces UX iteration budget declaration when the feature touches a visible UI surface.
83
+ - `plan-feature` now requires a `## Domain Assumptions` section when the feature uses project-specific vocabulary.
84
+
85
+ ## [1.17.0] - 2026-04-17
86
+
87
+ ### Added
88
+ - New skills: `refactoring`, `performance`, `migration`.
89
+ - Spec linking — plans can reference and update canonical specs.
90
+ - `.agents/audits/` directory for audit artifacts.
91
+
92
+ ## [1.16.0] - 2026-04-17
93
+
94
+ ### Changed
95
+ - Refactor: progressive disclosure across skills, shared references between skills (e.g. `worktree` cites `git/flow-detection.md`), cleaner telemetry.
96
+
97
+ ## [1.15.0] - 2026-04-17
98
+
99
+ ### Added
100
+ - MCP server template (`.mcp.json.template`).
101
+ - New hooks: `UserPromptSubmit` (skill routing hints), `PreCompact` (session state snapshot).
102
+ - Optional statusline renderer (`hooks/statusline.js`) showing branch · worktree indicator · uncommitted count · active plan.
103
+
104
+ ## [1.14.1] - 2026-04-17
105
+
106
+ ### Fixed
107
+ - 7 critical audit findings (see commit `69c025b` for the full list).
108
+
109
+ ## [1.14.0] - 2026-04-17
110
+
111
+ ### Added
112
+ - CLI `--dry-run` flag that composes with any other flag.
113
+ - CLI cleans legacy agents (`code-reviewer.md`, `codebase-researcher.md`, `system-reviewer.md`) and legacy `PLANNING_PERMISSIONS` from older installs.
114
+ - CLI prints an advisory when the plugin or marketplace cache is still present (the CLI cannot remove those — the user does).
115
+
116
+ ## [1.13.0] - 2026-04-17
117
+
118
+ ### Added
119
+ - `git` skill integrated with `worktree` skill via centralized Git Flow detection in `flow-detection.md`.
120
+
121
+ ---
122
+
123
+ For releases prior to 1.13.0, see the git history (`git log --oneline`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Hopla Tools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -24,7 +24,7 @@ The full setup is **plugin + CLI** — they deliver different layers:
24
24
  **Step 1 — Register the marketplace and install the plugin** (inside Claude Code):
25
25
 
26
26
  ```
27
- /plugin marketplace add hopla-marketplace HOPLAtools/claude-setup
27
+ /plugin marketplace add HOPLAtools/claude-setup
28
28
  /plugin install hopla@hopla-marketplace
29
29
  ```
30
30
 
@@ -32,11 +32,15 @@ The full setup is **plugin + CLI** — they deliver different layers:
32
32
 
33
33
  ```bash
34
34
  npm install -g @hopla/claude-setup
35
- claude-setup --force
35
+ hopla-claude-setup --force
36
36
  ```
37
37
 
38
38
  That's it. Commands show as `/hopla:<name>`, skills auto-trigger when relevant.
39
39
 
40
+ **Recommended — enable auto-update for the marketplace** (one-time, inside Claude Code):
41
+
42
+ Open `/plugin` → **Marketplaces** tab → select `hopla-marketplace` → enable auto-update. From then on, new releases are fetched automatically at session start; Claude Code will prompt you to run `/reload-plugins` when an update is ready. Skip this step and you'll need to refresh manually (see [Update](#update) below).
43
+
40
44
  ### Plugin only (skip global rules)
41
45
 
42
46
  If you don't want the machine-wide `~/.claude/CLAUDE.md`, just do Step 1. The plugin works on its own.
@@ -51,28 +55,25 @@ If you only want the global rules and not the plugin's commands/skills, just do
51
55
 
52
56
  ### Plugin channel
53
57
 
54
- Claude Code caches the marketplace repo locally. To pick up new versions:
55
-
56
- ```bash
57
- cd ~/.claude/plugins/marketplaces/hopla-marketplace && git pull
58
- ```
58
+ **If auto-update is enabled** (see Quick Start Step 2.5): nothing to do. Claude Code fetches new releases at session start and prompts you to run `/reload-plugins`. That's it.
59
59
 
60
- Then inside Claude Code:
60
+ **Manual refresh** (when auto-update is off, or you want to pull immediately):
61
61
 
62
62
  ```
63
- /plugin uninstall hopla@hopla-marketplace
64
- /plugin install hopla@hopla-marketplace
63
+ /plugin marketplace update hopla-marketplace
64
+ /plugin disable hopla@hopla-marketplace
65
+ /plugin enable hopla@hopla-marketplace
65
66
  /reload-plugins
66
67
  ```
67
68
 
68
- > **Known issue:** Claude Code does not automatically `git pull` the marketplace when reinstalling a plugin. The manual `git pull` above is required.
69
+ `/plugin marketplace update` refreshes the cached marketplace listing. `disable`/`enable` cycles the plugin so the new files are picked up — faster than a full `uninstall`/`install` because no scope re-selection is needed. `/reload-plugins` ensures hooks and commands are re-registered in the current session.
69
70
 
70
- If the `cd` path does not exist, you never installed the marketplace do Step 1 of Quick Start instead.
71
+ > The older `cd ~/.claude/plugins/marketplaces/hopla-marketplace && git pull` workflow still works but is no longer recommended — the built-in `/plugin marketplace update` is the canonical path since Claude Code v1.24.
71
72
 
72
73
  ### CLI channel (global rules)
73
74
 
74
75
  ```bash
75
- npm install -g @hopla/claude-setup@latest --prefer-online && claude-setup --force
76
+ npm install -g @hopla/claude-setup@latest --prefer-online && hopla-claude-setup --force
76
77
  ```
77
78
 
78
79
  ---
@@ -88,7 +89,7 @@ npm install -g @hopla/claude-setup@latest --prefer-online && claude-setup --forc
88
89
  **CLI:**
89
90
 
90
91
  ```bash
91
- claude-setup --uninstall
92
+ hopla-claude-setup --uninstall
92
93
  ```
93
94
 
94
95
  Removes `~/.claude/CLAUDE.md` plus legacy `hopla-*` files from older installs.
@@ -97,14 +98,14 @@ Removes `~/.claude/CLAUDE.md` plus legacy `hopla-*` files from older installs.
97
98
 
98
99
  | Flag | Purpose |
99
100
  |---|---|
100
- | `claude-setup` | Interactive install of global rules + permissions |
101
- | `claude-setup --force` | Install without prompts |
102
- | `claude-setup --migrate` | Remove legacy CLI-installed duplicates only |
103
- | `claude-setup --uninstall` | Remove global rules + legacy files |
104
- | `claude-setup status` | Read-only inspection of the current project's `.agents/` workflow state (plans, specs, reviews, suggested next step) |
105
- | `claude-setup status --json` | Same as above, JSON output for agents to parse |
106
- | `claude-setup --dry-run` | Preview changes without touching disk (composes with other flags) |
107
- | `claude-setup --version` | Print package version |
101
+ | `hopla-claude-setup` | Interactive install of global rules + permissions |
102
+ | `hopla-claude-setup --force` | Install without prompts |
103
+ | `hopla-claude-setup --migrate` | Remove legacy CLI-installed duplicates only |
104
+ | `hopla-claude-setup --uninstall` | Remove global rules + legacy files |
105
+ | `hopla-claude-setup status` | Read-only inspection of the current project's `.agents/` workflow state (plans, specs, reviews, suggested next step) |
106
+ | `hopla-claude-setup status --json` | Same as above, JSON output for agents to parse |
107
+ | `hopla-claude-setup --dry-run` | Preview changes without touching disk (composes with other flags) |
108
+ | `hopla-claude-setup --version` | Print package version |
108
109
 
109
110
  ---
110
111
 
@@ -112,21 +113,33 @@ Removes `~/.claude/CLAUDE.md` plus legacy `hopla-*` files from older installs.
112
113
 
113
114
  The plugin ships a statusline script that shows your branch, worktree indicator, uncommitted count, and active plan file (`📋 plan-name`) in Claude Code's status bar.
114
115
 
115
- Enable it by adding to `~/.claude/settings.json`:
116
+ **Enable it (one-time):**
116
117
 
117
- ```json
118
- {
119
- "statusLine": {
120
- "type": "command",
121
- "command": "node ~/.claude/plugins/marketplaces/hopla-marketplace/hooks/statusline.js"
122
- }
123
- }
118
+ ```bash
119
+ hopla-claude-setup --setup-statusline
124
120
  ```
125
121
 
126
- Then run `/reload-plugins` or restart Claude Code.
122
+ This writes the right `statusLine` block to `~/.claude/settings.json`, pointing at the installed plugin's `statusline.js`. Run `/reload-plugins` (or restart Claude Code) to see it.
123
+
124
+ **Disable it:**
125
+
126
+ ```bash
127
+ hopla-claude-setup --remove-statusline
128
+ ```
127
129
 
128
130
  Sample output: ` feature/auth · 3M · 📋 add-authentication`
129
131
 
132
+ > **Manual setup (fallback):** if you don't have the CLI installed, add this to `~/.claude/settings.json` yourself:
133
+ > ```json
134
+ > {
135
+ > "statusLine": {
136
+ > "type": "command",
137
+ > "command": "node ~/.claude/plugins/marketplaces/hopla-marketplace/hooks/statusline.js"
138
+ > }
139
+ > }
140
+ > ```
141
+ > Replace `hopla-marketplace` with the actual marketplace name if you registered it differently.
142
+
130
143
  ---
131
144
 
132
145
  ## Naming Convention
@@ -379,7 +392,7 @@ Commands are modular — the output of one becomes the input of the next. Some a
379
392
  ### `/plugin install` doesn't show the `hopla` plugin
380
393
 
381
394
  - Run `/plugin marketplace list` — confirm `hopla-marketplace` is registered
382
- - If missing, re-run `/plugin marketplace add hopla-marketplace HOPLAtools/claude-setup`
395
+ - If missing, re-run `/plugin marketplace add HOPLAtools/claude-setup`
383
396
  - Restart Claude Code after registering a new marketplace
384
397
 
385
398
  ### Commands appear as both `hopla-*` and `hopla:*`
@@ -387,7 +400,7 @@ Commands are modular — the output of one becomes the input of the next. Some a
387
400
  You have legacy CLI-installed files from before the plugin refactor. Clean up:
388
401
 
389
402
  ```bash
390
- claude-setup --migrate
403
+ hopla-claude-setup --migrate
391
404
  ```
392
405
 
393
406
  This removes `hopla-*` duplicates from `~/.claude/commands/` and `~/.claude/skills/` without touching your global rules.
@@ -401,7 +414,7 @@ cd ~/.claude/plugins/marketplaces/hopla-marketplace && git pull
401
414
 
402
415
  You never registered the marketplace. Run Quick Start Step 1 first.
403
416
 
404
- ### `claude-setup: command not found`
417
+ ### `hopla-claude-setup: command not found`
405
418
 
406
419
  The npm global bin is not on your PATH. Check with:
407
420
 
@@ -412,6 +425,8 @@ npm config get prefix
412
425
 
413
426
  Alternatively, run via npx: `npx @hopla/claude-setup --force`.
414
427
 
428
+ > **Migrating from v1.x?** The `claude-setup` bin alias was removed in v2.0.0. Replace any script that calls `claude-setup` with `hopla-claude-setup` (same flags, same behavior).
429
+
415
430
  ### Changes to skills/commands don't take effect
416
431
 
417
432
  Claude Code caches plugin content. After a plugin update:
package/cli.js CHANGED
@@ -13,6 +13,8 @@ const VERSION = process.argv.includes("--version") || process.argv.includes("-v"
13
13
  const DRY_RUN = process.argv.includes("--dry-run");
14
14
  const STATUS = process.argv.includes("status");
15
15
  const JSON_OUT = process.argv.includes("--json");
16
+ const SETUP_STATUSLINE = process.argv.includes("--setup-statusline");
17
+ const REMOVE_STATUSLINE = process.argv.includes("--remove-statusline");
16
18
 
17
19
  if (VERSION) {
18
20
  const pkg = JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"));
@@ -27,6 +29,10 @@ const HOOKS_DIR = path.join(CLAUDE_DIR, "hooks");
27
29
  const AGENTS_DIR = path.join(CLAUDE_DIR, "agents");
28
30
  const PLUGINS_DIR = path.join(CLAUDE_DIR, "plugins");
29
31
  const MARKETPLACE_CACHE = path.join(PLUGINS_DIR, "marketplaces", "hopla-marketplace");
32
+ const STATUSLINE_SCRIPT = path.join(MARKETPLACE_CACHE, "hooks", "statusline.js");
33
+ // Substring used to identify a statusLine block managed by this plugin.
34
+ // Survives manual renames of the marketplace as long as users keep "hopla" in the path.
35
+ const STATUSLINE_MARKER = "hopla-marketplace";
30
36
  const SETTINGS_FILES = [
31
37
  path.join(CLAUDE_DIR, "settings.json"),
32
38
  path.join(CLAUDE_DIR, "settings.local.json"),
@@ -55,8 +61,9 @@ function safeRm(target, opts = {}) {
55
61
 
56
62
  // Atomic write: stage to a tmp file and rename over the target.
57
63
  // Protects ~/.claude/settings.json from corruption if the process crashes mid-write.
58
- function safeWrite(target, content) {
59
- if (DRY_RUN) return;
64
+ // Exported for testing — when imported as a library, callers can pass dryRun explicitly.
65
+ export function safeWrite(target, content, { dryRun = DRY_RUN } = {}) {
66
+ if (dryRun) return;
60
67
  const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
61
68
  try {
62
69
  fs.writeFileSync(tmp, content);
@@ -94,7 +101,8 @@ function logRemoved(label) {
94
101
  // missing. Warns (and returns null) when the file exists but is not valid JSON
95
102
  // — previously these failures were silently swallowed, causing cleanup and
96
103
  // permission updates to skip with no signal to the user.
97
- function parseSettingsFile(settingsPath) {
104
+ // Exported for unit testing.
105
+ export function parseSettingsFile(settingsPath) {
98
106
  if (!fs.existsSync(settingsPath)) return null;
99
107
  try {
100
108
  return JSON.parse(fs.readFileSync(settingsPath, "utf8"));
@@ -147,6 +155,23 @@ const LEGACY_HOOK_COMMANDS = [
147
155
  "session-prime.js",
148
156
  ];
149
157
 
158
+ // Guide files the pre-plugin CLI (≤ v1.11.x) copied to ~/.claude/commands/guides/.
159
+ // The plugin now ships them via commands/guides/, namespaced as /hopla:guides:<name>,
160
+ // so the user-level copies show up as duplicates in autocomplete (no "(hopla)" suffix).
161
+ // Cleanup removes only files whose name matches a plugin-shipped guide, leaving any
162
+ // custom user guides in ~/.claude/commands/guides/ untouched.
163
+ const LEGACY_GUIDE_FILES = [
164
+ "ai-optimized-codebase.md",
165
+ "data-audit.md",
166
+ "hooks-reference.md",
167
+ "mcp-integration.md",
168
+ "remote-coding.md",
169
+ "review-checklist.md",
170
+ "scaling-beyond-engineering.md",
171
+ "validation-pyramid.md",
172
+ "write-skill.md",
173
+ ];
174
+
150
175
  // Agents installed directly by v1.11.0 and v1.12.0 (no hopla- prefix)
151
176
  // Must be cleaned up so the plugin-provided versions are the only source of truth
152
177
  const LEGACY_AGENT_FILES = [
@@ -191,6 +216,26 @@ function removeLegacyFiles() {
191
216
  }
192
217
  }
193
218
 
219
+ // Legacy guide duplicates in ~/.claude/commands/guides/ (pre-plugin CLI).
220
+ // Only remove files matching a guide the plugin currently ships; preserve
221
+ // any custom user-created guides in the same directory.
222
+ const legacyGuidesDir = path.join(COMMANDS_DIR, "guides");
223
+ if (fs.existsSync(legacyGuidesDir)) {
224
+ for (const guideFile of LEGACY_GUIDE_FILES) {
225
+ const guidePath = path.join(legacyGuidesDir, guideFile);
226
+ if (fs.existsSync(guidePath)) {
227
+ safeRm(guidePath);
228
+ removed.push(`~/.claude/commands/guides/${guideFile}`);
229
+ }
230
+ }
231
+ if (!DRY_RUN) {
232
+ try {
233
+ const remaining = fs.readdirSync(legacyGuidesDir);
234
+ if (remaining.length === 0) fs.rmSync(legacyGuidesDir, { recursive: true });
235
+ } catch { /* ignore */ }
236
+ }
237
+ }
238
+
194
239
  // hopla-* skills
195
240
  if (fs.existsSync(SKILLS_DIR)) {
196
241
  for (const entry of fs.readdirSync(SKILLS_DIR)) {
@@ -372,6 +417,11 @@ async function uninstall() {
372
417
  logRemoved(item);
373
418
  }
374
419
 
420
+ const statuslineRemoved = removeStatuslineFromSettings();
421
+ for (const item of statuslineRemoved) {
422
+ logRemoved(item);
423
+ }
424
+
375
425
  log(`\n${GREEN}${BOLD}Done!${RESET} ${DRY_RUN ? "Dry-run complete — no files were changed." : "CLI-managed files removed."}\n`);
376
426
 
377
427
  if (pluginActive) {
@@ -425,6 +475,82 @@ async function setupPermissions() {
425
475
  log(` ${GREEN}✓${RESET} Permissions configured.\n`);
426
476
  }
427
477
 
478
+ async function setupStatusline() {
479
+ log(`\n${BOLD}@hopla/claude-setup${RESET} — Setup statusline${dryTag()}\n`);
480
+
481
+ if (!fs.existsSync(STATUSLINE_SCRIPT)) {
482
+ log(`${RED}✕${RESET} Plugin not detected at ${STATUSLINE_SCRIPT}`);
483
+ log(` Install the plugin first inside Claude Code:`);
484
+ log(` ${CYAN}/plugin marketplace add HOPLAtools/claude-setup${RESET}`);
485
+ log(` ${CYAN}/plugin install hopla@hopla-marketplace${RESET}\n`);
486
+ process.exit(1);
487
+ }
488
+
489
+ const settingsPath = path.join(CLAUDE_DIR, "settings.json");
490
+ let settings = parseSettingsFile(settingsPath);
491
+ if (!settings) {
492
+ if (fs.existsSync(settingsPath)) {
493
+ log(` ${YELLOW}↷${RESET} Skipped — settings.json is not valid JSON. Fix it and re-run.\n`);
494
+ return;
495
+ }
496
+ settings = {};
497
+ }
498
+
499
+ const command = `node ${STATUSLINE_SCRIPT}`;
500
+ const newBlock = { type: "command", command };
501
+
502
+ const existing = settings.statusLine;
503
+ if (existing && typeof existing === "object") {
504
+ const isOurs = typeof existing.command === "string" && existing.command.includes(STATUSLINE_MARKER);
505
+ if (isOurs && existing.command === command) {
506
+ log(`${GREEN}✓${RESET} Statusline already configured (idempotent — no changes).\n`);
507
+ return;
508
+ }
509
+ if (!isOurs) {
510
+ log(` ${YELLOW}⚠${RESET} Existing statusLine points elsewhere:`);
511
+ log(` ${existing.command || JSON.stringify(existing)}`);
512
+ const ok = await confirm(` Overwrite with Hopla statusline? (y/N) `);
513
+ if (!ok) {
514
+ log(` ${YELLOW}↷${RESET} Kept existing statusLine. No changes.\n`);
515
+ return;
516
+ }
517
+ }
518
+ }
519
+
520
+ settings.statusLine = newBlock;
521
+ safeWrite(settingsPath, JSON.stringify(settings, null, 2) + "\n");
522
+ log(` ${GREEN}✓${RESET} ${DRY_RUN ? "Would write" : "Wrote"} statusLine to ~/.claude/settings.json:`);
523
+ log(` ${CYAN}${command}${RESET}\n`);
524
+ }
525
+
526
+ function removeStatuslineFromSettings() {
527
+ const removed = [];
528
+ const settingsPath = path.join(CLAUDE_DIR, "settings.json");
529
+ const settings = parseSettingsFile(settingsPath);
530
+ if (!settings || !settings.statusLine) return removed;
531
+
532
+ const cmd = settings.statusLine.command;
533
+ if (typeof cmd === "string" && cmd.includes(STATUSLINE_MARKER)) {
534
+ delete settings.statusLine;
535
+ safeWrite(settingsPath, JSON.stringify(settings, null, 2) + "\n");
536
+ removed.push(`statusLine from settings.json`);
537
+ }
538
+ return removed;
539
+ }
540
+
541
+ async function removeStatusline() {
542
+ log(`\n${BOLD}@hopla/claude-setup${RESET} — Remove statusline${dryTag()}\n`);
543
+ const removed = removeStatuslineFromSettings();
544
+ if (removed.length === 0) {
545
+ log(`${GREEN}✓${RESET} No Hopla statusline found. Nothing to remove.\n`);
546
+ return;
547
+ }
548
+ for (const item of removed) {
549
+ logRemoved(item);
550
+ }
551
+ log("");
552
+ }
553
+
428
554
  async function install() {
429
555
  log(`\n${BOLD}@hopla/claude-setup${RESET} — Global Rules Setup${dryTag()}\n`);
430
556
 
@@ -594,8 +720,21 @@ function status() {
594
720
 
595
721
  const run = STATUS
596
722
  ? async () => status()
723
+ : SETUP_STATUSLINE
724
+ ? setupStatusline
725
+ : REMOVE_STATUSLINE
726
+ ? removeStatusline
597
727
  : (UNINSTALL ? uninstall : (MIGRATE ? migrate : install));
598
- run().catch((err) => {
599
- console.error("Failed:", err.message);
600
- process.exit(1);
601
- });
728
+
729
+ // Only invoke the dispatcher when this file is executed directly (e.g. via
730
+ // `node cli.js` or the npm bin). When the file is imported as a library
731
+ // (tests), skip — tests call the exported helpers themselves.
732
+ import { pathToFileURL } from "url";
733
+ const isMainModule = process.argv[1]
734
+ && import.meta.url === pathToFileURL(process.argv[1]).href;
735
+ if (isMainModule) {
736
+ run().catch((err) => {
737
+ console.error("Failed:", err.message);
738
+ process.exit(1);
739
+ });
740
+ }
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Guide for structuring codebases to be navigable and editable by AI agents (file layout, naming, comments, doc placement).
3
+ ---
4
+
1
5
  # AI-Optimized Codebase Guide
2
6
 
3
7
  ## When to Use This Guide
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Reference for auditing existing data sources (schema, value semantics, null cases, derived value propagation) before implementing data-consuming features.
3
+ ---
4
+
1
5
  # Guide: Data Audit and Value Semantics
2
6
 
3
7
  Use this guide when a feature reads from, calculates with, or references existing data or code patterns. Applies during planning (full audit) and execution (verification of documented findings).
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Reference for creating Claude Code hooks — event names, matchers, payload shapes, exit code contracts, and stdout conventions.
3
+ ---
4
+
1
5
  # Hooks Reference Guide
2
6
 
3
7
  ## When to Use This Guide
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Reference for integrating Model Context Protocol (MCP) servers with Claude Code projects — config, auth, and common patterns.
3
+ ---
4
+
1
5
  # MCP Integration Guide
2
6
 
3
7
  ## When to Use This Guide
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Forward-looking guide for remote agentic coding workflows — long-running agents, async coordination, sandboxed execution.
3
+ ---
4
+
1
5
  # Remote Agentic Coding Guide (Future)
2
6
 
3
7
  ## When to Use This Guide
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Guide for creating a project-specific code review checklist (.agents/guides/review-checklist.md) consumed by the code-review skill.
3
+ ---
4
+
1
5
  # Guide: Creating a Project-Specific Review Checklist
2
6
 
3
7
  Use this guide to create a `.agents/guides/review-checklist.md` file in your project with code review checks specific to your tech stack, domain, and known anti-patterns. The the `code-review` skill command loads this file automatically when it exists, applying your custom checks alongside the standard review categories.
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Guide for applying agentic coding workflows beyond software engineering — operations, content, research, customer support.
3
+ ---
4
+
1
5
  # Scaling Agentic Coding Beyond Engineering
2
6
 
3
7
  ## When to Use This Guide
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Shared reference for the full validation sequence (lint, types, tests, code review, manual smoke). Used by execute, validate, and verify.
3
+ ---
4
+
1
5
  # Validation Pyramid
2
6
 
3
7
  Shared reference for the full validation sequence. Callers (`commands/execute.md`, `commands/validate.md`, `skills/verify/SKILL.md`, plus plans' `Validation Checklist`) pick the levels that apply to their scope.
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Internal guide for authoring new skills in this plugin — SKILL.md frontmatter, naming, triggers, organization, when to add triggers override.
3
+ ---
4
+
1
5
  # Writing Skills Guide (Internal)
2
6
 
3
7
  ## When to Use This Guide
@@ -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. Keeps skill suggestions visible mid-session even after
4
- // compaction or long conversations where the initial skill list gets buried.
5
-
6
- const SKILL_TRIGGERS = [
7
- {
8
- skill: "git",
9
- patterns: [
10
- /\bcommit\b/i,
11
- /save (my |the )?changes/i,
12
- /\bcreate (a )?pr\b/i,
13
- /pull request/i,
14
- /\bpush\b/i,
15
- /merge request/i,
16
- ],
17
- },
18
- {
19
- skill: "worktree",
20
- patterns: [
21
- /\bworktree\b/i,
22
- /isolated branch/i,
23
- /parallel (feature|development)/i,
24
- ],
25
- },
26
- {
27
- skill: "prime",
28
- patterns: [
29
- /\borient(\b| yourself)/i,
30
- /catch me up/i,
31
- /get context/i,
32
- /load project/i,
33
- /what is this project/i,
34
- ],
35
- },
36
- {
37
- skill: "brainstorm",
38
- patterns: [
39
- /\bbrainstorm\b/i,
40
- /explore (options|approaches)/i,
41
- /how should we/i,
42
- /trade[- ]offs?/i,
43
- ],
44
- },
45
- {
46
- skill: "debug",
47
- patterns: [
48
- /\bbug\b/i,
49
- /\berror\b/i,
50
- /\bdebug\b/i,
51
- /not working/i,
52
- /\bfailing\b/i,
53
- /\bbroken\b/i,
54
- /\bno funciona\b/i,
55
- ],
56
- },
57
- {
58
- skill: "verify",
59
- patterns: [
60
- /\bverify\b/i,
61
- /all tests? pass/i,
62
- /\blisto\b/i,
63
- /\bterminé\b/i,
64
- /\bya está\b/i,
65
- ],
66
- },
67
- {
68
- skill: "code-review",
69
- patterns: [
70
- /review (the |my )?code/i,
71
- /code review/i,
72
- /audit (the |my )?code/i,
73
- ],
74
- },
75
- {
76
- skill: "execution-report",
77
- patterns: [
78
- /generate (the |a )?report/i,
79
- /document what was done/i,
80
- /execution report/i,
81
- ],
82
- },
83
- {
84
- skill: "tdd",
85
- patterns: [
86
- /\btdd\b/i,
87
- /test[- ]first/i,
88
- /red[- ]green[- ]refactor/i,
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 { skill, patterns } of SKILL_TRIGGERS) {
111
- if (patterns.some((p) => p.test(prompt))) matched.push(skill);
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);
@@ -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 adding to ~/.claude/settings.json:
4
- // "statusLine": {
5
- // "type": "command",
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";
@@ -7,6 +7,25 @@ import { execSync } from "child_process";
7
7
  import fs from "fs";
8
8
  import path from "path";
9
9
 
10
+ const TS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"];
11
+
12
+ // Pulls every file_path the hook payload references. Covers Write/Edit (single
13
+ // file_path) and MultiEdit (edits[]). Returns null when no payload was available
14
+ // — callers treat null as "can't tell, run tsc to be safe".
15
+ function extractFilePaths(payload) {
16
+ const input = payload?.tool_input;
17
+ if (!input) return null;
18
+ const collected = new Set();
19
+ if (typeof input.file_path === "string") collected.add(input.file_path);
20
+ if (typeof input.path === "string") collected.add(input.path);
21
+ if (Array.isArray(input.edits)) {
22
+ for (const edit of input.edits) {
23
+ if (edit && typeof edit.file_path === "string") collected.add(edit.file_path);
24
+ }
25
+ }
26
+ return collected.size > 0 ? [...collected] : null;
27
+ }
28
+
10
29
  function resolveTscCommand(cwd) {
11
30
  const localBin = path.join(cwd, "node_modules", ".bin", "tsc");
12
31
  if (fs.existsSync(localBin)) return `"${localBin}"`;
@@ -16,10 +35,30 @@ function resolveTscCommand(cwd) {
16
35
  }
17
36
 
18
37
  async function main() {
19
- // Drain stdin (hook contract) but we don't need the payload for tsc --noEmit
20
38
  const chunks = [];
21
39
  for await (const chunk of process.stdin) chunks.push(chunk);
22
40
 
41
+ let payload = null;
42
+ if (chunks.length > 0) {
43
+ try {
44
+ payload = JSON.parse(Buffer.concat(chunks).toString());
45
+ } catch {
46
+ // Malformed payload — fall through; treat as "can't tell"
47
+ }
48
+ }
49
+
50
+ // Filter: if every touched file is non-TS/JS, skip invoking tsc.
51
+ // When the payload is missing or unparseable we default to running tsc
52
+ // (safe behavior — matches the pre-filter implementation).
53
+ const touched = extractFilePaths(payload);
54
+ if (touched !== null) {
55
+ const hasCompilable = touched.some((f) => {
56
+ const ext = path.extname(f).toLowerCase();
57
+ return TS_EXTENSIONS.includes(ext);
58
+ });
59
+ if (!hasCompilable) process.exit(0);
60
+ }
61
+
23
62
  const cwd = process.cwd();
24
63
  const tsconfigPath = path.join(cwd, "tsconfig.json");
25
64
  if (!fs.existsSync(tsconfigPath)) {
@@ -31,14 +70,12 @@ async function main() {
31
70
 
32
71
  try {
33
72
  execSync(`${tsc} --noEmit`, { cwd, stdio: "pipe" });
34
- // Compile clean — exit silently
35
73
  process.exit(0);
36
74
  } catch (err) {
37
75
  const output = (err.stdout || "").toString() + (err.stderr || "").toString();
38
76
  if (output.trim()) {
39
77
  process.stdout.write("TypeScript errors detected:\n" + output + "\n");
40
78
  }
41
- // PostToolUse hooks cannot block; stdout is fed back to Claude
42
79
  process.exit(0);
43
80
  }
44
81
  }
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "@hopla/claude-setup",
3
- "version": "1.19.0",
4
- "description": "Hopla team agentic coding system for Claude Code",
3
+ "version": "2.1.1",
4
+ "description": "Agentic coding system for Claude Code — CLI for global rules + permissions (plugin available via /plugin marketplace add HOPLAtools/claude-setup)",
5
5
  "type": "module",
6
6
  "bin": {
7
- "claude-setup": "cli.js",
8
7
  "hopla-claude-setup": "cli.js"
9
8
  },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/HOPLAtools/claude-setup.git"
12
+ },
13
+ "homepage": "https://github.com/HOPLAtools/claude-setup#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/HOPLAtools/claude-setup/issues"
16
+ },
10
17
  "files": [
11
18
  "cli.js",
12
19
  "global-rules.md",
@@ -14,11 +21,14 @@
14
21
  "skills/",
15
22
  "agents/",
16
23
  "hooks/",
17
- ".claude-plugin/"
24
+ ".claude-plugin/",
25
+ "LICENSE",
26
+ "CHANGELOG.md"
18
27
  ],
19
28
  "scripts": {
20
- "prepublishOnly": "node scripts/check-versions.js",
21
- "check-versions": "node scripts/check-versions.js"
29
+ "prepublishOnly": "node scripts/check-versions.js && npm test",
30
+ "check-versions": "node scripts/check-versions.js",
31
+ "test": "node --test tests/*.test.js tests/hooks/*.test.js"
22
32
  },
23
33
  "engines": {
24
34
  "node": ">=18"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: brainstorm
3
- description: "Design exploration and brainstorming before planning. Use when the user wants to explore options for a new feature, discuss approaches, design a solution, brainstorm ideas, or evaluate trade-offs. Trigger on: 'new feature', 'brainstorm', 'explore options', 'design', 'how should we', 'what approach', 'trade-offs', 'quiero agregar', 'diseñar', 'explorar opciones'. Do NOT use when the user already has a clear plan or is asking to execute existing work."
3
+ description: "Design exploration and brainstorming before planning. Use when the user wants to explore options for a new feature, discuss approaches, design a solution, brainstorm ideas, or evaluate trade-offs. Trigger on: 'new feature', 'brainstorm', 'explore options', 'design', 'how should we', 'what approach', 'trade-offs'. Do NOT use when the user already has a clear plan or is asking to execute existing work."
4
4
  ---
5
5
 
6
6
  # Brainstorming: Design Exploration Before Planning
@@ -1,6 +1,11 @@
1
1
  ---
2
2
  name: code-review
3
3
  description: "Technical code review on changed files. Use when the user says 'review code', 'code review', 'check my code', 'review changes', 'look for bugs', or 'audit code'. Also use after completing implementation when validation passes. Do NOT use for reviewing plans or documents — only code."
4
+ triggers:
5
+ - "review (my |the |this )?code"
6
+ - "code review"
7
+ - "audit (my |the |this )?code"
8
+ - "look for bugs"
4
9
  allowed-tools: Read, Grep, Glob, Bash
5
10
  ---
6
11
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: debug
3
- description: "Systematic debugging methodology for finding and fixing bugs. Use when encountering errors, bugs, failures, unexpected behavior, or when the user says 'bug', 'error', 'not working', 'failing', 'debug', 'fix', 'broken', 'falla', 'no funciona', 'error'. Do NOT use for planned feature work or refactoring — only for diagnosing and fixing unexpected problems."
3
+ description: "Systematic debugging methodology for finding and fixing bugs. Use when encountering errors, bugs, failures, unexpected behavior, or when the user says 'bug', 'error', 'not working', 'failing', 'debug', 'fix', 'broken'. Do NOT use for planned feature work or refactoring — only for diagnosing and fixing unexpected problems."
4
4
  ---
5
5
 
6
6
  # Systematic Debugging
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: performance
3
- description: "Measured performance optimization workflow. Use when the user says 'slow', 'optimize', 'performance', 'bottleneck', 'too slow', 'high memory', 'high CPU', 'lento', 'tarda mucho', or when asking to make something faster. Do NOT use for correctness bugs or new features — use the debug or plan-feature skills instead."
3
+ description: "Measured performance optimization workflow. Use when the user says 'slow', 'optimize', 'performance', 'bottleneck', 'too slow', 'high memory', 'high CPU', or when asking to make something faster. Do NOT use for correctness bugs or new features — use the debug or plan-feature skills instead."
4
4
  ---
5
5
 
6
6
  > 🌐 **Language:** All user-facing output must match the user's language. Code, paths, and commands stay in English.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: verify
3
- description: "Verification gate that ensures all completion claims are backed by fresh evidence. Use when the agent is about to declare work as done, finished, complete, ready, or implemented. Also use when hearing 'listo', 'terminé', 'ya está', 'done', 'finished', 'all tests pass', 'everything works', or any completion claim. Do NOT use for intermediate progress updates or partial task completion."
3
+ description: "Verification gate that ensures all completion claims are backed by fresh evidence. Use when the agent is about to declare work as done, finished, complete, ready, or implemented. Also use when hearing 'done', 'finished', 'all tests pass', 'everything works', or any completion claim. Do NOT use for intermediate progress updates or partial task completion."
4
4
  ---
5
5
 
6
6
  # Verification Before Completion