Skip to content

Commit a46fdf2

Browse files
silouoneclaude
andcommitted
feat: tiered hook installation — local, global, plugin
Refactor `clens init` to support three installation tiers so hooks no longer pollute the project's `.claude/settings.json`. Default tier writes to `.claude/settings.local.json` (gitignored by convention), `--global` writes to `~/.claude/settings.json`, and `clens init plugin` now installs both analysis tools and capture hooks in one command. - Add InitTarget type and resolveInitPaths for multi-tier path resolution - Default init writes to settings.local.json instead of settings.json - Add --global flag for user-level hook installation - Add legacy detection with migration guidance - Refactor uninit for multi-tier removal (local, global, legacy) - Update renderStatus to show all tiers with deduplication warnings - Create agentic/hooks/hooks.json with all 17 hook events - Enhance installPlugin to merge capture hooks into user settings - Enhance uninstallPlugin to remove capture hooks from user settings - Add type guards replacing unsafe `as` casts on untrusted data - Add try/catch to readSettingsFile for malformed JSON resilience - 1137 tests (51 new), 0 failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 44e3141 commit a46fdf2

9 files changed

Lines changed: 1028 additions & 113 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.2.1] - 2026-02-25
6+
7+
### Added
8+
9+
#### Tiered Hook Installation
10+
- `clens init` now writes hooks to `.claude/settings.local.json` (gitignored) instead of `.claude/settings.json` — no more polluting the repo
11+
- `clens init --global` installs hooks to `~/.claude/settings.json` for all projects
12+
- `clens init plugin` now installs both analysis tools AND capture hooks in one command
13+
- `clens init --status` shows installation state across all tiers (Local, Global, Plugin, Legacy)
14+
- `clens init --remove --legacy` removes hooks from `.claude/settings.json` (legacy location)
15+
- Legacy detection: warns when hooks exist in `.claude/settings.json` and suggests migration
16+
- Multi-tier deduplication warning when hooks are installed in multiple tiers
17+
- `agentic/hooks/hooks.json` — plugin hooks file with all 17 hook events
18+
19+
### Changed
20+
- `init()` refactored with `InitTarget` type (`"local" | "global"`) and `resolveInitPaths` helper
21+
- `uninit` detects and removes hooks from all active tiers
22+
- `installPlugin` merges capture hooks into user-level settings with backup
23+
- `uninstallPlugin` removes capture hooks from user-level settings
24+
- `validatePluginStructure` now checks for `hooks/hooks.json`
25+
- `readSettingsFile` handles malformed JSON gracefully (try/catch)
26+
- Type-safe parsing: replaced `as` casts on untrusted data with type guards
27+
- 1137 tests across 49 test files (was 1086 across 48)
28+
529
## [0.2.0] - 2026-02-25
630

731
### Breaking Changes

agentic/hooks/hooks.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"description": "cLens session capture — records all hook events to .clens/sessions/",
3+
"hooks": {
4+
"SessionStart": [{ "hooks": [{ "type": "command", "command": "clens-hook SessionStart" }] }],
5+
"SessionEnd": [{ "hooks": [{ "type": "command", "command": "clens-hook SessionEnd" }] }],
6+
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "clens-hook UserPromptSubmit" }] }],
7+
"PreToolUse": [{ "hooks": [{ "type": "command", "command": "clens-hook PreToolUse" }] }],
8+
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "clens-hook PostToolUse" }] }],
9+
"PostToolUseFailure": [{ "hooks": [{ "type": "command", "command": "clens-hook PostToolUseFailure" }] }],
10+
"PermissionRequest": [{ "hooks": [{ "type": "command", "command": "clens-hook PermissionRequest" }] }],
11+
"Notification": [{ "hooks": [{ "type": "command", "command": "clens-hook Notification" }] }],
12+
"SubagentStart": [{ "hooks": [{ "type": "command", "command": "clens-hook SubagentStart" }] }],
13+
"SubagentStop": [{ "hooks": [{ "type": "command", "command": "clens-hook SubagentStop" }] }],
14+
"Stop": [{ "hooks": [{ "type": "command", "command": "clens-hook Stop" }] }],
15+
"TeammateIdle": [{ "hooks": [{ "type": "command", "command": "clens-hook TeammateIdle" }] }],
16+
"TaskCompleted": [{ "hooks": [{ "type": "command", "command": "clens-hook TaskCompleted" }] }],
17+
"PreCompact": [{ "hooks": [{ "type": "command", "command": "clens-hook PreCompact" }] }],
18+
"ConfigChange": [{ "hooks": [{ "type": "command", "command": "clens-hook ConfigChange" }] }],
19+
"WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "clens-hook WorktreeCreate" }] }],
20+
"WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "clens-hook WorktreeRemove" }] }]
21+
}
22+
}

src/cli.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const KILLED_COMMANDS: Readonly<Record<string, string>> = {
195195
const GLOBAL_FLAGS = new Set(["--help", "-h", "--version", "-v"]);
196196

197197
const VALID_FLAGS_BY_COMMAND: Readonly<Record<string, ReadonlySet<string>>> = {
198-
init: new Set(["--remove", "--status", "--dev"]),
198+
init: new Set(["--remove", "--status", "--dev", "--global", "--legacy"]),
199199
list: new Set(["--json"]),
200200
distill: new Set(["--last", "--all", "--deep", "--json"]),
201201
report: new Set(["--last", "--json", "--detail", "--full", "--intent"]),
@@ -245,9 +245,11 @@ const printHelp = (): void => {
245245
${bold("Usage:")} clens <command> [session-id] [options]
246246
247247
${bold("Setup:")}
248-
${cyan("init")} Initialize clens hooks
249-
${cyan("init --remove")} Remove hooks and restore settings
250-
${cyan("init --status")} Show hook installation status
248+
${cyan("init")} Initialize clens hooks (local, per-project)
249+
${cyan("init --global")} Initialize clens hooks (global, all projects)
250+
${cyan("init --remove")} Remove hooks from all active tiers
251+
${cyan("init --remove --legacy")} Also remove legacy hooks from .claude/settings.json
252+
${cyan("init --status")} Show hook installation status across all tiers
251253
${cyan("init plugin")} Install/uninstall analysis plugin
252254
253255
${bold("Sessions:")}
@@ -277,11 +279,14 @@ ${bold("Options:")}
277279
${dim("--intent")} Filter by reasoning intent type
278280
${dim("--all")} Distill all sessions
279281
${dim("--comms")} Show communication timeline
282+
${dim("--global")} Install hooks globally (~/.claude/settings.json)
283+
${dim("--legacy")} Include legacy hooks in --remove
280284
${dim("--version")} Show version
281285
${dim("--help")} Show help
282286
283287
${bold("Examples:")}
284-
clens init # Set up hooks
288+
clens init # Set up hooks (local)
289+
clens init --global # Set up hooks (global)
285290
clens list # See all sessions
286291
clens distill --last # Distill latest session
287292
clens report --last # Summary of latest session
@@ -310,6 +315,8 @@ const flags: Flags = {
310315
status: args.includes("--status"),
311316
dev: args.includes("--dev"),
312317
comms: args.includes("--comms"),
318+
global: args.includes("--global"),
319+
legacy: args.includes("--legacy"),
313320
};
314321

315322
const positional = args.filter((a) => !a.startsWith("--") && !a.startsWith("-"));

0 commit comments

Comments
 (0)