@infinitedusky/indusk-mcp 0.9.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/bin/cli.js +53 -4
  2. package/dist/bin/commands/extensions.d.ts +7 -0
  3. package/dist/bin/commands/extensions.js +288 -0
  4. package/dist/bin/commands/init.d.ts +0 -2
  5. package/dist/bin/commands/init.js +38 -150
  6. package/dist/lib/extension-loader.d.ts +60 -0
  7. package/dist/lib/extension-loader.js +97 -0
  8. package/dist/lib/verification-discovery.d.ts +2 -2
  9. package/dist/lib/verification-discovery.js +21 -1
  10. package/dist/tools/graph-tools.js +2 -2
  11. package/dist/tools/quality-tools.js +1 -1
  12. package/dist/tools/system-tools.js +49 -82
  13. package/extensions/cgc/manifest.json +25 -0
  14. package/extensions/cgc/skill.md +35 -0
  15. package/extensions/docker/manifest.json +9 -0
  16. package/extensions/falkordb/manifest.json +19 -0
  17. package/extensions/nextjs/manifest.json +6 -0
  18. package/extensions/react/manifest.json +6 -0
  19. package/extensions/solidity/manifest.json +6 -0
  20. package/extensions/tailwind/manifest.json +6 -0
  21. package/extensions/testing/manifest.json +12 -0
  22. package/extensions/typescript/manifest.json +11 -0
  23. package/extensions/vitepress/manifest.json +6 -0
  24. package/hooks/validate-impl-structure.js +1 -2
  25. package/lessons/community/community-add-to-sidebar.md +7 -0
  26. package/lessons/community/community-orbstack-no-port-forwarding.md +11 -0
  27. package/package.json +2 -1
  28. package/skills/context.md +10 -31
  29. package/skills/document.md +20 -38
  30. package/skills/onboard.md +8 -4
  31. package/skills/plan.md +9 -21
  32. package/skills/retrospective.md +1 -6
  33. package/skills/toolbelt.md +24 -75
  34. package/skills/verify.md +2 -1
  35. package/skills/work.md +3 -8
  36. /package/{skills/domain/docker.md → extensions/docker/skill.md} +0 -0
  37. /package/{skills/domain/nextjs.md → extensions/nextjs/skill.md} +0 -0
  38. /package/{skills/domain/react.md → extensions/react/skill.md} +0 -0
  39. /package/{skills/domain/solidity.md → extensions/solidity/skill.md} +0 -0
  40. /package/{skills/domain/tailwind.md → extensions/tailwind/skill.md} +0 -0
  41. /package/{skills/domain/testing.md → extensions/testing/skill.md} +0 -0
  42. /package/{skills/domain/typescript.md → extensions/typescript/skill.md} +0 -0
  43. /package/{skills/domain/vitepress.md → extensions/vitepress/skill.md} +0 -0
package/dist/bin/cli.js CHANGED
@@ -14,15 +14,11 @@ program
14
14
  .command("init")
15
15
  .description("Initialize a project with InDusk dev system")
16
16
  .option("-f, --force", "Overwrite existing files (except CLAUDE.md and planning/)")
17
- .option("--skills <list>", "Comma-separated domain skills to install (e.g., nextjs,tailwind)")
18
- .option("--no-domain-skills", "Skip domain skill detection and installation")
19
17
  .option("--no-index", "Skip code graph indexing")
20
18
  .action(async (opts) => {
21
19
  const { init } = await import("./commands/init.js");
22
20
  await init(process.cwd(), {
23
21
  force: opts.force ?? false,
24
- skills: opts.skills,
25
- noDomainSkills: opts.domainSkills === false,
26
22
  noIndex: opts.index === false,
27
23
  });
28
24
  });
@@ -33,6 +29,59 @@ program
33
29
  const { update } = await import("./commands/update.js");
34
30
  await update(process.cwd());
35
31
  });
32
+ const ext = program
33
+ .command("extensions")
34
+ .description("Manage extensions (built-in and third-party)");
35
+ ext
36
+ .command("list")
37
+ .description("Show all available extensions")
38
+ .action(async () => {
39
+ const { extensionsList } = await import("./commands/extensions.js");
40
+ await extensionsList(process.cwd());
41
+ });
42
+ ext
43
+ .command("status")
44
+ .description("Show enabled extensions with health")
45
+ .action(async () => {
46
+ const { extensionsStatus } = await import("./commands/extensions.js");
47
+ await extensionsStatus(process.cwd());
48
+ });
49
+ ext
50
+ .command("enable <names...>")
51
+ .description("Enable extensions")
52
+ .action(async (names) => {
53
+ const { extensionsEnable } = await import("./commands/extensions.js");
54
+ await extensionsEnable(process.cwd(), names);
55
+ });
56
+ ext
57
+ .command("disable <names...>")
58
+ .description("Disable extensions")
59
+ .action(async (names) => {
60
+ const { extensionsDisable } = await import("./commands/extensions.js");
61
+ await extensionsDisable(process.cwd(), names);
62
+ });
63
+ ext
64
+ .command("add <name>")
65
+ .description("Add a third-party extension")
66
+ .requiredOption("--from <source>", "Source: npm:pkg, github:user/repo, URL, or local path")
67
+ .action(async (name, opts) => {
68
+ const { extensionsAdd } = await import("./commands/extensions.js");
69
+ await extensionsAdd(process.cwd(), name, opts.from);
70
+ });
71
+ ext
72
+ .command("remove <names...>")
73
+ .description("Remove extensions")
74
+ .action(async (names) => {
75
+ const { extensionsRemove } = await import("./commands/extensions.js");
76
+ await extensionsRemove(process.cwd(), names);
77
+ });
78
+ ext
79
+ .command("suggest")
80
+ .description("Recommend extensions based on project contents")
81
+ .action(async () => {
82
+ const { extensionsSuggest } = await import("./commands/extensions.js");
83
+ await extensionsSuggest(process.cwd());
84
+ });
36
85
  program
37
86
  .command("init-docs")
38
87
  .description("Scaffold a VitePress documentation site with Mermaid, llms.txt, and FullscreenDiagram")
@@ -0,0 +1,7 @@
1
+ export declare function extensionsList(projectRoot: string): Promise<void>;
2
+ export declare function extensionsStatus(projectRoot: string): Promise<void>;
3
+ export declare function extensionsEnable(projectRoot: string, names: string[]): Promise<void>;
4
+ export declare function extensionsDisable(projectRoot: string, names: string[]): Promise<void>;
5
+ export declare function extensionsAdd(projectRoot: string, name: string, from: string): Promise<void>;
6
+ export declare function extensionsRemove(projectRoot: string, names: string[]): Promise<void>;
7
+ export declare function extensionsSuggest(projectRoot: string): Promise<void>;
@@ -0,0 +1,288 @@
1
+ import { execSync } from "node:child_process";
2
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { globSync } from "glob";
6
+ import { disableExtension, enableExtension, ensureExtensionsDirs, extensionsDir, getEnabledExtensions, isEnabled, loadExtension, loadExtensions, } from "../../lib/extension-loader.js";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const packageRoot = join(__dirname, "../../..");
9
+ const builtinDir = join(packageRoot, "extensions");
10
+ function getBuiltinExtensions() {
11
+ if (!existsSync(builtinDir))
12
+ return [];
13
+ const dirs = globSync("*/manifest.json", { cwd: builtinDir });
14
+ return dirs
15
+ .map((d) => loadExtension(join(builtinDir, d)))
16
+ .filter((m) => m !== null);
17
+ }
18
+ export async function extensionsList(projectRoot) {
19
+ const builtins = getBuiltinExtensions();
20
+ const installed = loadExtensions(projectRoot);
21
+ console.info("Built-in extensions:\n");
22
+ for (const ext of builtins) {
23
+ const status = isEnabled(projectRoot, ext.name)
24
+ ? "enabled"
25
+ : installed.some((i) => i.manifest.name === ext.name)
26
+ ? "disabled"
27
+ : "not installed";
28
+ console.info(` ${ext.name} — ${ext.description} [${status}]`);
29
+ }
30
+ const thirdParty = installed.filter((i) => !builtins.some((b) => b.name === i.manifest.name));
31
+ if (thirdParty.length > 0) {
32
+ console.info("\nThird-party extensions:\n");
33
+ for (const ext of thirdParty) {
34
+ const status = ext.enabled ? "enabled" : "disabled";
35
+ console.info(` ${ext.manifest.name} — ${ext.manifest.description} [${status}]`);
36
+ }
37
+ }
38
+ }
39
+ export async function extensionsStatus(projectRoot) {
40
+ const enabled = getEnabledExtensions(projectRoot);
41
+ if (enabled.length === 0) {
42
+ console.info("No extensions enabled. Run `extensions list` to see available.");
43
+ return;
44
+ }
45
+ console.info("Enabled extensions:\n");
46
+ for (const ext of enabled) {
47
+ const checks = ext.manifest.provides.health_checks ?? [];
48
+ let healthStatus = "no health check";
49
+ if (checks.length > 0) {
50
+ const results = checks.map((check) => {
51
+ try {
52
+ execSync(check.command, {
53
+ cwd: projectRoot,
54
+ timeout: 10000,
55
+ stdio: ["ignore", "pipe", "pipe"],
56
+ });
57
+ return { name: check.name, ok: true };
58
+ }
59
+ catch {
60
+ return { name: check.name, ok: false };
61
+ }
62
+ });
63
+ const allOk = results.every((r) => r.ok);
64
+ healthStatus = allOk
65
+ ? "healthy"
66
+ : `unhealthy: ${results
67
+ .filter((r) => !r.ok)
68
+ .map((r) => r.name)
69
+ .join(", ")}`;
70
+ }
71
+ console.info(` ${ext.manifest.name} — ${healthStatus}`);
72
+ }
73
+ }
74
+ export async function extensionsEnable(projectRoot, names) {
75
+ ensureExtensionsDirs(projectRoot);
76
+ for (const name of names) {
77
+ // Check if already enabled
78
+ if (isEnabled(projectRoot, name)) {
79
+ console.info(` ${name}: already enabled`);
80
+ continue;
81
+ }
82
+ // Try to move from disabled
83
+ if (enableExtension(projectRoot, name)) {
84
+ console.info(` ${name}: enabled (was disabled)`);
85
+ runHook(projectRoot, name, "on_init");
86
+ installSkill(projectRoot, name);
87
+ continue;
88
+ }
89
+ // Try to copy from built-in
90
+ const builtinManifest = join(builtinDir, name, "manifest.json");
91
+ if (existsSync(builtinManifest)) {
92
+ const targetPath = join(extensionsDir(projectRoot), `${name}.json`);
93
+ cpSync(builtinManifest, targetPath);
94
+ console.info(` ${name}: enabled (built-in)`);
95
+ runHook(projectRoot, name, "on_init");
96
+ installSkill(projectRoot, name);
97
+ continue;
98
+ }
99
+ console.info(` ${name}: not found — use 'extensions add ${name} --from <source>' for third-party`);
100
+ }
101
+ }
102
+ export async function extensionsDisable(projectRoot, names) {
103
+ for (const name of names) {
104
+ if (disableExtension(projectRoot, name)) {
105
+ console.info(` ${name}: disabled`);
106
+ }
107
+ else {
108
+ console.info(` ${name}: not found or already disabled`);
109
+ }
110
+ }
111
+ }
112
+ export async function extensionsAdd(projectRoot, name, from) {
113
+ ensureExtensionsDirs(projectRoot);
114
+ let manifestContent = null;
115
+ if (from.startsWith("npm:")) {
116
+ // Fetch from npm package
117
+ const pkg = from.slice(4);
118
+ try {
119
+ const result = execSync(`npm pack ${pkg} --dry-run --json 2>/dev/null || echo "[]"`, {
120
+ encoding: "utf-8",
121
+ timeout: 30000,
122
+ });
123
+ // Try to read the manifest from the installed package
124
+ const npmRoot = execSync(`npm root`, { encoding: "utf-8", cwd: projectRoot }).trim();
125
+ const manifestPath = join(npmRoot, pkg, "indusk-extension.json");
126
+ if (existsSync(manifestPath)) {
127
+ manifestContent = readFileSync(manifestPath, "utf-8");
128
+ }
129
+ else {
130
+ console.info(` ${name}: no indusk-extension.json found in ${pkg}`);
131
+ return;
132
+ }
133
+ }
134
+ catch {
135
+ console.info(` ${name}: failed to fetch from npm:${pkg}`);
136
+ return;
137
+ }
138
+ }
139
+ else if (from.startsWith("github:")) {
140
+ // Fetch from GitHub raw
141
+ const repo = from.slice(7);
142
+ const url = `https://raw.githubusercontent.com/${repo}/main/indusk-extension.json`;
143
+ try {
144
+ const result = execSync(`curl -sf "${url}"`, { encoding: "utf-8", timeout: 15000 });
145
+ manifestContent = result;
146
+ }
147
+ catch {
148
+ console.info(` ${name}: failed to fetch from ${url}`);
149
+ return;
150
+ }
151
+ }
152
+ else if (from.startsWith("http://") || from.startsWith("https://")) {
153
+ try {
154
+ manifestContent = execSync(`curl -sf "${from}"`, { encoding: "utf-8", timeout: 15000 });
155
+ }
156
+ catch {
157
+ console.info(` ${name}: failed to fetch from ${from}`);
158
+ return;
159
+ }
160
+ }
161
+ else {
162
+ // Local path
163
+ if (existsSync(from)) {
164
+ manifestContent = readFileSync(from, "utf-8");
165
+ }
166
+ else {
167
+ console.info(` ${name}: file not found at ${from}`);
168
+ return;
169
+ }
170
+ }
171
+ if (!manifestContent)
172
+ return;
173
+ // Validate
174
+ try {
175
+ const manifest = JSON.parse(manifestContent);
176
+ if (!manifest.name || !manifest.provides) {
177
+ console.info(` ${name}: invalid manifest (missing name or provides)`);
178
+ return;
179
+ }
180
+ }
181
+ catch {
182
+ console.info(` ${name}: invalid JSON in manifest`);
183
+ return;
184
+ }
185
+ const targetPath = join(extensionsDir(projectRoot), `${name}.json`);
186
+ writeFileSync(targetPath, manifestContent);
187
+ console.info(` ${name}: added from ${from}`);
188
+ }
189
+ export async function extensionsRemove(projectRoot, names) {
190
+ for (const name of names) {
191
+ const enPath = join(extensionsDir(projectRoot), `${name}.json`);
192
+ const disPath = join(extensionsDir(projectRoot), ".disabled", `${name}.json`);
193
+ if (existsSync(enPath)) {
194
+ rmSync(enPath);
195
+ console.info(` ${name}: removed`);
196
+ }
197
+ else if (existsSync(disPath)) {
198
+ rmSync(disPath);
199
+ console.info(` ${name}: removed (was disabled)`);
200
+ }
201
+ else {
202
+ console.info(` ${name}: not found`);
203
+ }
204
+ // Remove skill if installed
205
+ const skillDir = join(projectRoot, ".claude/skills", name);
206
+ if (existsSync(skillDir)) {
207
+ rmSync(skillDir, { recursive: true });
208
+ console.info(` ${name}: skill removed from .claude/skills/`);
209
+ }
210
+ }
211
+ }
212
+ export async function extensionsSuggest(projectRoot) {
213
+ const builtins = getBuiltinExtensions();
214
+ const suggestions = [];
215
+ for (const ext of builtins) {
216
+ if (isEnabled(projectRoot, ext.name))
217
+ continue;
218
+ if (!ext.detect)
219
+ continue;
220
+ if (ext.detect.file && existsSync(join(projectRoot, ext.detect.file))) {
221
+ suggestions.push({ name: ext.name, reason: `${ext.detect.file} found` });
222
+ continue;
223
+ }
224
+ if (ext.detect.file_pattern) {
225
+ const matches = globSync(ext.detect.file_pattern, { cwd: projectRoot, maxDepth: 3 });
226
+ if (matches.length > 0) {
227
+ suggestions.push({ name: ext.name, reason: `${ext.detect.file_pattern} found` });
228
+ continue;
229
+ }
230
+ }
231
+ if (ext.detect.dependency || ext.detect.devDependency) {
232
+ const pkgPath = join(projectRoot, "package.json");
233
+ if (existsSync(pkgPath)) {
234
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
235
+ const deps = pkg.dependencies ?? {};
236
+ const devDeps = pkg.devDependencies ?? {};
237
+ if (ext.detect.dependency && deps[ext.detect.dependency]) {
238
+ suggestions.push({ name: ext.name, reason: `${ext.detect.dependency} in dependencies` });
239
+ }
240
+ else if (ext.detect.devDependency && devDeps[ext.detect.devDependency]) {
241
+ suggestions.push({
242
+ name: ext.name,
243
+ reason: `${ext.detect.devDependency} in devDependencies`,
244
+ });
245
+ }
246
+ }
247
+ }
248
+ }
249
+ if (suggestions.length === 0) {
250
+ console.info("No extension suggestions — all detected extensions are already enabled.");
251
+ return;
252
+ }
253
+ console.info("Suggested extensions:\n");
254
+ for (const s of suggestions) {
255
+ console.info(` ${s.name} — ${s.reason}`);
256
+ }
257
+ console.info(`\nEnable with: extensions enable ${suggestions.map((s) => s.name).join(" ")}`);
258
+ }
259
+ // --- Helpers ---
260
+ function runHook(projectRoot, name, hook) {
261
+ const extPath = join(extensionsDir(projectRoot), `${name}.json`);
262
+ const manifest = loadExtension(extPath);
263
+ if (!manifest?.hooks)
264
+ return;
265
+ const command = manifest.hooks[hook];
266
+ if (!command)
267
+ return;
268
+ try {
269
+ execSync(command, { cwd: projectRoot, stdio: "inherit", timeout: 30000 });
270
+ }
271
+ catch {
272
+ console.info(` ${name}: ${hook} hook failed`);
273
+ }
274
+ }
275
+ function installSkill(projectRoot, name) {
276
+ const extPath = join(extensionsDir(projectRoot), `${name}.json`);
277
+ const manifest = loadExtension(extPath);
278
+ if (!manifest?.provides.skill)
279
+ return;
280
+ // Look for skill.md in built-in extensions
281
+ const builtinSkill = join(builtinDir, name, "skill.md");
282
+ if (existsSync(builtinSkill)) {
283
+ const targetDir = join(projectRoot, ".claude/skills", name);
284
+ mkdirSync(targetDir, { recursive: true });
285
+ cpSync(builtinSkill, join(targetDir, "SKILL.md"));
286
+ console.info(` ${name}: skill installed to .claude/skills/${name}/SKILL.md`);
287
+ }
288
+ }
@@ -1,7 +1,5 @@
1
1
  export interface InitOptions {
2
2
  force?: boolean;
3
- skills?: string;
4
- noDomainSkills?: boolean;
5
3
  noIndex?: boolean;
6
4
  }
7
5
  export declare function init(projectRoot: string, options?: InitOptions): Promise<void>;
@@ -17,34 +17,6 @@ function run(cmd, options) {
17
17
  return "";
18
18
  }
19
19
  }
20
- function ensureFalkorDB() {
21
- // Check if FalkorDB container exists and is running
22
- const status = run('docker ps --filter name=falkordb --format "{{.Status}}"');
23
- if (status) {
24
- console.info(" ok: FalkorDB container running");
25
- return;
26
- }
27
- // Check if container exists but is stopped
28
- const stopped = run('docker ps -a --filter name=falkordb --format "{{.Status}}"');
29
- if (stopped) {
30
- console.info(" start: FalkorDB container (was stopped)");
31
- run("docker start falkordb");
32
- return;
33
- }
34
- // Create and start the container
35
- console.info(" create: FalkorDB container (global, persistent)");
36
- run("docker run -d --name falkordb --restart unless-stopped -p 6379:6379 -v falkordb-global:/data falkordb/falkordb:latest");
37
- }
38
- function checkCGC() {
39
- const cgcPaths = [join(process.env.HOME ?? "", ".local/bin/cgc"), "/usr/local/bin/cgc"];
40
- const found = cgcPaths.find((p) => existsSync(p));
41
- if (found) {
42
- console.info(` ok: CGC found at ${found}`);
43
- return true;
44
- }
45
- console.info(" missing: CGC — install via: pipx install codegraphcontext");
46
- return false;
47
- }
48
20
  function createCgcIgnore(projectRoot) {
49
21
  const ignorePath = join(projectRoot, ".cgcignore");
50
22
  if (existsSync(ignorePath)) {
@@ -70,56 +42,8 @@ function createCgcIgnore(projectRoot) {
70
42
  ].join("\n"));
71
43
  console.info(" create: .cgcignore");
72
44
  }
73
- const DOMAIN_DETECTION_MAP = [
74
- { signal: "dependency", match: "next", skill: "nextjs" },
75
- { signal: "dependency", match: "tailwindcss", skill: "tailwind" },
76
- { signal: "dependency", match: "react", skill: "react" },
77
- { signal: "devDependency", match: "typescript", skill: "typescript" },
78
- { signal: "devDependency", match: "vitest", skill: "testing" },
79
- { signal: "devDependency", match: "jest", skill: "testing" },
80
- { signal: "dependency", match: "vitepress", skill: "vitepress" },
81
- ];
82
- const FILE_PATTERN_DETECTIONS = [
83
- { pattern: "*.sol", skill: "solidity" },
84
- { pattern: "Dockerfile*", skill: "docker" },
85
- ];
86
- function detectDomainSkills(projectRoot) {
87
- const detections = [];
88
- const seen = new Set();
89
- // Check package.json dependencies
90
- const pkgPath = join(projectRoot, "package.json");
91
- if (existsSync(pkgPath)) {
92
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
93
- const deps = pkg.dependencies ?? {};
94
- const devDeps = pkg.devDependencies ?? {};
95
- for (const rule of DOMAIN_DETECTION_MAP) {
96
- if (seen.has(rule.skill))
97
- continue;
98
- const source = rule.signal === "dependency" ? deps : devDeps;
99
- if (source[rule.match]) {
100
- detections.push({
101
- skill: rule.skill,
102
- signal: rule.signal,
103
- match: rule.match,
104
- });
105
- seen.add(rule.skill);
106
- }
107
- }
108
- }
109
- // Check file patterns
110
- for (const rule of FILE_PATTERN_DETECTIONS) {
111
- if (seen.has(rule.skill))
112
- continue;
113
- const matches = globSync(rule.pattern, { cwd: projectRoot, maxDepth: 3 });
114
- if (matches.length > 0) {
115
- detections.push({ skill: rule.skill, signal: "file-pattern", match: rule.pattern });
116
- seen.add(rule.skill);
117
- }
118
- }
119
- return detections;
120
- }
121
45
  export async function init(projectRoot, options = {}) {
122
- const { force = false, skills, noDomainSkills = false, noIndex = false } = options;
46
+ const { force = false, noIndex = false } = options;
123
47
  const projectName = basename(projectRoot);
124
48
  console.info(`Initializing InDusk dev system...${force ? " (--force)" : ""}\n`);
125
49
  // 1. Copy skills
@@ -139,49 +63,7 @@ export async function init(projectRoot, options = {}) {
139
63
  cpSync(join(skillsSource, file), targetFile);
140
64
  console.info(` ${existsSync(targetFile) ? "overwrite" : "create"}: .claude/skills/${skillName}/SKILL.md`);
141
65
  }
142
- // 2. Install domain skills
143
- if (!noDomainSkills) {
144
- console.info("\n[Domain Skills]");
145
- const domainSource = join(packageRoot, "skills/domain");
146
- let skillsToInstall;
147
- if (skills) {
148
- skillsToInstall = skills.split(",").map((s) => s.trim());
149
- console.info(` manual: installing ${skillsToInstall.join(", ")}`);
150
- }
151
- else {
152
- const detections = detectDomainSkills(projectRoot);
153
- skillsToInstall = detections.map((d) => d.skill);
154
- if (detections.length > 0) {
155
- for (const d of detections) {
156
- console.info(` detected: ${d.skill} (${d.signal}: ${d.match})`);
157
- }
158
- }
159
- else {
160
- console.info(" none detected");
161
- }
162
- }
163
- for (const skillName of skillsToInstall) {
164
- const sourceFile = join(domainSource, `${skillName}.md`);
165
- if (!existsSync(sourceFile)) {
166
- console.info(` skip: ${skillName} (not found in registry)`);
167
- continue;
168
- }
169
- const targetDir = join(skillsTarget, skillName);
170
- const targetFile = join(targetDir, "SKILL.md");
171
- if (existsSync(targetFile) && !force) {
172
- console.info(` skip: .claude/skills/${skillName}/SKILL.md (already exists)`);
173
- continue;
174
- }
175
- mkdirSync(targetDir, { recursive: true });
176
- cpSync(sourceFile, targetFile);
177
- console.info(` install: .claude/skills/${skillName}/SKILL.md`);
178
- }
179
- }
180
- else {
181
- console.info("\n[Domain Skills]");
182
- console.info(" skipped (--no-domain-skills)");
183
- }
184
- // 3. Copy community lessons
66
+ // 2. Copy community lessons
185
67
  console.info("\n[Lessons]");
186
68
  const lessonsSource = join(packageRoot, "lessons/community");
187
69
  const lessonsTarget = join(projectRoot, ".claude/lessons");
@@ -232,8 +114,7 @@ export async function init(projectRoot, options = {}) {
232
114
  args: ["mcp", "start"],
233
115
  env: {
234
116
  DATABASE_TYPE: "falkordb-remote",
235
- FALKORDB_HOST: "localhost",
236
- FALKORDB_PORT: "6379",
117
+ FALKORDB_HOST: "falkordb.orb.local",
237
118
  FALKORDB_GRAPH_NAME: projectName,
238
119
  },
239
120
  };
@@ -367,47 +248,54 @@ export async function init(projectRoot, options = {}) {
367
248
  }
368
249
  // 8. Create .cgcignore (always overwrite — package-owned)
369
250
  createCgcIgnore(projectRoot);
370
- // 8. Infrastructure: FalkorDB + CGC
371
- console.info("\n[Infrastructure]");
372
- const dockerAvailable = run("docker info") !== "";
373
- if (dockerAvailable) {
374
- ensureFalkorDB();
251
+ // 9. Run on_init hooks from enabled extensions
252
+ console.info("\n[Extension Hooks]");
253
+ const { getEnabledExtensions } = await import("../../lib/extension-loader.js");
254
+ const enabledExts = getEnabledExtensions(projectRoot);
255
+ for (const ext of enabledExts) {
256
+ if (ext.manifest.hooks?.on_init) {
257
+ console.info(` ${ext.manifest.name}: running on_init...`);
258
+ const result = run(ext.manifest.hooks.on_init);
259
+ if (result) {
260
+ console.info(` ${ext.manifest.name}: ${result.slice(0, 100)}`);
261
+ }
262
+ }
375
263
  }
376
- else {
377
- console.info(" missing: Dockerinstall Docker or OrbStack to enable FalkorDB");
264
+ if (enabledExts.length === 0) {
265
+ console.info(" no extensions enabled run 'extensions enable' to add tool integrations");
378
266
  }
379
- const cgcInstalled = checkCGC();
380
- // 9. Auto-index the codebase into the graph
267
+ // 10. Auto-index the codebase into the graph (if CGC extension is enabled)
268
+ const cgcEnabled = enabledExts.some((e) => e.manifest.name === "cgc");
381
269
  if (noIndex) {
382
270
  console.info("\n[Code Graph]");
383
271
  console.info(" skipped (--no-index)");
384
272
  }
385
- else if (dockerAvailable && cgcInstalled) {
273
+ else if (cgcEnabled) {
386
274
  console.info("\n[Code Graph]");
387
275
  console.info(" indexing: scanning codebase...");
388
- const { indexProject } = await import("../../tools/graph-tools.js");
389
- const result = indexProject(projectRoot);
390
- if (result.success) {
391
- console.info(` done: ${result.output}`);
276
+ try {
277
+ const { indexProject } = await import("../../tools/graph-tools.js");
278
+ const result = indexProject(projectRoot);
279
+ if (result.success) {
280
+ console.info(` done: ${result.output}`);
281
+ }
282
+ else {
283
+ console.info(` error: ${result.output}`);
284
+ }
392
285
  }
393
- else {
394
- console.info(` error: ${result.output}`);
286
+ catch {
287
+ console.info(" skipped (CGC not available)");
395
288
  }
396
289
  }
290
+ // 11. Suggest extensions
291
+ console.info("\n[Extensions]");
292
+ const { extensionsSuggest } = await import("./extensions.js");
293
+ await extensionsSuggest(projectRoot);
397
294
  // Summary
398
295
  console.info("\nDone!");
399
- if (!cgcInstalled || !dockerAvailable) {
400
- console.info("\nManual steps needed:");
401
- if (!dockerAvailable) {
402
- console.info(" 1. Install Docker or OrbStack");
403
- console.info(" 2. Re-run init to set up FalkorDB");
404
- }
405
- if (!cgcInstalled) {
406
- console.info(" - Install CGC: pipx install codegraphcontext");
407
- }
408
- }
409
296
  console.info("\nNext steps:");
410
297
  console.info(" 1. Edit CLAUDE.md with your project details");
411
- console.info(" 2. Start a Claude Code session MCP tools will be available");
412
- console.info(" 3. Start planning: /plan your-first-feature");
298
+ console.info(" 2. Enable extensions: extensions enable falkordb cgc typescript");
299
+ console.info(" 3. Start a Claude Code session — MCP tools will be available");
300
+ console.info(" 4. Start planning: /plan your-first-feature");
413
301
  }
@@ -0,0 +1,60 @@
1
+ export interface DetectRule {
2
+ file?: string;
3
+ file_pattern?: string;
4
+ dependency?: string;
5
+ devDependency?: string;
6
+ }
7
+ export interface HealthCheck {
8
+ name: string;
9
+ command: string;
10
+ }
11
+ export interface VerificationEntry {
12
+ name: string;
13
+ command: string;
14
+ detect?: DetectRule;
15
+ }
16
+ export interface ExtensionManifest {
17
+ name: string;
18
+ description: string;
19
+ version?: string;
20
+ provides: {
21
+ skill?: boolean;
22
+ networking?: {
23
+ env_file?: string;
24
+ command?: string;
25
+ description?: string;
26
+ };
27
+ services?: {
28
+ command?: string;
29
+ description?: string;
30
+ };
31
+ health_checks?: HealthCheck[];
32
+ verification?: VerificationEntry[];
33
+ env_vars?: Record<string, string> | {
34
+ source: string;
35
+ files?: string[];
36
+ };
37
+ };
38
+ hooks?: {
39
+ on_init?: string;
40
+ on_update?: string;
41
+ on_health_check?: string;
42
+ on_onboard?: string;
43
+ };
44
+ detect?: DetectRule;
45
+ }
46
+ export interface LoadedExtension {
47
+ manifest: ExtensionManifest;
48
+ path: string;
49
+ enabled: boolean;
50
+ }
51
+ export declare function extensionsDir(projectRoot: string): string;
52
+ export declare function disabledDir(projectRoot: string): string;
53
+ export declare function ensureExtensionsDirs(projectRoot: string): void;
54
+ export declare function loadExtension(manifestPath: string): ExtensionManifest | null;
55
+ export declare function loadExtensions(projectRoot: string): LoadedExtension[];
56
+ export declare function getEnabledExtensions(projectRoot: string): LoadedExtension[];
57
+ export declare function enableExtension(projectRoot: string, name: string): boolean;
58
+ export declare function disableExtension(projectRoot: string, name: string): boolean;
59
+ export declare function isEnabled(projectRoot: string, name: string): boolean;
60
+ export declare function getExtension(projectRoot: string, name: string): LoadedExtension | null;