@khanhcan148/mk 0.1.10 → 0.1.12

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/README.md CHANGED
@@ -6,15 +6,8 @@
6
6
 
7
7
  Modular packages that extend Claude Code with specialized knowledge, workflows, and tool integrations.
8
8
 
9
- **Quick Navigation:** [Quick Reference](docs/QUICK-REFERENCE.md) | [Common Workflows](docs/COMMON-WORKFLOWS.md) | [Skill Index](docs/SKILL-INDEX.md)
9
+ **Quick Navigation:** [Quick Reference](docs/quick-reference.md) | [Common Workflows](docs/common-workflows.md) | [Skill Index](docs/skill-index.md)
10
10
 
11
- ## 🔒 Access Notice
12
-
13
- **This package has limited access.** To get access to the npm package and repository:
14
-
15
- 📧 **Email:** khanhcan148@gmail.com
16
-
17
- Include your use case and I'll send you an invitation.
18
11
 
19
12
  ## Quick Start
20
13
 
@@ -74,7 +67,7 @@ cp -r .claude ~/.claude/
74
67
  ```bash
75
68
  # Use a workflow command
76
69
  /mk-init # Bootstrap new project
77
- /mk-brainstorm # Explore options, debate trade-offs
70
+ /mk-brainstorm # Explore options, debate trade-offs; pass Jira/AzDO URL to fetch ticket context first
78
71
  /mk-plan # Create implementation plan
79
72
  /mk-implement # End-to-end feature delivery
80
73
  /mk-test # Run tests and validate
@@ -83,20 +76,21 @@ cp -r .claude ~/.claude/
83
76
  /mk-security # Security scan and audit
84
77
  /mk-db # Database operations
85
78
  /mk-docs # Generate documentation
79
+ /mk-overview # Multi-tier stakeholder overview (Executive, Product, Technical)
86
80
  /mk-git # Git operations
87
81
  /mk-audit # Code archaeology chain: orientation, testing, heatmap, breaking-change analysis, technical debt, domain extraction
88
- /mk-skill-creator # Create and scaffold new Claude Code skills with automated validation
89
82
  /mk-selftest # Self-validation checks on kit agents, skills, docs, workflows
83
+ /mk-log-analysis # Datadog + Azure App Insights log analysis with severity triage and Mermaid visual reports
90
84
  ```
91
85
 
92
86
  ## Structure
93
87
 
94
88
  ```
95
89
  ├── .claude/
96
- │ ├── agents/ # 29 agents (5 primary + 24 utility: implementers, quality, docs, specialized, concerns)
97
- │ ├── skills/ # 58 skill packages (SKILL.md + scripts/references/assets)
98
- │ │ ├── mk-*/ # 16 workflow commands (/mk-audit, /mk-brainstorm, /mk-skill-creator, /mk-selftest, etc.)
99
- │ │ └── ... # Domain skills (frontend, backend, testing, etc.)
90
+ │ ├── agents/ # 31 agents (5 primary + 26 utility: implementers, quality, docs, specialized, concerns)
91
+ │ ├── skills/ # 65 skill packages (SKILL.md + scripts/references/assets)
92
+ │ │ ├── mk-*/ # 19 workflow commands (/mk-audit, /mk-brainstorm, /mk-log-analysis, /mk-overview, /mk-selftest, etc.)
93
+ │ │ └── ... # Domain skills (frontend, backend, testing, browser automation, etc.)
100
94
  │ └── workflows/ # Development protocols
101
95
  ├── bin/ # CLI entry point (mk command)
102
96
  ├── src/ # CLI source code
@@ -109,7 +103,7 @@ cp -r .claude ~/.claude/
109
103
 
110
104
  ## Primary Workflow
111
105
 
112
- Orchestration is handled by `/mk-*` skills which spawn utility agents as needed. See [Common Workflows](docs/COMMON-WORKFLOWS.md) for practical examples.
106
+ Orchestration is handled by `/mk-*` skills which spawn utility agents as needed. See [Common Workflows](docs/common-workflows.md) for practical examples.
113
107
 
114
108
  **Architecture:**
115
109
 
@@ -126,21 +120,24 @@ User → /mk-* command (skill) → spawns utility agents → agents use knowledg
126
120
 
127
121
  | Command | Purpose |
128
122
  |---------|---------|
129
- | `/mk-init` | Full project bootstrap from conception to deployment; includes brownfield detection (Phase 0 gate) and adoption workflow (B1-B4.5 stages) |
130
- | `/mk-brainstorm` | Ideation, requirements exploration, structured debate (analyzer opus agent) |
131
- | `/mk-audit` | Code archaeology chain: orientation report, characterization testing, topology heatmap, breaking-change analysis, technical debt dashboard, domain extraction |
123
+ | `/mk-init` | Full project bootstrap from conception to deployment; includes brownfield detection (Phase 0 gate) and adoption workflow (B1-B4.5 stages); generates AGENTS.md template (or migrates existing tool configs); suggests /mk-audit as next step for brownfield projects |
124
+ | `/mk-brainstorm` | Ideation, requirements exploration, structured debate (analyzer opus agent); pass a Jira or AzDO URL to fetch ticket hierarchy first (Phase 0.5) |
125
+ | `/mk-audit` | Code archaeology chain: orientation report, characterization testing, topology heatmap, breaking-change analysis, technical debt dashboard, domain extraction; enriches CLAUDE.md and AGENTS.md with auto-generated architecture and debt sections |
132
126
  | `/mk-plan` | Create implementation plans with phases and tasks (opus) |
133
127
  | `/mk-design` | UI/UX wireframes, design systems, mockups |
134
128
  | `/mk-implement` | End-to-end feature delivery (sonnet) |
135
129
  | `/mk-test` | Run tests, analyze coverage, validate builds |
136
130
  | `/mk-review` | Code review for quality, security, performance |
137
- | `/mk-debug` | Debugging workflow: investigate, diagnose, fix, verify |
131
+ | `/mk-debug` | Debugging workflow: investigate, diagnose, fix, verify; always offers incident journal documentation with severity hints |
138
132
  | `/mk-security` | Security-first workflow: dependency scan, secrets detection |
139
133
  | `/mk-db` | Database operations: diagnose, optimize, design, migrate |
140
- | `/mk-docs` | Generate and update project documentation |
134
+ | `/mk-docs` | Generate and update project documentation; maintains AGENTS.md; Impact Areas analysis produces human-readable "What changed / Who is affected / What could go wrong" narrative |
141
135
  | `/mk-git` | Git operations: branch, commit, push, PR, merge |
142
136
  | `/mk-research` | Deep multi-source research on technical topics |
143
- | `/mk-skill-creator` | Create and scaffold new Claude Code skills with automated SKILL.md, scripts, references, and agent |
137
+ | `/mk-spike` | Investigate external service integrations: fetch API docs, evaluate options, produce spike.md with Go/No-Go |
138
+ | `/mk-overview` | Synthesize project artifacts into multi-tier stakeholder overview: Executive Brief, Product Report, Technical Report |
139
+ | `/mk-workflow` | Trace REST endpoint call chains with upstream caller detection, variant branching, side effects/feature flags, Mermaid diagrams |
140
+ | `/mk-log-analysis` | Analyze production logs from Datadog or Azure Application Insights via MCP; progressive severity triage, pattern detection, mandatory stack trace investigation, mk-debug integration |
144
141
  | `/mk-selftest` | Run self-validation checks on kit agents, skills, docs, and workflows |
145
142
 
146
143
  ## Primary Agents
@@ -153,17 +150,18 @@ Invoked directly via `/mk-*` skills:
153
150
  | **planner** | Implementation planning with phases, tasks, dependencies | opus |
154
151
  | **implementer** | End-to-end feature implementation | sonnet |
155
152
  | **database-admin** | Database operations: diagnose, optimize, design, migrate | sonnet |
156
- | **git-manager** | Git operations: commit, push, PR, merge | haiku |
153
+ | **git-manager** | Git operations: commit, push, PR, merge | sonnet |
157
154
 
158
155
  ## Utility Agents
159
156
 
160
157
  Spawned by primary agents for domain-specific work:
161
158
 
162
- **Implementation & Quality (8)**
159
+ **Implementation & Quality (9)**
163
160
  | Agent | Purpose |
164
161
  |-------|---------|
165
162
  | **backend-implementer** | API and domain logic implementation |
166
163
  | **frontend-implementer** | UI components, consuming API contract |
164
+ | **mobile-implementer** | Mobile app implementation (Flutter, React Native, Swift, Kotlin) with TFD |
167
165
  | **tester** | Test execution and validation |
168
166
  | **debugger** | Issue investigation and diagnosis |
169
167
  | **refactorer** | Refactoring with TFD-first safety workflow |
@@ -189,7 +187,7 @@ Spawned by primary agents for domain-specific work:
189
187
  | **scout-external** | Codebase search via external tools |
190
188
  | **mcp-manager** | MCP server integrations |
191
189
 
192
- **Parallel Concern Review Agents (10)**
190
+ **Parallel Concern Review Agents (11)**
193
191
  | Agent | Purpose |
194
192
  |-------|---------|
195
193
  | **quality-reviewer** | Code quality, readability, maintainability (mk-review concern) |
@@ -202,33 +200,30 @@ Spawned by primary agents for domain-specific work:
202
200
  | **infra-reviewer** | Infrastructure security review (mk-security concern) |
203
201
  | **log-analyzer** | Log analysis and diagnostics (mk-debug concern) |
204
202
  | **hypothesis-generator** | Root cause hypothesis generation (mk-debug concern) |
203
+ | **log-collector** | External observability platform queries — Datadog and Azure App Insights MCP (mk-log-analysis concern) |
205
204
 
206
205
  [Full agent details →](.claude/agents/README.md)
207
206
 
208
207
  ## Skills
209
208
 
210
- See [Skill Index](docs/SKILL-INDEX.md) for a complete categorized list of all skills.
209
+ See [Skill Index](docs/skill-index.md) for a complete categorized list of all skills.
211
210
 
212
211
  Each skill contains:
213
- - `SKILL.md` (required) - Instructions (<100 lines)
212
+ - `SKILL.md` (required) - Instructions (≤250 lines)
214
213
  - `scripts/` (optional) - Executable code with tests
215
214
  - `references/` (optional) - Documentation chunks
216
215
  - `assets/` (optional) - Templates, images
217
216
 
218
217
  ```bash
219
- # Create new skill (recommended)
220
- /mk-skill-creator
221
-
222
- # Or use scripts directly
223
- .claude/skills/mk-skill-creator/scripts/init_skill.py <skill-name> --path <output-directory>
224
- .claude/skills/mk-skill-creator/scripts/package_skill.py <path/to/skill-folder>
218
+ # Create new skill
219
+ python .claude/skills/skill-creator/scripts/package_skill.py <path/to/skill-folder>
225
220
  ```
226
221
 
227
222
  ## Documentation
228
223
 
229
224
  | Document | Description |
230
225
  |----------|-------------|
231
- | [Skill Index](docs/SKILL-INDEX.md) | Categorized list of all skills and workflows |
226
+ | [Skill Index](docs/skill-index.md) | Categorized list of all skills and workflows |
232
227
  | [mk-* Workflows & Agents](docs/mk-workflow-agents.md) | How each /mk-* command works and which agents it uses |
233
228
  | [Project Overview & PDR](docs/project-overview-pdr.md) | Requirements, constraints, success metrics |
234
229
  | [Project Roadmap](docs/project-roadmap.md) | Phases, milestones, and risk register |
@@ -236,8 +231,8 @@ Each skill contains:
236
231
  | [Code Standards](docs/code-standards.md) | Conventions, naming, quality gates |
237
232
  | [System Architecture](docs/system-architecture.md) | Component diagrams, data flow |
238
233
  | [Product Domain Glossary](docs/product-domain-glossary.md) | Core domain concepts, terminology, and ecosystem definitions |
239
- | [Quick Reference](docs/QUICK-REFERENCE.md) | Common commands and quick lookup |
240
- | [Common Workflows](docs/COMMON-WORKFLOWS.md) | Practical workflow examples |
234
+ | [Quick Reference](docs/quick-reference.md) | Common commands and quick lookup |
235
+ | [Common Workflows](docs/common-workflows.md) | Practical workflow examples |
241
236
 
242
237
  ## Core Principles
243
238
 
@@ -248,16 +243,12 @@ Each skill contains:
248
243
 
249
244
  ## Key Constraints
250
245
 
251
- - SKILL.md must be under 100 lines
252
- - Referenced markdown files also under 100 lines
246
+ - SKILL.md must be ≤250 lines
247
+ - Reference files in `references/` have no line limit; loaded on-demand
253
248
  - Scripts must include tests
254
249
  - **Cross-platform compatibility (Windows + macOS/Linux) is mandatory**: Scripts use Python or Node.js only — no bash/shell scripts
255
250
  - Token efficiency - minimize context usage
256
251
 
257
- ## Compatibility
258
-
259
- This skill kit works in both Claude Code CLI and VSCode GitHub Copilot (1.108+).
260
-
261
252
  ## Cross-Platform Compatibility
262
253
 
263
254
  This kit is designed to work on Windows, macOS, and Linux:
@@ -265,19 +256,4 @@ This kit is designed to work on Windows, macOS, and Linux:
265
256
  - **Scripts**: All executable scripts use Python or Node.js (no bash/shell scripts)
266
257
  - **Claude Code Bash tool**: Provides a Unix-like shell on all platforms, including Windows — git commands and other Bash tool invocations work everywhere
267
258
  - **External tool installs**: Reference files include install instructions for macOS (`brew`), Ubuntu/Debian (`apt-get`), and Windows (`winget`/`choco`) where applicable
268
- - **Agent fallbacks**: Agents using CLI-only tools (TodoWrite, BashOutput, MultiEdit) document VSCode Copilot fallback behavior inline
269
-
270
- | Feature | Claude Code CLI | VSCode Copilot (1.108+) |
271
- |---------|----------------|------------------------|
272
- | Skills (SKILL.md) | Full support | Full support |
273
- | Agents (.md) | Full support | Full support |
274
- | CLAUDE.md | Full support | Full support |
275
- | Model field | Shorthand (opus/sonnet/haiku) | Shorthand (recommended) |
276
- | Task tool | Native Task() syntax | Natural language delegation* |
277
- | color | Supported | Ignored (harmless) |
278
- | MCP tools | Full support | Limited |
279
- | AskUserQuestion | Native UI | Natural language fallback |
280
-
281
- *VSCode Copilot users: Use natural language to invoke agents (e.g., "Use the planner agent to create a plan"). The explicit Task() syntax is Claude Code CLI-specific.
282
-
283
- **CLI-only tools**: TodoWrite, BashOutput, KillShell, MultiEdit are Claude Code-specific. Agents gracefully fall back to standard tools (Write, Bash, sequential Edit) when these are unavailable.
259
+ **Supported runtime**: Claude Code CLI. All skills, agents, and workflows are designed for the Claude Code runtime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanhcan148/mk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "CLI to install and manage MyClaudeKit (.claude/) in your projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,8 +14,8 @@
14
14
  "node": ">=18.0.0"
15
15
  },
16
16
  "scripts": {
17
- "test": "node --test test/lib/*.test.js test/commands/*.test.js test/integration/*.test.js",
18
- "lint": "node --check src/**/*.js bin/**/*.js 2>/dev/null || true",
17
+ "test": "node --test test/lib/*.test.js test/commands/*.test.js test/integration/*.test.js test/characterization/*.characterization.test.js test/hooks/*.test.cjs",
18
+ "lint": "node --check src/**/*.js bin/**/*.js 2>/dev/null",
19
19
  "selftest": "python3 .claude/skills/mk-selftest/scripts/validate_kit.py"
20
20
  },
21
21
  "dependencies": {
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join, relative } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { copyKitFiles } from '../lib/copy.js';
5
+ import { copyKitFiles, mergeSettingsJson } from '../lib/copy.js';
6
6
  import { writeManifest } from '../lib/manifest.js';
7
7
  import { computeChecksum } from '../lib/checksum.js';
8
8
  import { resolveTargetDir, resolveManifestPath } from '../lib/paths.js';
@@ -45,6 +45,9 @@ export async function runInit(params = {}) {
45
45
  // Copy files (dry-run or real)
46
46
  const fileList = copyKitFiles(sourceDir, targetDir, { dryRun });
47
47
 
48
+ // Merge settings.json (hooks configuration) — safe merge, never overwrites user keys
49
+ mergeSettingsJson(sourceDir, targetDir, { dryRun });
50
+
48
51
  if (dryRun) {
49
52
  return { files: fileList, totalSize: fileList.reduce((s, f) => s + f.size, 0), dryRun: true };
50
53
  }
@@ -1,8 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { existsSync, unlinkSync, rmdirSync, readdirSync } from 'node:fs';
2
+ import { existsSync, unlinkSync, rmdirSync } from 'node:fs';
3
3
  import { join, dirname, resolve, sep } from 'node:path';
4
4
  import { readManifest } from '../lib/manifest.js';
5
5
  import { resolveTargetDir, resolveManifestPath, deriveProjectRoot, assertSafePath } from '../lib/paths.js';
6
+ import { isEmptyDir } from '../lib/fs-utils.js';
6
7
 
7
8
  /**
8
9
  * Run the remove command.
@@ -110,19 +111,6 @@ export async function runRemove(params = {}) {
110
111
  return { removed, skipped, dirsCleaned };
111
112
  }
112
113
 
113
- /**
114
- * Check if a directory is empty.
115
- * @param {string} dir
116
- * @returns {boolean}
117
- */
118
- function isEmptyDir(dir) {
119
- try {
120
- return readdirSync(dir).length === 0;
121
- } catch {
122
- return false;
123
- }
124
- }
125
-
126
114
  /**
127
115
  * CLI action handler for 'mk remove'.
128
116
  * @param {{ global: boolean }} options
@@ -1,16 +1,17 @@
1
1
  import chalk from 'chalk';
2
2
  import { createInterface } from 'node:readline';
3
- import { existsSync, unlinkSync, copyFileSync, mkdirSync, readFileSync, rmdirSync, readdirSync } from 'node:fs';
3
+ import { existsSync, unlinkSync, copyFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
4
4
  import { join, dirname, resolve, sep } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { readManifest, updateManifest, diffManifest } from '../lib/manifest.js';
7
7
  import { computeChecksum } from '../lib/checksum.js';
8
- import { copyKitFiles, collectDiskFiles } from '../lib/copy.js';
8
+ import { copyKitFiles, collectDiskFiles, mergeSettingsJson } from '../lib/copy.js';
9
9
  import { resolveTargetDir, resolveManifestPath, deriveProjectRoot, assertSafePath } from '../lib/paths.js';
10
10
  import { resolveTokenOrLogin } from '../lib/auth.js';
11
11
  import { writeToken, readStoredToken } from '../lib/config.js';
12
12
  import { downloadAndExtractKit, cleanupTempDir } from '../lib/download.js';
13
13
  import { fetchLatestRelease, compareVersions } from '../lib/releases.js';
14
+ import { isEmptyDir } from '../lib/fs-utils.js';
14
15
 
15
16
  // ---------------------------------------------------------------------------
16
17
  // Prompt helper
@@ -219,6 +220,9 @@ export async function runUpdate(params = {}) {
219
220
  }
220
221
  }
221
222
 
223
+ // Merge settings.json hooks — additive merge, never overwrites user keys
224
+ mergeSettingsJson(sourceDir, targetDir);
225
+
222
226
  // Update manifest with new file map.
223
227
  // Use explicitVersion when provided (e.g. release.version from updateAction);
224
228
  // fall back to pkg.version for direct runUpdate calls or main-branch fallback downloads.
@@ -235,19 +239,6 @@ export async function runUpdate(params = {}) {
235
239
  };
236
240
  }
237
241
 
238
- /**
239
- * Check if a directory is empty.
240
- * @param {string} dir
241
- * @returns {boolean}
242
- */
243
- function isEmptyDir(dir) {
244
- try {
245
- return readdirSync(dir).length === 0;
246
- } catch {
247
- return false;
248
- }
249
- }
250
-
251
242
  /**
252
243
  * CLI action handler for 'mk update'.
253
244
  * Checks GitHub Releases for a newer version, prompts the user, then downloads
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Kit subdirectories to copy/manage (relative to .claude/)
3
3
  */
4
- export const KIT_SUBDIRS = ['agents', 'skills', 'workflows'];
4
+ export const KIT_SUBDIRS = ['agents', 'skills', 'workflows', 'hooks'];
5
5
 
6
6
  /**
7
7
  * Manifest file name
@@ -19,7 +19,8 @@ export const COPY_FILTER_PATTERNS = [
19
19
  'node_modules',
20
20
  '.DS_Store',
21
21
  'package-lock.json',
22
- '.env'
22
+ '.env',
23
+ '.logs'
23
24
  ];
24
25
 
25
26
  /**
package/src/lib/copy.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { join, relative } from 'node:path';
2
- import { statSync, lstatSync, existsSync } from 'node:fs';
2
+ import { statSync, lstatSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
3
3
  import fsExtra from 'fs-extra';
4
4
  import { KIT_SUBDIRS, COPY_FILTER_PATTERNS, WINDOWS_PATH_WARN_LENGTH } from './constants.js';
5
5
 
@@ -153,3 +153,96 @@ export function copyKitFiles(sourceDir, targetDir, options = {}) {
153
153
 
154
154
  return fileList;
155
155
  }
156
+
157
+ /**
158
+ * Merge kit's settings.json into user's existing settings.json.
159
+ * Strategy: deep-merge "hooks" key from kit source; preserve all other user keys.
160
+ * If user has no settings.json, copy kit source as-is.
161
+ * If kit source has no settings.json, do nothing.
162
+ *
163
+ * @param {string} sourceDir - Absolute path to source .claude/
164
+ * @param {string} targetDir - Absolute path to target .claude/
165
+ * @param {{ dryRun: boolean }} options
166
+ * @returns {{ action: 'created'|'merged'|'skipped', merged?: string[] }}
167
+ */
168
+ export function mergeSettingsJson(sourceDir, targetDir, options = {}) {
169
+ const { dryRun = false } = options;
170
+ const srcPath = join(sourceDir, 'settings.json');
171
+ const destPath = join(targetDir, 'settings.json');
172
+
173
+ if (!existsSync(srcPath)) return { action: 'skipped' };
174
+
175
+ let kitSettings;
176
+ try {
177
+ kitSettings = JSON.parse(readFileSync(srcPath, 'utf-8'));
178
+ } catch {
179
+ return { action: 'skipped' };
180
+ }
181
+
182
+ // No existing user settings — copy kit source as-is
183
+ if (!existsSync(destPath)) {
184
+ if (!dryRun) {
185
+ mkdirSync(targetDir, { recursive: true });
186
+ writeFileSync(destPath, JSON.stringify(kitSettings, null, 2) + '\n', 'utf-8');
187
+ }
188
+ return { action: 'created' };
189
+ }
190
+
191
+ // Existing user settings — merge hooks only, preserve everything else
192
+ let userSettings;
193
+ try {
194
+ userSettings = JSON.parse(readFileSync(destPath, 'utf-8'));
195
+ } catch {
196
+ // Malformed user settings — skip to avoid data loss
197
+ return { action: 'skipped' };
198
+ }
199
+
200
+ const merged = [];
201
+
202
+ // Merge hooks: kit entries are added/updated, user entries not in kit are preserved
203
+ if (kitSettings.hooks) {
204
+ if (!userSettings.hooks) userSettings.hooks = {};
205
+ for (const [event, kitEntries] of Object.entries(kitSettings.hooks)) {
206
+ if (!userSettings.hooks[event]) {
207
+ userSettings.hooks[event] = kitEntries;
208
+ merged.push(event);
209
+ } else {
210
+ // Merge by matcher: add kit entries whose matcher doesn't already exist
211
+ for (const kitEntry of kitEntries) {
212
+ const kitMatcher = kitEntry.matcher || '*';
213
+ const exists = userSettings.hooks[event].some(
214
+ e => (e.matcher || '*') === kitMatcher
215
+ );
216
+ if (!exists) {
217
+ userSettings.hooks[event].push(kitEntry);
218
+ merged.push(`${event}[${kitMatcher}]`);
219
+ }
220
+ // If matcher exists, check if kit hooks are present
221
+ if (exists) {
222
+ const userEntry = userSettings.hooks[event].find(
223
+ e => (e.matcher || '*') === kitMatcher
224
+ );
225
+ if (userEntry && userEntry.hooks && kitEntry.hooks) {
226
+ for (const kh of kitEntry.hooks) {
227
+ const hookExists = userEntry.hooks.some(
228
+ uh => uh.command === kh.command
229
+ );
230
+ if (!hookExists) {
231
+ userEntry.hooks.push(kh);
232
+ merged.push(`${event}[${kitMatcher}]:${kh.command}`);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ if (merged.length === 0) return { action: 'skipped' };
243
+
244
+ if (!dryRun) {
245
+ writeFileSync(destPath, JSON.stringify(userSettings, null, 2) + '\n', 'utf-8');
246
+ }
247
+ return { action: 'merged', merged };
248
+ }
@@ -0,0 +1,18 @@
1
+ import { readdirSync } from 'node:fs';
2
+
3
+ /**
4
+ * Check if a directory is empty.
5
+ *
6
+ * Returns false (rather than throwing) if the directory does not exist
7
+ * or is inaccessible — callers treat non-empty as a safe default.
8
+ *
9
+ * @param {string} dir - Absolute path to directory
10
+ * @returns {boolean} true if directory exists and contains no entries
11
+ */
12
+ export function isEmptyDir(dir) {
13
+ try {
14
+ return readdirSync(dir).length === 0;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }