@juho0719/cckit 0.2.0 → 0.2.3

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.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: Claude Behavioral Guidelines
3
+ description: LLM coding best practices - think before coding, simplicity, surgical changes, goal-driven execution
4
+ ---
5
+ # Claude Behavioral Guidelines
6
+
7
+ Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
8
+
9
+ **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
10
+
11
+ ## 1. Think Before Coding
12
+
13
+ **Don't assume. Don't hide confusion. Surface tradeoffs.**
14
+
15
+ Before implementing:
16
+ - State your assumptions explicitly. If uncertain, ask.
17
+ - If multiple interpretations exist, present them - don't pick silently.
18
+ - If a simpler approach exists, say so. Push back when warranted.
19
+ - If something is unclear, stop. Name what's confusing. Ask.
20
+
21
+ ## 2. Simplicity First
22
+
23
+ **Minimum code that solves the problem. Nothing speculative.**
24
+
25
+ - No features beyond what was asked.
26
+ - No abstractions for single-use code.
27
+ - No "flexibility" or "configurability" that wasn't requested.
28
+ - No error handling for impossible scenarios.
29
+ - If you write 200 lines and it could be 50, rewrite it.
30
+
31
+ Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
32
+
33
+ ## 3. Surgical Changes
34
+
35
+ **Touch only what you must. Clean up only your own mess.**
36
+
37
+ When editing existing code:
38
+ - Don't "improve" adjacent code, comments, or formatting.
39
+ - Don't refactor things that aren't broken.
40
+ - Match existing style, even if you'd do it differently.
41
+ - If you notice unrelated dead code, mention it - don't delete it.
42
+
43
+ When your changes create orphans:
44
+ - Remove imports/variables/functions that YOUR changes made unused.
45
+ - Don't remove pre-existing dead code unless asked.
46
+
47
+ The test: Every changed line should trace directly to the user's request.
48
+
49
+ ## 4. Goal-Driven Execution
50
+
51
+ **Define success criteria. Loop until verified.**
52
+
53
+ Transform tasks into verifiable goals:
54
+ - "Add validation" → "Write tests for invalid inputs, then make them pass"
55
+ - "Fix the bug" → "Write a test that reproduces it, then make it pass"
56
+ - "Refactor X" → "Ensure tests pass before and after"
57
+
58
+ For multi-step tasks, state a brief plan:
59
+ ```
60
+ 1. [Step] → verify: [check]
61
+ 2. [Step] → verify: [check]
62
+ 3. [Step] → verify: [check]
63
+ ```
64
+
65
+ Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
66
+
67
+ ---
68
+
69
+ **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
@@ -44,6 +44,5 @@ process.stdin.on('end', () => {
44
44
  // Invalid input — pass through
45
45
  }
46
46
 
47
- process.stdout.write(data);
48
47
  process.exit(0);
49
48
  });
@@ -32,7 +32,6 @@ process.stdin.on("end", () => {
32
32
  if (filePath && /\.(ts|tsx)$/.test(filePath)) {
33
33
  const resolvedPath = path.resolve(filePath);
34
34
  if (!fs.existsSync(resolvedPath)) {
35
- process.stdout.write(data);
36
35
  process.exit(0);
37
36
  }
38
37
  // Find nearest tsconfig.json by walking up (max 20 levels to prevent infinite loop)
@@ -91,6 +90,5 @@ process.stdin.on("end", () => {
91
90
  // Invalid input — pass through
92
91
  }
93
92
 
94
- process.stdout.write(data);
95
93
  process.exit(0);
96
94
  });
@@ -0,0 +1,37 @@
1
+ import {
2
+ backupIfExists
3
+ } from "./chunk-K25UZZVG.js";
4
+ import {
5
+ copyFileUtil,
6
+ ensureDir
7
+ } from "./chunk-3GUKEMND.js";
8
+ import {
9
+ getAssetsDir
10
+ } from "./chunk-5XOKKPAA.js";
11
+
12
+ // src/installers/rules.ts
13
+ import { join } from "path";
14
+ import { readdir } from "fs/promises";
15
+ async function installRules(categories, targetDir) {
16
+ const srcRulesDir = join(getAssetsDir(), "rules");
17
+ for (const category of categories) {
18
+ const srcCatDir = join(srcRulesDir, category);
19
+ let files;
20
+ try {
21
+ files = (await readdir(srcCatDir)).filter((f) => f.endsWith(".md"));
22
+ } catch {
23
+ continue;
24
+ }
25
+ const destCatDir = join(targetDir, "rules", category);
26
+ await ensureDir(destCatDir);
27
+ for (const file of files) {
28
+ const dest = join(destCatDir, file);
29
+ await backupIfExists(dest);
30
+ await copyFileUtil(join(srcCatDir, file), dest);
31
+ }
32
+ }
33
+ }
34
+
35
+ export {
36
+ installRules
37
+ };
@@ -98,6 +98,21 @@ async function getRuleCategories() {
98
98
  }
99
99
  return categories;
100
100
  }
101
+ async function getClaudemdItems() {
102
+ const dir = join(getAssetsDir(), "claudemd");
103
+ const files = (await readdir(dir)).filter((f) => f.endsWith(".md"));
104
+ const items = [];
105
+ for (const file of files) {
106
+ const content = await readFile(join(dir, file), "utf-8");
107
+ const fm = parseFrontmatter(content);
108
+ items.push({
109
+ name: fm["name"] || basename(file, ".md"),
110
+ description: fm["description"] || "",
111
+ file
112
+ });
113
+ }
114
+ return items;
115
+ }
101
116
  async function getMcpServers() {
102
117
  const filePath = join(getAssetsDir(), "mcps", "mcp-servers.json");
103
118
  const content = await readFile(filePath, "utf-8");
@@ -132,5 +147,6 @@ export {
132
147
  getCommands,
133
148
  getHooks,
134
149
  getRuleCategories,
150
+ getClaudemdItems,
135
151
  getMcpServers
136
152
  };
@@ -44,7 +44,7 @@ async function installHooks(hooks, targetDir) {
44
44
  const dest = join(hooksDir, hook.file);
45
45
  await backupIfExists(dest);
46
46
  await copyFileUtil(join(srcDir, hook.file), dest);
47
- const hookCommand = `node ${dest}`;
47
+ const hookCommand = `node "$CLAUDE_PROJECT_DIR"/.claude/hooks/${hook.file}`;
48
48
  await mergeHookSettings(settingsPath, hook.hookType, hook.matcher, hookCommand);
49
49
  }
50
50
  }
@@ -0,0 +1,35 @@
1
+ import {
2
+ getAssetsDir
3
+ } from "./chunk-5XOKKPAA.js";
4
+
5
+ // src/installers/claudemd.ts
6
+ import { join } from "path";
7
+ import { readFile, writeFile, access } from "fs/promises";
8
+ import { constants } from "fs";
9
+ async function installClaudemd(items, targetDir) {
10
+ const claudemdDir = join(getAssetsDir(), "claudemd");
11
+ const targetClaudeMd = join(targetDir, "CLAUDE.md");
12
+ let existing = "";
13
+ try {
14
+ await access(targetClaudeMd, constants.F_OK);
15
+ existing = await readFile(targetClaudeMd, "utf-8");
16
+ } catch {
17
+ }
18
+ const toAppend = [];
19
+ for (const item of items) {
20
+ const raw = await readFile(join(claudemdDir, item.file), "utf-8");
21
+ const content = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "").trim();
22
+ const headerMatch = content.match(/^#{1,2}\s+.+/m);
23
+ if (headerMatch) {
24
+ if (existing.includes(headerMatch[0])) continue;
25
+ }
26
+ toAppend.push(content);
27
+ }
28
+ if (toAppend.length === 0) return;
29
+ const appendContent = (existing.endsWith("\n") ? "" : "\n") + toAppend.join("\n\n") + "\n";
30
+ await writeFile(targetClaudeMd, existing + appendContent, "utf-8");
31
+ }
32
+
33
+ export {
34
+ installClaudemd
35
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ installClaudemd
3
+ } from "./chunk-SW3OJLHC.js";
4
+ import "./chunk-5XOKKPAA.js";
5
+ export {
6
+ installClaudemd
7
+ };
@@ -1,14 +1,18 @@
1
1
  import {
2
2
  installMcps
3
3
  } from "./chunk-W63UKEIT.js";
4
+ import {
5
+ installClaudemd
6
+ } from "./chunk-SW3OJLHC.js";
4
7
  import {
5
8
  getAgents,
9
+ getClaudemdItems,
6
10
  getCommands,
7
11
  getHooks,
8
12
  getMcpServers,
9
13
  getRuleCategories,
10
14
  getSkills
11
- } from "./chunk-6B46AIFM.js";
15
+ } from "./chunk-OLLOS3GG.js";
12
16
  import {
13
17
  installAgents
14
18
  } from "./chunk-EYY2IZ7N.js";
@@ -20,12 +24,12 @@ import {
20
24
  } from "./chunk-3Y26YU4R.js";
21
25
  import {
22
26
  installHooks
23
- } from "./chunk-3UNN3IBE.js";
24
- import "./chunk-K25UZZVG.js";
25
- import "./chunk-3GUKEMND.js";
27
+ } from "./chunk-SLVASXTF.js";
26
28
  import {
27
29
  installRules
28
- } from "./chunk-RMUKD7CW.js";
30
+ } from "./chunk-ID643AV4.js";
31
+ import "./chunk-K25UZZVG.js";
32
+ import "./chunk-3GUKEMND.js";
29
33
  import {
30
34
  getGlobalDir,
31
35
  getProjectDir
@@ -74,7 +78,8 @@ async function runCli() {
74
78
  { name: "Commands - Slash commands", value: "commands" },
75
79
  { name: "Hooks - Post-tool hooks", value: "hooks" },
76
80
  { name: "Rules - CLAUDE.md rules", value: "rules" },
77
- { name: "MCPs - MCP server configs", value: "mcps" }
81
+ { name: "MCPs - MCP server configs", value: "mcps" },
82
+ { name: "ClaudeMd - Behavioral guidelines", value: "claudemd" }
78
83
  ]
79
84
  });
80
85
  if (selectedCategories.length === 0) {
@@ -89,7 +94,8 @@ async function runCli() {
89
94
  hooks: [],
90
95
  ruleCategories: [],
91
96
  mcps: [],
92
- mcpEnvValues: /* @__PURE__ */ new Map()
97
+ mcpEnvValues: /* @__PURE__ */ new Map(),
98
+ claudemds: []
93
99
  };
94
100
  const skippedMcps = /* @__PURE__ */ new Map();
95
101
  if (selectedCategories.includes("agents")) {
@@ -147,6 +153,17 @@ async function runCli() {
147
153
  }))
148
154
  });
149
155
  }
156
+ if (selectedCategories.includes("claudemd")) {
157
+ const allClaudemds = await getClaudemdItems();
158
+ plan.claudemds = await checkbox({
159
+ message: "Select claudemd items:",
160
+ choices: allClaudemds.map((c) => ({
161
+ name: `${chalk.bold(c.name.padEnd(35))} ${chalk.gray(c.description.slice(0, 55))}`,
162
+ value: c,
163
+ checked: false
164
+ }))
165
+ });
166
+ }
150
167
  if (selectedCategories.includes("mcps")) {
151
168
  const allMcps = await getMcpServers();
152
169
  plan.mcps = await checkbox({
@@ -196,8 +213,9 @@ async function runCli() {
196
213
  if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
197
214
  if (plan.ruleCategories.length) console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
198
215
  if (plan.mcps.length) console.log(` MCPs : ${plan.mcps.map((m) => m.name).join(", ")}`);
216
+ if (plan.claudemds.length) console.log(` ClaudeMd: ${plan.claudemds.map((c) => c.name).join(", ")}`);
199
217
  console.log("");
200
- const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length;
218
+ const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length + plan.claudemds.length;
201
219
  if (totalItems === 0) {
202
220
  console.log(chalk.yellow(" Nothing selected. Exiting."));
203
221
  return;
@@ -228,21 +246,25 @@ async function runCli() {
228
246
  }
229
247
  if (plan.ruleCategories.length) {
230
248
  spinner.text = "Installing rules...";
231
- const claudeMd = join(targetDir, "CLAUDE.md");
232
- await installRules(plan.ruleCategories, claudeMd);
249
+ await installRules(plan.ruleCategories, targetDir);
233
250
  }
234
251
  if (plan.mcps.length) {
235
252
  spinner.text = "Installing MCP servers...";
236
253
  await installMcps(plan.mcps, plan.scope, plan.mcpEnvValues);
237
254
  }
255
+ if (plan.claudemds.length) {
256
+ spinner.text = "Installing claudemd...";
257
+ await installClaudemd(plan.claudemds, targetDir);
258
+ }
238
259
  spinner.succeed(chalk.green("Installation complete!"));
239
260
  console.log("");
240
261
  if (plan.agents.length) console.log(` ${chalk.green("\u2713")} ${plan.agents.length} agent(s) \u2192 ${join(targetDir, "agents")}`);
241
262
  if (plan.skills.length) console.log(` ${chalk.green("\u2713")} ${plan.skills.length} skill(s) \u2192 ${join(targetDir, "skills")}`);
242
263
  if (plan.commands.length) console.log(` ${chalk.green("\u2713")} ${plan.commands.length} command(s) \u2192 ${join(targetDir, "commands")}`);
243
264
  if (plan.hooks.length) console.log(` ${chalk.green("\u2713")} ${plan.hooks.length} hook(s) \u2192 ${join(targetDir, "hooks")}`);
244
- if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
265
+ if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules copied \u2192 ${join(targetDir, "rules")}`);
245
266
  if (plan.mcps.length) console.log(` ${chalk.green("\u2713")} ${plan.mcps.length} MCP server(s) \u2192 ${scope === "global" ? "~/.claude.json" : "./.claude.json"}`);
267
+ if (plan.claudemds.length) console.log(` ${chalk.green("\u2713")} ClaudeMd appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
246
268
  console.log("");
247
269
  if (skippedMcps.size > 0) {
248
270
  console.log(chalk.yellow(" \u26A0 The following MCP servers have placeholder env vars:"));
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  installHooks
3
- } from "./chunk-3UNN3IBE.js";
3
+ } from "./chunk-SLVASXTF.js";
4
4
  import "./chunk-K25UZZVG.js";
5
5
  import "./chunk-3GUKEMND.js";
6
6
  import "./chunk-5XOKKPAA.js";
package/dist/index.js CHANGED
@@ -51,33 +51,38 @@ Installing all to ${targetDir}...
51
51
  { getHooks },
52
52
  { getRuleCategories },
53
53
  { getMcpServers },
54
+ { getClaudemdItems },
54
55
  { installAgents },
55
56
  { installSkills },
56
57
  { installCommands },
57
58
  { installHooks },
58
59
  { installRules },
59
- { installMcps }
60
+ { installMcps },
61
+ { installClaudemd }
60
62
  ] = await Promise.all([
61
- import("./registry-EGXWYWWK.js"),
62
- import("./registry-EGXWYWWK.js"),
63
- import("./registry-EGXWYWWK.js"),
64
- import("./registry-EGXWYWWK.js"),
65
- import("./registry-EGXWYWWK.js"),
66
- import("./registry-EGXWYWWK.js"),
63
+ import("./registry-BU55RMHU.js"),
64
+ import("./registry-BU55RMHU.js"),
65
+ import("./registry-BU55RMHU.js"),
66
+ import("./registry-BU55RMHU.js"),
67
+ import("./registry-BU55RMHU.js"),
68
+ import("./registry-BU55RMHU.js"),
69
+ import("./registry-BU55RMHU.js"),
67
70
  import("./agents-AEKT67A6.js"),
68
71
  import("./skills-ULMW3UCM.js"),
69
72
  import("./commands-P5LILVZ5.js"),
70
- import("./hooks-IIG2XK4I.js"),
71
- import("./rules-2CPBVNNJ.js"),
72
- import("./mcps-67Q7TBGW.js")
73
+ import("./hooks-S73JTX2I.js"),
74
+ import("./rules-EFSJ3L3A.js"),
75
+ import("./mcps-67Q7TBGW.js"),
76
+ import("./claudemd-KKQ2DL7P.js")
73
77
  ]);
74
- const [agents, skills, commands, hooks, ruleCategories, mcps] = await Promise.all([
78
+ const [agents, skills, commands, hooks, ruleCategories, mcps, claudemds] = await Promise.all([
75
79
  getAgents(),
76
80
  getSkills(),
77
81
  getCommands(),
78
82
  getHooks(),
79
83
  getRuleCategories(),
80
- getMcpServers()
84
+ getMcpServers(),
85
+ getClaudemdItems()
81
86
  ]);
82
87
  spinner.text = "Installing agents...";
83
88
  await installAgents(agents, targetDir);
@@ -88,10 +93,11 @@ Installing all to ${targetDir}...
88
93
  spinner.text = "Installing hooks...";
89
94
  await installHooks(hooks, targetDir);
90
95
  spinner.text = "Installing rules...";
91
- const claudeMd = join2(targetDir, "CLAUDE.md");
92
- await installRules(ruleCategories.map((rc) => rc.category), claudeMd);
96
+ await installRules(ruleCategories.map((rc) => rc.category), targetDir);
93
97
  spinner.text = "Installing MCP servers...";
94
98
  await installMcps(mcps, scope, /* @__PURE__ */ new Map());
99
+ spinner.text = "Installing claudemd...";
100
+ await installClaudemd(claudemds, targetDir);
95
101
  spinner.succeed(chalk.green("All items installed successfully!"));
96
102
  console.log(`
97
103
  ${chalk.green("\u2713")} ${agents.length} agents`);
@@ -99,7 +105,8 @@ Installing all to ${targetDir}...
99
105
  console.log(` ${chalk.green("\u2713")} ${commands.length} commands`);
100
106
  console.log(` ${chalk.green("\u2713")} ${hooks.length} hooks`);
101
107
  console.log(` ${chalk.green("\u2713")} Rules (${ruleCategories.length} categories)`);
102
- console.log(` ${chalk.green("\u2713")} ${mcps.length} MCP servers
108
+ console.log(` ${chalk.green("\u2713")} ${mcps.length} MCP servers`);
109
+ console.log(` ${chalk.green("\u2713")} ${claudemds.length} claudemd item(s)
103
110
  `);
104
111
  } catch (err) {
105
112
  spinner.fail("Installation failed");
@@ -117,7 +124,7 @@ async function main() {
117
124
  return;
118
125
  }
119
126
  if (args.includes("--uninstall") || args.includes("-u")) {
120
- const { runUninstallCli } = await import("./uninstall-cli-KYQTJCKO.js");
127
+ const { runUninstallCli } = await import("./uninstall-cli-7XGNDIUC.js");
121
128
  await runUninstallCli();
122
129
  return;
123
130
  }
@@ -127,7 +134,7 @@ async function main() {
127
134
  await installAll(scope);
128
135
  return;
129
136
  }
130
- const { runCli } = await import("./cli-H6IDWH7Q.js");
137
+ const { runCli } = await import("./cli-6WQMAFNA.js");
131
138
  await runCli();
132
139
  }
133
140
  main().catch((err) => {
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  getAgents,
3
+ getClaudemdItems,
3
4
  getCommands,
4
5
  getHooks,
5
6
  getMcpServers,
6
7
  getRuleCategories,
7
8
  getSkills
8
- } from "./chunk-6B46AIFM.js";
9
+ } from "./chunk-OLLOS3GG.js";
9
10
  import "./chunk-5XOKKPAA.js";
10
11
  export {
11
12
  getAgents,
13
+ getClaudemdItems,
12
14
  getCommands,
13
15
  getHooks,
14
16
  getMcpServers,
@@ -0,0 +1,9 @@
1
+ import {
2
+ installRules
3
+ } from "./chunk-ID643AV4.js";
4
+ import "./chunk-K25UZZVG.js";
5
+ import "./chunk-3GUKEMND.js";
6
+ import "./chunk-5XOKKPAA.js";
7
+ export {
8
+ installRules
9
+ };
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  getAgents,
3
+ getClaudemdItems,
3
4
  getCommands,
4
5
  getHooks,
5
6
  getMcpServers,
6
7
  getRuleCategories,
7
8
  getSkills
8
- } from "./chunk-6B46AIFM.js";
9
+ } from "./chunk-OLLOS3GG.js";
9
10
  import {
10
11
  getAssetsDir,
11
12
  getGlobalDir,
@@ -16,7 +17,6 @@ import {
16
17
  import { checkbox, select, confirm } from "@inquirer/prompts";
17
18
  import chalk from "chalk";
18
19
  import ora from "ora";
19
- import { join as join8 } from "path";
20
20
 
21
21
  // src/scanner.ts
22
22
  import { readdir, readFile, access } from "fs/promises";
@@ -75,6 +75,19 @@ async function getInstalledHooks(targetDir) {
75
75
  return registryHooks.filter((h) => installedFiles.includes(h.file));
76
76
  }
77
77
  async function getInstalledRuleCategories(targetDir) {
78
+ const rulesDir = join(targetDir, "rules");
79
+ let installedDirs = [];
80
+ try {
81
+ const entries = await readdir(rulesDir, { withFileTypes: true });
82
+ installedDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
83
+ } catch {
84
+ return [];
85
+ }
86
+ const allCategories = await getRuleCategories();
87
+ const registryCategoryNames = new Set(allCategories.map((rc) => rc.category));
88
+ return installedDirs.filter((d) => registryCategoryNames.has(d));
89
+ }
90
+ async function getInstalledClaudemd(targetDir) {
78
91
  const claudeMdPath = join(targetDir, "CLAUDE.md");
79
92
  let content = "";
80
93
  try {
@@ -88,25 +101,20 @@ async function getInstalledRuleCategories(targetDir) {
88
101
  const match = line.match(/^(#{1,2}\s+.+)/);
89
102
  if (match) installedHeaders.add(match[1].trim());
90
103
  }
91
- const allCategories = await getRuleCategories();
92
- const installedCategories = [];
93
- for (const rc of allCategories) {
94
- const catDir = join(getAssetsDir(), "rules", rc.category);
95
- let found = false;
96
- for (const file of rc.files) {
97
- try {
98
- const fileContent = await readFile(join(catDir, file), "utf-8");
99
- const headerMatch = fileContent.match(/^(#{1,2}\s+.+)/m);
100
- if (headerMatch && installedHeaders.has(headerMatch[1].trim())) {
101
- found = true;
102
- break;
103
- }
104
- } catch {
104
+ const allItems = await getClaudemdItems();
105
+ const result = [];
106
+ for (const item of allItems) {
107
+ try {
108
+ const raw = await readFile(join(getAssetsDir(), "claudemd", item.file), "utf-8");
109
+ const itemContent = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
110
+ const headerMatch = itemContent.match(/^(#{1,2}\s+.+)/m);
111
+ if (headerMatch && installedHeaders.has(headerMatch[1].trim())) {
112
+ result.push(item);
105
113
  }
114
+ } catch {
106
115
  }
107
- if (found) installedCategories.push(rc.category);
108
116
  }
109
- return installedCategories;
117
+ return result;
110
118
  }
111
119
  async function getInstalledMcps(scope) {
112
120
  const filePath = getClaudeJsonPath(scope);
@@ -176,7 +184,7 @@ async function uninstallCommands(commands, targetDir) {
176
184
  import { join as join5 } from "path";
177
185
  import { rm as rm4, readFile as readFile2, writeFile, access as access5 } from "fs/promises";
178
186
  import { constants as constants5 } from "fs";
179
- async function removeHookFromSettings(settingsPath, hookCommand) {
187
+ async function removeHookFromSettings(settingsPath, hookFileName) {
180
188
  let settings = {};
181
189
  try {
182
190
  await access5(settingsPath, constants5.F_OK);
@@ -186,11 +194,12 @@ async function removeHookFromSettings(settingsPath, hookCommand) {
186
194
  return;
187
195
  }
188
196
  if (!settings.hooks) return;
197
+ const suffix = `/hooks/${hookFileName}`;
189
198
  for (const hookType of Object.keys(settings.hooks)) {
190
199
  const matchers = settings.hooks[hookType];
191
200
  for (let i = matchers.length - 1; i >= 0; i--) {
192
201
  const matcher = matchers[i];
193
- matcher.hooks = matcher.hooks.filter((h) => h.command !== hookCommand);
202
+ matcher.hooks = matcher.hooks.filter((h) => !h.command.endsWith(suffix));
194
203
  if (matcher.hooks.length === 0) {
195
204
  matchers.splice(i, 1);
196
205
  }
@@ -209,8 +218,7 @@ async function uninstallHooks(hooks, targetDir) {
209
218
  const settingsPath = join5(targetDir, "settings.json");
210
219
  for (const hook of hooks) {
211
220
  const filePath = join5(hooksDir, hook.file);
212
- const hookCommand = `node ${filePath}`;
213
- await removeHookFromSettings(settingsPath, hookCommand);
221
+ await removeHookFromSettings(settingsPath, hook.file);
214
222
  try {
215
223
  await access5(filePath, constants5.F_OK);
216
224
  await rm4(filePath);
@@ -220,69 +228,47 @@ async function uninstallHooks(hooks, targetDir) {
220
228
  }
221
229
 
222
230
  // src/uninstallers/rules.ts
223
- import { readFile as readFile3, writeFile as writeFile2, access as access6 } from "fs/promises";
224
- import { constants as constants6 } from "fs";
231
+ import { readdir as readdir2, rm as rm5 } from "fs/promises";
225
232
  import { join as join6 } from "path";
226
- async function uninstallRules(categories, claudeMdPath) {
227
- let existing = "";
228
- try {
229
- await access6(claudeMdPath, constants6.F_OK);
230
- existing = await readFile3(claudeMdPath, "utf-8");
231
- } catch {
232
- return;
233
- }
234
- const headersToRemove = /* @__PURE__ */ new Set();
233
+ async function uninstallRules(categories, targetDir) {
234
+ const assetsRulesDir = join6(getAssetsDir(), "rules");
235
235
  for (const category of categories) {
236
- const catDir = join6(getAssetsDir(), "rules", category);
237
- let files;
236
+ const srcCatDir = join6(assetsRulesDir, category);
237
+ let assetFiles;
238
238
  try {
239
- const { readdir: readdir2 } = await import("fs/promises");
240
- files = (await readdir2(catDir)).filter((f) => f.endsWith(".md"));
239
+ assetFiles = (await readdir2(srcCatDir)).filter((f) => f.endsWith(".md"));
241
240
  } catch {
242
241
  continue;
243
242
  }
244
- for (const file of files) {
243
+ const destCatDir = join6(targetDir, "rules", category);
244
+ for (const file of assetFiles) {
245
245
  try {
246
- const content = await readFile3(join6(catDir, file), "utf-8");
247
- const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
248
- if (headerMatch) {
249
- headersToRemove.add(headerMatch[1].trim());
250
- }
246
+ await rm5(join6(destCatDir, file));
251
247
  } catch {
252
248
  }
253
249
  }
254
- }
255
- if (headersToRemove.size === 0) return;
256
- const lines = existing.split("\n");
257
- const result = [];
258
- let inRemovedSection = false;
259
- let currentDepth = 0;
260
- for (const line of lines) {
261
- const headerMatch = line.match(/^(#{1,2})\s+/);
262
- if (headerMatch) {
263
- const depth = headerMatch[1].length;
264
- const trimmed = line.trim();
265
- if (headersToRemove.has(trimmed)) {
266
- inRemovedSection = true;
267
- currentDepth = depth;
268
- continue;
269
- }
270
- if (inRemovedSection && depth <= currentDepth) {
271
- inRemovedSection = false;
250
+ try {
251
+ const remaining = await readdir2(destCatDir);
252
+ if (remaining.length === 0) {
253
+ await rm5(destCatDir, { recursive: true });
272
254
  }
255
+ } catch {
273
256
  }
274
- if (!inRemovedSection) {
275
- result.push(line);
257
+ }
258
+ const rulesDir = join6(targetDir, "rules");
259
+ try {
260
+ const remaining = await readdir2(rulesDir);
261
+ if (remaining.length === 0) {
262
+ await rm5(rulesDir, { recursive: true });
276
263
  }
264
+ } catch {
277
265
  }
278
- const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
279
- await writeFile2(claudeMdPath, cleaned, "utf-8");
280
266
  }
281
267
 
282
268
  // src/uninstallers/mcps.ts
283
269
  import { join as join7 } from "path";
284
- import { readFile as readFile4, writeFile as writeFile3, access as access7 } from "fs/promises";
285
- import { constants as constants7 } from "fs";
270
+ import { readFile as readFile3, writeFile as writeFile2, access as access6 } from "fs/promises";
271
+ import { constants as constants6 } from "fs";
286
272
  import { homedir as homedir2 } from "os";
287
273
  function getClaudeJsonPath2(scope) {
288
274
  if (scope === "global") {
@@ -294,8 +280,8 @@ async function uninstallMcps(serverNames, scope) {
294
280
  const filePath = getClaudeJsonPath2(scope);
295
281
  let claudeJson = {};
296
282
  try {
297
- await access7(filePath, constants7.F_OK);
298
- const content = await readFile4(filePath, "utf-8");
283
+ await access6(filePath, constants6.F_OK);
284
+ const content = await readFile3(filePath, "utf-8");
299
285
  claudeJson = JSON.parse(content);
300
286
  } catch {
301
287
  return;
@@ -304,7 +290,59 @@ async function uninstallMcps(serverNames, scope) {
304
290
  for (const name of serverNames) {
305
291
  delete claudeJson.mcpServers[name];
306
292
  }
307
- await writeFile3(filePath, JSON.stringify(claudeJson, null, 2) + "\n", "utf-8");
293
+ await writeFile2(filePath, JSON.stringify(claudeJson, null, 2) + "\n", "utf-8");
294
+ }
295
+
296
+ // src/uninstallers/claudemd.ts
297
+ import { readFile as readFile4, writeFile as writeFile3, access as access7 } from "fs/promises";
298
+ import { constants as constants7 } from "fs";
299
+ import { join as join8 } from "path";
300
+ async function uninstallClaudemd(items, targetDir) {
301
+ const claudeMdPath = join8(targetDir, "CLAUDE.md");
302
+ let existing = "";
303
+ try {
304
+ await access7(claudeMdPath, constants7.F_OK);
305
+ existing = await readFile4(claudeMdPath, "utf-8");
306
+ } catch {
307
+ return;
308
+ }
309
+ const headersToRemove = /* @__PURE__ */ new Set();
310
+ for (const item of items) {
311
+ try {
312
+ const raw = await readFile4(join8(getAssetsDir(), "claudemd", item.file), "utf-8");
313
+ const content = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
314
+ const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
315
+ if (headerMatch) {
316
+ headersToRemove.add(headerMatch[1].trim());
317
+ }
318
+ } catch {
319
+ }
320
+ }
321
+ if (headersToRemove.size === 0) return;
322
+ const lines = existing.split("\n");
323
+ const result = [];
324
+ let inRemovedSection = false;
325
+ let currentDepth = 0;
326
+ for (const line of lines) {
327
+ const headerMatch = line.match(/^(#{1,2})\s+/);
328
+ if (headerMatch) {
329
+ const depth = headerMatch[1].length;
330
+ const trimmed = line.trim();
331
+ if (headersToRemove.has(trimmed)) {
332
+ inRemovedSection = true;
333
+ currentDepth = depth;
334
+ continue;
335
+ }
336
+ if (inRemovedSection && depth <= currentDepth) {
337
+ inRemovedSection = false;
338
+ }
339
+ }
340
+ if (!inRemovedSection) {
341
+ result.push(line);
342
+ }
343
+ }
344
+ const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
345
+ await writeFile3(claudeMdPath, cleaned, "utf-8");
308
346
  }
309
347
 
310
348
  // src/uninstall-cli.ts
@@ -344,16 +382,18 @@ async function runUninstallCli() {
344
382
  installedCommands,
345
383
  installedHooks,
346
384
  installedRuleCategories,
347
- installedMcpNames
385
+ installedMcpNames,
386
+ installedClaudemds
348
387
  ] = await Promise.all([
349
388
  getInstalledAgents(targetDir),
350
389
  getInstalledSkills(targetDir),
351
390
  getInstalledCommands(targetDir),
352
391
  getInstalledHooks(targetDir),
353
392
  getInstalledRuleCategories(targetDir),
354
- getInstalledMcps(scope)
393
+ getInstalledMcps(scope),
394
+ getInstalledClaudemd(targetDir)
355
395
  ]);
356
- const totalInstalled = installedAgents.length + installedSkills.length + installedCommands.length + installedHooks.length + installedRuleCategories.length + installedMcpNames.length;
396
+ const totalInstalled = installedAgents.length + installedSkills.length + installedCommands.length + installedHooks.length + installedRuleCategories.length + installedMcpNames.length + installedClaudemds.length;
357
397
  if (totalInstalled === 0) {
358
398
  console.log(chalk.yellow(" Nothing installed. Exiting."));
359
399
  return;
@@ -389,6 +429,11 @@ async function runUninstallCli() {
389
429
  name: `MCPs ${chalk.gray(`(${installedMcpNames.length} installed)`)}`,
390
430
  value: "mcps"
391
431
  });
432
+ if (installedClaudemds.length > 0)
433
+ availableCategories.push({
434
+ name: `ClaudeMd ${chalk.gray(`(${installedClaudemds.length} installed)`)}`,
435
+ value: "claudemd"
436
+ });
392
437
  const selectedCategories = await checkbox({
393
438
  message: "Select categories to uninstall:",
394
439
  choices: availableCategories
@@ -404,7 +449,8 @@ async function runUninstallCli() {
404
449
  commands: [],
405
450
  hooks: [],
406
451
  ruleCategories: [],
407
- mcpNames: []
452
+ mcpNames: [],
453
+ claudemds: []
408
454
  };
409
455
  if (selectedCategories.includes("agents") && installedAgents.length > 0) {
410
456
  plan.agents = await checkbox({
@@ -466,7 +512,17 @@ async function runUninstallCli() {
466
512
  }))
467
513
  });
468
514
  }
469
- const totalToRemove = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcpNames.length;
515
+ if (selectedCategories.includes("claudemd") && installedClaudemds.length > 0) {
516
+ plan.claudemds = await checkbox({
517
+ message: "Select claudemd items to uninstall:",
518
+ choices: installedClaudemds.map((c) => ({
519
+ name: `${chalk.bold(c.name.padEnd(35))} ${chalk.gray(c.description.slice(0, 55))}`,
520
+ value: c,
521
+ checked: false
522
+ }))
523
+ });
524
+ }
525
+ const totalToRemove = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcpNames.length + plan.claudemds.length;
470
526
  if (totalToRemove === 0) {
471
527
  console.log(chalk.yellow("\n Nothing selected. Exiting."));
472
528
  return;
@@ -482,6 +538,7 @@ async function runUninstallCli() {
482
538
  if (plan.ruleCategories.length)
483
539
  console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
484
540
  if (plan.mcpNames.length) console.log(` MCPs : ${plan.mcpNames.join(", ")}`);
541
+ if (plan.claudemds.length) console.log(` ClaudeMd: ${plan.claudemds.map((c) => c.name).join(", ")}`);
485
542
  console.log("");
486
543
  const ok = await confirm({
487
544
  message: chalk.red("Proceed with uninstallation? This cannot be undone."),
@@ -512,13 +569,16 @@ async function runUninstallCli() {
512
569
  }
513
570
  if (plan.ruleCategories.length) {
514
571
  spinner.text = "Removing rules...";
515
- const claudeMd = join8(targetDir, "CLAUDE.md");
516
- await uninstallRules(plan.ruleCategories, claudeMd);
572
+ await uninstallRules(plan.ruleCategories, targetDir);
517
573
  }
518
574
  if (plan.mcpNames.length) {
519
575
  spinner.text = "Removing MCP servers...";
520
576
  await uninstallMcps(plan.mcpNames, scope);
521
577
  }
578
+ if (plan.claudemds.length) {
579
+ spinner.text = "Removing claudemd...";
580
+ await uninstallClaudemd(plan.claudemds, targetDir);
581
+ }
522
582
  spinner.succeed(chalk.green("Uninstallation complete!"));
523
583
  console.log("");
524
584
  if (plan.agents.length)
@@ -533,6 +593,8 @@ async function runUninstallCli() {
533
593
  console.log(` ${chalk.red("\u2717")} Rules removed (${plan.ruleCategories.join(", ")})`);
534
594
  if (plan.mcpNames.length)
535
595
  console.log(` ${chalk.red("\u2717")} ${plan.mcpNames.length} MCP server(s) removed`);
596
+ if (plan.claudemds.length)
597
+ console.log(` ${chalk.red("\u2717")} ClaudeMd removed (${plan.claudemds.map((c) => c.name).join(", ")})`);
536
598
  console.log("");
537
599
  } catch (err) {
538
600
  spinner.fail(chalk.red("Uninstallation failed"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juho0719/cckit",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Claude Code Harness Installer - Interactive CLI for installing Claude Code agents, skills, commands, hooks, rules, and MCP servers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,44 +0,0 @@
1
- import {
2
- getAssetsDir
3
- } from "./chunk-5XOKKPAA.js";
4
-
5
- // src/installers/rules.ts
6
- import { join } from "path";
7
- import { readFile, writeFile, access } from "fs/promises";
8
- import { constants } from "fs";
9
- async function installRules(categories, targetClaudeMd) {
10
- const rulesDir = join(getAssetsDir(), "rules");
11
- let existing = "";
12
- try {
13
- await access(targetClaudeMd, constants.F_OK);
14
- existing = await readFile(targetClaudeMd, "utf-8");
15
- } catch {
16
- }
17
- const toAppend = [];
18
- for (const category of categories) {
19
- const catDir = join(rulesDir, category);
20
- let files;
21
- try {
22
- const { readdir } = await import("fs/promises");
23
- files = (await readdir(catDir)).filter((f) => f.endsWith(".md"));
24
- } catch {
25
- continue;
26
- }
27
- for (const file of files) {
28
- const content = await readFile(join(catDir, file), "utf-8");
29
- const headerMatch = content.match(/^#{1,2}\s+(.+)/m);
30
- if (headerMatch) {
31
- const header = headerMatch[0];
32
- if (existing.includes(header)) continue;
33
- }
34
- toAppend.push(content.trim());
35
- }
36
- }
37
- if (toAppend.length === 0) return;
38
- const appendContent = (existing.endsWith("\n") ? "" : "\n") + toAppend.join("\n\n") + "\n";
39
- await writeFile(targetClaudeMd, existing + appendContent, "utf-8");
40
- }
41
-
42
- export {
43
- installRules
44
- };
@@ -1,7 +0,0 @@
1
- import {
2
- installRules
3
- } from "./chunk-RMUKD7CW.js";
4
- import "./chunk-5XOKKPAA.js";
5
- export {
6
- installRules
7
- };