@khanhcan148/mk 0.1.9 → 0.1.11
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 +46 -64
- package/package.json +3 -3
- package/src/commands/init.js +4 -1
- package/src/commands/remove.js +2 -14
- package/src/commands/update.js +69 -3
- package/src/lib/constants.js +4 -2
- package/src/lib/copy.js +119 -1
- package/src/lib/fs-utils.js +18 -0
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/
|
|
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
|
|
|
@@ -73,27 +66,31 @@ cp -r .claude ~/.claude/
|
|
|
73
66
|
|
|
74
67
|
```bash
|
|
75
68
|
# Use a workflow command
|
|
76
|
-
/mk-init
|
|
77
|
-
/mk-brainstorm
|
|
78
|
-
/mk-plan
|
|
79
|
-
/mk-implement
|
|
80
|
-
/mk-test
|
|
81
|
-
/mk-review
|
|
82
|
-
/mk-debug
|
|
83
|
-
/mk-security
|
|
84
|
-
/mk-db
|
|
85
|
-
/mk-docs
|
|
86
|
-
/mk-
|
|
69
|
+
/mk-init # Bootstrap new project
|
|
70
|
+
/mk-brainstorm # Explore options, debate trade-offs; pass Jira/AzDO URL to fetch ticket context first
|
|
71
|
+
/mk-plan # Create implementation plan
|
|
72
|
+
/mk-implement # End-to-end feature delivery
|
|
73
|
+
/mk-test # Run tests and validate
|
|
74
|
+
/mk-review # Code quality review
|
|
75
|
+
/mk-debug # Debug issues
|
|
76
|
+
/mk-security # Security scan and audit
|
|
77
|
+
/mk-db # Database operations
|
|
78
|
+
/mk-docs # Generate documentation
|
|
79
|
+
/mk-overview # Multi-tier stakeholder overview (Executive, Product, Technical)
|
|
80
|
+
/mk-git # Git operations
|
|
81
|
+
/mk-audit # Code archaeology chain: orientation, testing, heatmap, breaking-change analysis, technical debt, domain extraction
|
|
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
|
|
87
84
|
```
|
|
88
85
|
|
|
89
86
|
## Structure
|
|
90
87
|
|
|
91
88
|
```
|
|
92
89
|
├── .claude/
|
|
93
|
-
│ ├── agents/ #
|
|
94
|
-
│ ├── skills/ #
|
|
95
|
-
│ │ ├── mk-*/ #
|
|
96
|
-
│ │ └── ... # 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.)
|
|
97
94
|
│ └── workflows/ # Development protocols
|
|
98
95
|
├── bin/ # CLI entry point (mk command)
|
|
99
96
|
├── src/ # CLI source code
|
|
@@ -106,7 +103,7 @@ cp -r .claude ~/.claude/
|
|
|
106
103
|
|
|
107
104
|
## Primary Workflow
|
|
108
105
|
|
|
109
|
-
Orchestration is handled by `/mk-*` skills which spawn utility agents as needed. See [Common Workflows](docs/
|
|
106
|
+
Orchestration is handled by `/mk-*` skills which spawn utility agents as needed. See [Common Workflows](docs/common-workflows.md) for practical examples.
|
|
110
107
|
|
|
111
108
|
**Architecture:**
|
|
112
109
|
|
|
@@ -123,19 +120,24 @@ User → /mk-* command (skill) → spawns utility agents → agents use knowledg
|
|
|
123
120
|
|
|
124
121
|
| Command | Purpose |
|
|
125
122
|
|---------|---------|
|
|
126
|
-
| `/mk-init` | Full project bootstrap from conception to deployment; includes brownfield detection (Phase 0 gate) and adoption workflow (B1-B4.5 stages) |
|
|
127
|
-
| `/mk-brainstorm` | Ideation, requirements exploration, structured debate (analyzer opus agent);
|
|
128
|
-
| `/mk-
|
|
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 |
|
|
129
126
|
| `/mk-plan` | Create implementation plans with phases and tasks (opus) |
|
|
130
127
|
| `/mk-design` | UI/UX wireframes, design systems, mockups |
|
|
131
128
|
| `/mk-implement` | End-to-end feature delivery (sonnet) |
|
|
132
129
|
| `/mk-test` | Run tests, analyze coverage, validate builds |
|
|
133
130
|
| `/mk-review` | Code review for quality, security, performance |
|
|
134
|
-
| `/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 |
|
|
135
132
|
| `/mk-security` | Security-first workflow: dependency scan, secrets detection |
|
|
136
133
|
| `/mk-db` | Database operations: diagnose, optimize, design, migrate |
|
|
137
|
-
| `/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 |
|
|
138
135
|
| `/mk-git` | Git operations: branch, commit, push, PR, merge |
|
|
136
|
+
| `/mk-research` | Deep multi-source research on technical topics |
|
|
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 |
|
|
139
141
|
| `/mk-selftest` | Run self-validation checks on kit agents, skills, docs, and workflows |
|
|
140
142
|
|
|
141
143
|
## Primary Agents
|
|
@@ -148,17 +150,18 @@ Invoked directly via `/mk-*` skills:
|
|
|
148
150
|
| **planner** | Implementation planning with phases, tasks, dependencies | opus |
|
|
149
151
|
| **implementer** | End-to-end feature implementation | sonnet |
|
|
150
152
|
| **database-admin** | Database operations: diagnose, optimize, design, migrate | sonnet |
|
|
151
|
-
| **git-manager** | Git operations: commit, push, PR, merge |
|
|
153
|
+
| **git-manager** | Git operations: commit, push, PR, merge | sonnet |
|
|
152
154
|
|
|
153
155
|
## Utility Agents
|
|
154
156
|
|
|
155
157
|
Spawned by primary agents for domain-specific work:
|
|
156
158
|
|
|
157
|
-
**Implementation & Quality (
|
|
159
|
+
**Implementation & Quality (9)**
|
|
158
160
|
| Agent | Purpose |
|
|
159
161
|
|-------|---------|
|
|
160
162
|
| **backend-implementer** | API and domain logic implementation |
|
|
161
163
|
| **frontend-implementer** | UI components, consuming API contract |
|
|
164
|
+
| **mobile-implementer** | Mobile app implementation (Flutter, React Native, Swift, Kotlin) with TFD |
|
|
162
165
|
| **tester** | Test execution and validation |
|
|
163
166
|
| **debugger** | Issue investigation and diagnosis |
|
|
164
167
|
| **refactorer** | Refactoring with TFD-first safety workflow |
|
|
@@ -184,7 +187,7 @@ Spawned by primary agents for domain-specific work:
|
|
|
184
187
|
| **scout-external** | Codebase search via external tools |
|
|
185
188
|
| **mcp-manager** | MCP server integrations |
|
|
186
189
|
|
|
187
|
-
**Parallel Concern Review Agents (
|
|
190
|
+
**Parallel Concern Review Agents (11)**
|
|
188
191
|
| Agent | Purpose |
|
|
189
192
|
|-------|---------|
|
|
190
193
|
| **quality-reviewer** | Code quality, readability, maintainability (mk-review concern) |
|
|
@@ -197,32 +200,30 @@ Spawned by primary agents for domain-specific work:
|
|
|
197
200
|
| **infra-reviewer** | Infrastructure security review (mk-security concern) |
|
|
198
201
|
| **log-analyzer** | Log analysis and diagnostics (mk-debug concern) |
|
|
199
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) |
|
|
200
204
|
|
|
201
205
|
[Full agent details →](.claude/agents/README.md)
|
|
202
206
|
|
|
203
207
|
## Skills
|
|
204
208
|
|
|
205
|
-
See [Skill Index](docs/
|
|
209
|
+
See [Skill Index](docs/skill-index.md) for a complete categorized list of all skills.
|
|
206
210
|
|
|
207
211
|
Each skill contains:
|
|
208
|
-
- `SKILL.md` (required) - Instructions (
|
|
212
|
+
- `SKILL.md` (required) - Instructions (≤250 lines)
|
|
209
213
|
- `scripts/` (optional) - Executable code with tests
|
|
210
214
|
- `references/` (optional) - Documentation chunks
|
|
211
215
|
- `assets/` (optional) - Templates, images
|
|
212
216
|
|
|
213
217
|
```bash
|
|
214
|
-
#
|
|
215
|
-
.claude/skills/skill-creator/scripts/
|
|
216
|
-
|
|
217
|
-
# Package for distribution
|
|
218
|
-
.claude/skills/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>
|
|
219
220
|
```
|
|
220
221
|
|
|
221
222
|
## Documentation
|
|
222
223
|
|
|
223
224
|
| Document | Description |
|
|
224
225
|
|----------|-------------|
|
|
225
|
-
| [Skill Index](docs/
|
|
226
|
+
| [Skill Index](docs/skill-index.md) | Categorized list of all skills and workflows |
|
|
226
227
|
| [mk-* Workflows & Agents](docs/mk-workflow-agents.md) | How each /mk-* command works and which agents it uses |
|
|
227
228
|
| [Project Overview & PDR](docs/project-overview-pdr.md) | Requirements, constraints, success metrics |
|
|
228
229
|
| [Project Roadmap](docs/project-roadmap.md) | Phases, milestones, and risk register |
|
|
@@ -230,8 +231,8 @@ Each skill contains:
|
|
|
230
231
|
| [Code Standards](docs/code-standards.md) | Conventions, naming, quality gates |
|
|
231
232
|
| [System Architecture](docs/system-architecture.md) | Component diagrams, data flow |
|
|
232
233
|
| [Product Domain Glossary](docs/product-domain-glossary.md) | Core domain concepts, terminology, and ecosystem definitions |
|
|
233
|
-
| [Quick Reference](docs/
|
|
234
|
-
| [Common Workflows](docs/
|
|
234
|
+
| [Quick Reference](docs/quick-reference.md) | Common commands and quick lookup |
|
|
235
|
+
| [Common Workflows](docs/common-workflows.md) | Practical workflow examples |
|
|
235
236
|
|
|
236
237
|
## Core Principles
|
|
237
238
|
|
|
@@ -242,16 +243,12 @@ Each skill contains:
|
|
|
242
243
|
|
|
243
244
|
## Key Constraints
|
|
244
245
|
|
|
245
|
-
- SKILL.md must be
|
|
246
|
-
-
|
|
246
|
+
- SKILL.md must be ≤250 lines
|
|
247
|
+
- Reference files in `references/` have no line limit; loaded on-demand
|
|
247
248
|
- Scripts must include tests
|
|
248
249
|
- **Cross-platform compatibility (Windows + macOS/Linux) is mandatory**: Scripts use Python or Node.js only — no bash/shell scripts
|
|
249
250
|
- Token efficiency - minimize context usage
|
|
250
251
|
|
|
251
|
-
## Compatibility
|
|
252
|
-
|
|
253
|
-
This skill kit works in both Claude Code CLI and VSCode GitHub Copilot (1.108+).
|
|
254
|
-
|
|
255
252
|
## Cross-Platform Compatibility
|
|
256
253
|
|
|
257
254
|
This kit is designed to work on Windows, macOS, and Linux:
|
|
@@ -259,19 +256,4 @@ This kit is designed to work on Windows, macOS, and Linux:
|
|
|
259
256
|
- **Scripts**: All executable scripts use Python or Node.js (no bash/shell scripts)
|
|
260
257
|
- **Claude Code Bash tool**: Provides a Unix-like shell on all platforms, including Windows — git commands and other Bash tool invocations work everywhere
|
|
261
258
|
- **External tool installs**: Reference files include install instructions for macOS (`brew`), Ubuntu/Debian (`apt-get`), and Windows (`winget`/`choco`) where applicable
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
| Feature | Claude Code CLI | VSCode Copilot (1.108+) |
|
|
265
|
-
|---------|----------------|------------------------|
|
|
266
|
-
| Skills (SKILL.md) | Full support | Full support |
|
|
267
|
-
| Agents (.md) | Full support | Full support |
|
|
268
|
-
| CLAUDE.md | Full support | Full support |
|
|
269
|
-
| Model field | Shorthand (opus/sonnet/haiku) | Shorthand (recommended) |
|
|
270
|
-
| Task tool | Native Task() syntax | Natural language delegation* |
|
|
271
|
-
| color | Supported | Ignored (harmless) |
|
|
272
|
-
| MCP tools | Full support | Limited |
|
|
273
|
-
| AskUserQuestion | Native UI | Natural language fallback |
|
|
274
|
-
|
|
275
|
-
*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.
|
|
276
|
-
|
|
277
|
-
**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.
|
|
3
|
+
"version": "0.1.11",
|
|
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
|
|
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": {
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
}
|
package/src/commands/remove.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { existsSync, unlinkSync, rmdirSync
|
|
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
|
package/src/commands/update.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
-
import { existsSync, unlinkSync, copyFileSync, mkdirSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { join, dirname, resolve } from 'node:path';
|
|
3
|
+
import { existsSync, unlinkSync, copyFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
|
|
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 } 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
|
|
@@ -167,6 +168,61 @@ export async function runUpdate(params = {}) {
|
|
|
167
168
|
delete newFiles[relPath];
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
// Orphan cleanup: files on disk (in kit subdirs) that are not in the new source
|
|
172
|
+
const diskFiles = collectDiskFiles(targetDir);
|
|
173
|
+
const orphans = [];
|
|
174
|
+
const orphanParentDirs = new Set();
|
|
175
|
+
for (const relPath of diskFiles) {
|
|
176
|
+
if (relPath in sourceFiles) continue; // present in new source — keep
|
|
177
|
+
const absPath = join(projectRoot, relPath);
|
|
178
|
+
try {
|
|
179
|
+
assertSafePath(absPath, claudeRoot, `orphan "${relPath}"`);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
process.stderr.write(`Warning: Skipping unsafe orphan path: ${err.message}\n`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
unlinkSync(absPath);
|
|
186
|
+
orphans.push(relPath);
|
|
187
|
+
orphanParentDirs.add(dirname(absPath));
|
|
188
|
+
} catch {
|
|
189
|
+
// Already missing — skip
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Clean up empty directories bottom-up after orphan deletion
|
|
194
|
+
const resolvedTarget = resolve(targetDir);
|
|
195
|
+
const sortedOrphanDirs = [...orphanParentDirs].sort((a, b) => {
|
|
196
|
+
return b.split(/[/\\]/).length - a.split(/[/\\]/).length;
|
|
197
|
+
});
|
|
198
|
+
for (const dir of sortedOrphanDirs) {
|
|
199
|
+
if (isEmptyDir(dir)) {
|
|
200
|
+
try {
|
|
201
|
+
rmdirSync(dir);
|
|
202
|
+
let current = dirname(dir);
|
|
203
|
+
let prev = dir;
|
|
204
|
+
while (current !== prev && isEmptyDir(current)) {
|
|
205
|
+
const resolvedCurrent = resolve(current);
|
|
206
|
+
if (resolvedCurrent === resolvedTarget || !resolvedCurrent.startsWith(resolvedTarget + sep)) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
rmdirSync(current);
|
|
211
|
+
} catch {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
prev = current;
|
|
215
|
+
current = dirname(current);
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// Non-empty or permission error — skip
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Merge settings.json hooks — additive merge, never overwrites user keys
|
|
224
|
+
mergeSettingsJson(sourceDir, targetDir);
|
|
225
|
+
|
|
170
226
|
// Update manifest with new file map.
|
|
171
227
|
// Use explicitVersion when provided (e.g. release.version from updateAction);
|
|
172
228
|
// fall back to pkg.version for direct runUpdate calls or main-branch fallback downloads.
|
|
@@ -178,6 +234,7 @@ export async function runUpdate(params = {}) {
|
|
|
178
234
|
removed: diff.removed,
|
|
179
235
|
conflicts: skippedConflicts,
|
|
180
236
|
unchanged: diff.unchanged,
|
|
237
|
+
orphans,
|
|
181
238
|
upToDate: false
|
|
182
239
|
};
|
|
183
240
|
}
|
|
@@ -305,6 +362,15 @@ export async function updateAction(options = {}, deps = {}) {
|
|
|
305
362
|
}
|
|
306
363
|
if (result.removed.length > 0) {
|
|
307
364
|
process.stdout.write(chalk.yellow(`Removed: ${result.removed.length} files\n`));
|
|
365
|
+
for (const f of result.removed) {
|
|
366
|
+
process.stdout.write(` Removed: ${f}\n`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (result.orphans && result.orphans.length > 0) {
|
|
370
|
+
process.stdout.write(chalk.yellow(`Cleaned: ${result.orphans.length} orphan files\n`));
|
|
371
|
+
for (const f of result.orphans) {
|
|
372
|
+
process.stdout.write(` Cleaned: ${f}\n`);
|
|
373
|
+
}
|
|
308
374
|
}
|
|
309
375
|
if (result.conflicts.length > 0) {
|
|
310
376
|
process.stdout.write(
|
package/src/lib/constants.js
CHANGED
|
@@ -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
|
|
@@ -13,12 +13,14 @@ export const MANIFEST_FILENAME = '.mk-manifest.json';
|
|
|
13
13
|
*/
|
|
14
14
|
export const COPY_FILTER_PATTERNS = [
|
|
15
15
|
'__pycache__',
|
|
16
|
+
'.pytest_cache',
|
|
16
17
|
'.pyc',
|
|
17
18
|
'.pyo',
|
|
18
19
|
'node_modules',
|
|
19
20
|
'.DS_Store',
|
|
20
21
|
'package-lock.json',
|
|
21
|
-
'.env'
|
|
22
|
+
'.env',
|
|
23
|
+
'.logs'
|
|
22
24
|
];
|
|
23
25
|
|
|
24
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
|
|
|
@@ -52,6 +52,31 @@ function collectFiles(dir) {
|
|
|
52
52
|
return results;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Collect all kit-managed files currently on disk under targetDir (.claude/).
|
|
57
|
+
* Only walks KIT_SUBDIRS (agents/, skills/, workflows/).
|
|
58
|
+
* Returns relative paths in the form `.claude/agents/foo.md`.
|
|
59
|
+
* Applies shouldFilter to exclude filtered patterns.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} targetDir - Absolute path to target .claude/
|
|
62
|
+
* @returns {string[]} relative paths (relative to project root, e.g. `.claude/agents/foo.md`)
|
|
63
|
+
*/
|
|
64
|
+
export function collectDiskFiles(targetDir) {
|
|
65
|
+
const results = [];
|
|
66
|
+
for (const subdir of KIT_SUBDIRS) {
|
|
67
|
+
const subdirAbs = join(targetDir, subdir);
|
|
68
|
+
if (!existsSync(subdirAbs)) continue;
|
|
69
|
+
const files = collectFiles(subdirAbs);
|
|
70
|
+
for (const absPath of files) {
|
|
71
|
+
if (shouldFilter(absPath)) continue;
|
|
72
|
+
const relFromSubdir = relative(subdirAbs, absPath);
|
|
73
|
+
const relPath = join('.claude', subdir, relFromSubdir).replace(/\\/g, '/');
|
|
74
|
+
results.push(relPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
/**
|
|
56
81
|
* Copy kit files from sourceDir (.claude/) to targetDir (.claude/).
|
|
57
82
|
* Only copies KIT_SUBDIRS (agents/, skills/, workflows/).
|
|
@@ -128,3 +153,96 @@ export function copyKitFiles(sourceDir, targetDir, options = {}) {
|
|
|
128
153
|
|
|
129
154
|
return fileList;
|
|
130
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
|
+
}
|