@fenglimg/fabric-cli 1.6.0 → 1.8.0-rc.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 (45) hide show
  1. package/README.md +8 -14
  2. package/dist/{chunk-QSAEGVKE.js → chunk-NMMUETVK.js} +4 -8
  3. package/dist/{chunk-AEOYCVBG.js → chunk-QPCRBQ5Y.js} +52 -5
  4. package/dist/doctor-F52XWWZC.js +98 -0
  5. package/dist/index.js +5 -20
  6. package/dist/{init-LBVOI2QI.js → init-AEO5JU7R.js} +1084 -167
  7. package/dist/{scan-QH76LC7Z.js → scan-NNBNGIZG.js} +2 -4
  8. package/dist/{serve-4J2CQY25.js → serve-466QXQ5Q.js} +17 -9
  9. package/package.json +5 -7
  10. package/templates/agents-md/AGENTS.md.template +7 -7
  11. package/templates/agents-md/variants/cocos.md +7 -7
  12. package/templates/agents-md/variants/next.md +7 -7
  13. package/templates/agents-md/variants/vite.md +7 -7
  14. package/templates/bootstrap/CLAUDE.md +3 -1
  15. package/templates/bootstrap/GEMINI.md +3 -1
  16. package/templates/bootstrap/codex-AGENTS-header.md +3 -1
  17. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +5 -6
  18. package/templates/bootstrap/roo-fabric.md +5 -6
  19. package/templates/bootstrap/windsurf-fabric.md +5 -6
  20. package/templates/claude-skills/fabric-init/SKILL.md +163 -0
  21. package/templates/codex-skills/fabric-init/SKILL.md +153 -18
  22. package/templates/husky/pre-commit +9 -24
  23. package/templates/skill-source/fabric-init/SOURCE.md +157 -0
  24. package/templates/skill-source/fabric-init/clients.json +17 -0
  25. package/dist/approve-YT4DEABS.js +0 -138
  26. package/dist/bootstrap-VGL3AR26.js +0 -16
  27. package/dist/chunk-2YW5CJ32.js +0 -147
  28. package/dist/chunk-6ICJICVU.js +0 -10
  29. package/dist/chunk-BEKSXO5N.js +0 -442
  30. package/dist/chunk-BVTMVW5M.js +0 -159
  31. package/dist/chunk-KOAEIH72.js +0 -270
  32. package/dist/chunk-L43IGJ6X.js +0 -106
  33. package/dist/chunk-T2WJF5I3.js +0 -254
  34. package/dist/chunk-WWNXR34K.js +0 -49
  35. package/dist/chunk-YDZJRLHL.js +0 -155
  36. package/dist/config-EC5L2QNI.js +0 -16
  37. package/dist/doctor-4BPYHV7V.js +0 -134
  38. package/dist/hooks-ZSWVH2JD.js +0 -12
  39. package/dist/human-lint-YSFOZHZ7.js +0 -13
  40. package/dist/ledger-append-3MDNR3GU.js +0 -10
  41. package/dist/pre-commit-53ENJDRZ.js +0 -98
  42. package/dist/sync-meta-IZR2WLIL.js +0 -16
  43. package/dist/update-M5M5PYKE.js +0 -116
  44. package/templates/claude-skills/agents-md-init/SKILL.md +0 -86
  45. package/templates/fabric/human-lock.json +0 -12
@@ -1,47 +1,892 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- buildFabricBootstrapGuide,
4
- installBootstrap
5
- } from "./chunk-T2WJF5I3.js";
6
- import {
3
+ createScanReport,
7
4
  detectFramework
8
- } from "./chunk-QSAEGVKE.js";
5
+ } from "./chunk-NMMUETVK.js";
9
6
  import {
10
7
  createDebugLogger,
11
- resolveDevMode
12
- } from "./chunk-AEOYCVBG.js";
13
- import {
14
8
  displayWidth,
15
9
  padEnd,
16
- paint
17
- } from "./chunk-WWNXR34K.js";
18
- import {
19
- installMcpClients
20
- } from "./chunk-BVTMVW5M.js";
21
- import {
22
- detectClientSupports
23
- } from "./chunk-BEKSXO5N.js";
24
- import {
25
- installHooks
26
- } from "./chunk-YDZJRLHL.js";
27
- import {
10
+ paint,
11
+ readFabricConfig,
12
+ resolveDevMode,
28
13
  t
29
- } from "./chunk-6ICJICVU.js";
14
+ } from "./chunk-QPCRBQ5Y.js";
30
15
 
31
16
  // src/commands/init.ts
32
- import { createHash } from "crypto";
17
+ import { createHash, randomUUID } from "crypto";
33
18
  import * as childProcess from "child_process";
34
- import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
35
- import { dirname, isAbsolute as isAbsolute2, join as join2, parse, resolve as resolve2 } from "path";
36
- import { fileURLToPath } from "url";
19
+ import { appendFileSync, chmodSync as chmodSync2, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync3, writeFileSync } from "fs";
20
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join8, parse as parse3, resolve as resolve9 } from "path";
21
+ import { fileURLToPath as fileURLToPath4 } from "url";
37
22
  import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
23
+ import { atomicWriteJson as atomicWriteJson3, atomicWriteText as atomicWriteText4 } from "@fenglimg/fabric-shared/node/atomic-write";
24
+ import { defineCommand as defineCommand4 } from "citty";
25
+ import { checkLockOrThrow } from "@fenglimg/fabric-server";
26
+
27
+ // src/bootstrap-guide.ts
28
+ import { existsSync, mkdirSync, readFileSync } from "fs";
29
+ import { dirname, isAbsolute, join, parse, resolve } from "path";
30
+ import { fileURLToPath } from "url";
31
+ import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
32
+ var AGENTS_TEMPLATE_BY_FRAMEWORK = {
33
+ "cocos-creator": "templates/agents-md/variants/cocos.md",
34
+ vite: "templates/agents-md/variants/vite.md",
35
+ next: "templates/agents-md/variants/next.md"
36
+ };
37
+ var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
38
+ async function buildFabricBootstrapGuide(target) {
39
+ const workspaceRoot = normalizeTarget(target);
40
+ const scanReport = await createScanReport(workspaceRoot);
41
+ const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
42
+ const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
43
+ return ensureTrailingNewline(
44
+ template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
45
+ );
46
+ }
47
+ async function ensureFabricBootstrapGuide(workspaceRoot, force) {
48
+ const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
49
+ if (existsSync(guidePath) && !force) {
50
+ return;
51
+ }
52
+ mkdirSync(dirname(guidePath), { recursive: true });
53
+ await atomicWriteText(guidePath, await buildFabricBootstrapGuide(workspaceRoot));
54
+ }
55
+ function findBootstrapTemplatePath(frameworkKind) {
56
+ const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
57
+ return findTemplatePath(relativePath);
58
+ }
59
+ function readPackageName(target) {
60
+ const packageJsonPath = join(target, "package.json");
61
+ if (!existsSync(packageJsonPath)) {
62
+ return void 0;
63
+ }
64
+ try {
65
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
66
+ return packageJson.name;
67
+ } catch {
68
+ return void 0;
69
+ }
70
+ }
71
+ function findTemplatePath(relativePath) {
72
+ const currentModuleDir = dirname(fileURLToPath(import.meta.url));
73
+ const candidates = [
74
+ ...templateCandidatesFrom(process.cwd(), relativePath),
75
+ ...templateCandidatesFrom(currentModuleDir, relativePath)
76
+ ];
77
+ for (const candidate of candidates) {
78
+ if (existsSync(candidate)) {
79
+ return candidate;
80
+ }
81
+ }
82
+ throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
83
+ }
84
+ function templateCandidatesFrom(start, relativePath) {
85
+ const candidates = [];
86
+ let current = resolve(start);
87
+ while (true) {
88
+ candidates.push(join(current, ...relativePath.split("/")));
89
+ const parent = dirname(current);
90
+ if (parent === current || parse(current).root === current) {
91
+ break;
92
+ }
93
+ current = parent;
94
+ }
95
+ return candidates.reverse();
96
+ }
97
+ function ensureTrailingNewline(content) {
98
+ return content.endsWith("\n") ? content : `${content}
99
+ `;
100
+ }
101
+ function normalizeTarget(targetInput) {
102
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
103
+ }
104
+
105
+ // src/commands/bootstrap.ts
106
+ import { resolve as resolve5 } from "path";
38
107
  import { defineCommand } from "citty";
39
108
 
109
+ // src/config/resolver.ts
110
+ import { existsSync as existsSync5 } from "fs";
111
+ import { join as join5 } from "path";
112
+ import { homedir as homedir4 } from "os";
113
+
114
+ // src/config/claude-code.ts
115
+ import { existsSync as existsSync3 } from "fs";
116
+ import { join as join3, resolve as resolve3 } from "path";
117
+ import { homedir as homedir2, platform } from "os";
118
+
119
+ // src/config/json.ts
120
+ import { existsSync as existsSync2 } from "fs";
121
+ import { mkdir, readFile } from "fs/promises";
122
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
123
+ import { homedir } from "os";
124
+ import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
125
+
126
+ // src/config/writer.ts
127
+ function createServerEntry(serverPath) {
128
+ return {
129
+ command: process.execPath,
130
+ args: [serverPath]
131
+ };
132
+ }
133
+
134
+ // src/config/json.ts
135
+ function deepMerge(target, source) {
136
+ if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
137
+ return source;
138
+ }
139
+ const out = { ...target };
140
+ for (const key of Object.keys(source)) {
141
+ out[key] = deepMerge(
142
+ target[key],
143
+ source[key]
144
+ );
145
+ }
146
+ return out;
147
+ }
148
+ function expandHome(filePath) {
149
+ if (filePath === "~") {
150
+ return homedir();
151
+ }
152
+ if (filePath.startsWith("~/")) {
153
+ return join2(homedir(), filePath.slice(2));
154
+ }
155
+ return filePath;
156
+ }
157
+ function normalizeConfigPath(filePath) {
158
+ return resolve2(expandHome(filePath));
159
+ }
160
+ async function readJsonConfig(configPath) {
161
+ try {
162
+ const raw = await readFile(configPath, "utf8");
163
+ if (raw.trim().length === 0) {
164
+ return {};
165
+ }
166
+ const parsed = JSON.parse(raw);
167
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
168
+ throw new Error(`Expected JSON object in ${configPath}`);
169
+ }
170
+ return parsed;
171
+ } catch (error) {
172
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
173
+ return {};
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+ async function writeJsonClientConfig(configPath, serverEntry) {
179
+ const existing = await readJsonConfig(configPath);
180
+ const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
181
+ await mkdir(dirname2(configPath), { recursive: true });
182
+ await atomicWriteJson(configPath, merged, { indent: 2 });
183
+ }
184
+ var JsonClientConfigWriter = class {
185
+ configuredPath;
186
+ constructor(configuredPath) {
187
+ this.configuredPath = configuredPath;
188
+ }
189
+ async detect(workspaceRoot, overridePath) {
190
+ const explicitPath = overridePath ?? this.configuredPath;
191
+ if (explicitPath !== void 0) {
192
+ return normalizeConfigPath(explicitPath);
193
+ }
194
+ const configPath = this.defaultPath(workspaceRoot);
195
+ return configPath === null ? null : normalizeConfigPath(configPath);
196
+ }
197
+ async write(serverPath, workspaceRoot, overridePath) {
198
+ const configPath = await this.detect(workspaceRoot, overridePath);
199
+ if (configPath === null) {
200
+ return;
201
+ }
202
+ await writeJsonClientConfig(configPath, createServerEntry(serverPath));
203
+ }
204
+ };
205
+ var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
206
+ clientKind = "ClaudeCodeCLI";
207
+ scope;
208
+ constructor(configuredPath, scope = "project") {
209
+ super(configuredPath);
210
+ this.scope = scope;
211
+ }
212
+ // Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
213
+ // or ~/.claude.json for user scope.
214
+ // Detection still checks ~/.claude to confirm Claude Code is installed.
215
+ defaultPath(workspaceRoot) {
216
+ const globalClaudeDir = join2(homedir(), ".claude");
217
+ const projectClaudeDir = join2(workspaceRoot, ".claude");
218
+ if (!existsSync2(globalClaudeDir) && !existsSync2(projectClaudeDir)) {
219
+ return null;
220
+ }
221
+ return this.scope === "user" ? join2(homedir(), ".claude.json") : join2(workspaceRoot, ".mcp.json");
222
+ }
223
+ };
224
+ var CursorWriter = class extends JsonClientConfigWriter {
225
+ clientKind = "Cursor";
226
+ constructor(configuredPath) {
227
+ super(configuredPath);
228
+ }
229
+ defaultPath(workspaceRoot) {
230
+ const cursorDir = join2(workspaceRoot, ".cursor");
231
+ return existsSync2(cursorDir) ? join2(cursorDir, "mcp.json") : null;
232
+ }
233
+ };
234
+
235
+ // src/config/claude-code.ts
236
+ function getClaudeDesktopConfigPath() {
237
+ const os = platform();
238
+ if (os === "darwin") {
239
+ return join3(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
240
+ }
241
+ if (os === "win32") {
242
+ return join3(process.env.APPDATA ?? join3(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
243
+ }
244
+ return join3(homedir2(), ".config", "Claude", "claude_desktop_config.json");
245
+ }
246
+ var ClaudeCodeDesktopWriter = class {
247
+ clientKind = "ClaudeCodeDesktop";
248
+ configuredPath;
249
+ constructor(configuredPath) {
250
+ this.configuredPath = configuredPath;
251
+ }
252
+ async detect(_workspaceRoot, overridePath) {
253
+ const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
254
+ return existsSync3(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
255
+ }
256
+ async write(serverPath, workspaceRoot, overridePath) {
257
+ const configPath = await this.detect(workspaceRoot, overridePath);
258
+ if (configPath === null) {
259
+ return;
260
+ }
261
+ await writeJsonClientConfig(configPath, {
262
+ command: process.execPath,
263
+ args: [serverPath]
264
+ });
265
+ }
266
+ };
267
+
268
+ // src/config/toml.ts
269
+ import { existsSync as existsSync4 } from "fs";
270
+ import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
271
+ import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
272
+ import { homedir as homedir3 } from "os";
273
+ import { atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
274
+ function expandHome2(filePath) {
275
+ if (filePath === "~") {
276
+ return homedir3();
277
+ }
278
+ if (filePath.startsWith("~/")) {
279
+ return join4(homedir3(), filePath.slice(2));
280
+ }
281
+ return filePath;
282
+ }
283
+ function escapeTomlString(value) {
284
+ return JSON.stringify(value);
285
+ }
286
+ function serializeTomlStringArray(values) {
287
+ return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
288
+ }
289
+ function serializeTomlInlineTable(values) {
290
+ const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
291
+ return `{ ${entries.join(", ")} }`;
292
+ }
293
+ function serializeCodexServerBlock(serverName, serverEntry) {
294
+ const lines = [
295
+ `[mcp_servers.${serverName}]`,
296
+ `command = ${escapeTomlString(serverEntry.command)}`,
297
+ `args = ${serializeTomlStringArray(serverEntry.args)}`
298
+ ];
299
+ if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
300
+ lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
301
+ }
302
+ return `${lines.join("\n")}
303
+ `;
304
+ }
305
+ function trimTrailingBlankLines(value) {
306
+ return value.replace(/\s+$/u, "");
307
+ }
308
+ function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
309
+ const block = serializeCodexServerBlock(serverName, serverEntry);
310
+ const normalized = rawConfig.replace(/\r\n/g, "\n");
311
+ const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
312
+ const currentPattern = new RegExp(
313
+ String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
314
+ "g"
315
+ );
316
+ const withoutLegacy = normalized.replace(legacyPattern, "");
317
+ const withoutExisting = withoutLegacy.replace(currentPattern, "");
318
+ const trimmed = trimTrailingBlankLines(withoutExisting);
319
+ if (trimmed.length === 0) {
320
+ return block;
321
+ }
322
+ return `${trimmed}
323
+
324
+ ${block}`;
325
+ }
326
+ async function readTomlConfigText(configPath) {
327
+ try {
328
+ return await readFile2(configPath, "utf8");
329
+ } catch (error) {
330
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
331
+ return "";
332
+ }
333
+ throw error;
334
+ }
335
+ }
336
+ var CodexTOMLConfigWriter = class {
337
+ clientKind = "CodexCLI";
338
+ configuredPath;
339
+ constructor(configuredPath) {
340
+ this.configuredPath = configuredPath;
341
+ }
342
+ async detect(_workspaceRoot, overridePath) {
343
+ const explicitPath = overridePath ?? this.configuredPath;
344
+ if (explicitPath !== void 0) {
345
+ return resolve4(expandHome2(explicitPath));
346
+ }
347
+ const codexDir = join4(homedir3(), ".codex");
348
+ return existsSync4(codexDir) ? resolve4(join4(codexDir, "config.toml")) : null;
349
+ }
350
+ async write(serverPath, workspaceRoot, overridePath) {
351
+ const configPath = await this.detect(workspaceRoot, overridePath);
352
+ if (configPath === null) {
353
+ return;
354
+ }
355
+ const rawConfig = await readTomlConfigText(configPath);
356
+ const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
357
+ await mkdir2(dirname3(configPath), { recursive: true });
358
+ await atomicWriteText2(configPath, nextConfig);
359
+ }
360
+ };
361
+
362
+ // src/config/resolver.ts
363
+ import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
364
+ function hasExplicitPath(clientPaths, key) {
365
+ return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
366
+ }
367
+ function addIfDetected(writers, detected, createWriter, configuredPath) {
368
+ if (configuredPath !== void 0 || detected) {
369
+ writers.push(createWriter(configuredPath));
370
+ }
371
+ }
372
+ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
373
+ const clientPaths = fabricConfig.clientPaths;
374
+ const writers = [];
375
+ const claudeMcpScope = opts.claudeMcpScope ?? "project";
376
+ addIfDetected(
377
+ writers,
378
+ existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude")),
379
+ (configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
380
+ hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
381
+ );
382
+ addIfDetected(
383
+ writers,
384
+ existsSync5(getClaudeDesktopConfigPath()),
385
+ (configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
386
+ hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
387
+ );
388
+ addIfDetected(
389
+ writers,
390
+ existsSync5(join5(workspaceRoot, ".cursor")),
391
+ (configuredPath) => new CursorWriter(configuredPath),
392
+ hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
393
+ );
394
+ addIfDetected(
395
+ writers,
396
+ existsSync5(join5(homedir4(), ".codex")),
397
+ (configuredPath) => new CodexTOMLConfigWriter(configuredPath),
398
+ hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
399
+ );
400
+ return writers;
401
+ }
402
+ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
403
+ const clientPaths = fabricConfig.clientPaths;
404
+ const claudeDetected = existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude"));
405
+ const claudeDesktopDetected = existsSync5(getClaudeDesktopConfigPath());
406
+ const cursorDetected = existsSync5(join5(workspaceRoot, ".cursor"));
407
+ const codexDetected = existsSync5(join5(homedir4(), ".codex"));
408
+ return [
409
+ {
410
+ clientKind: "ClaudeCodeCLI",
411
+ label: "Claude Code CLI",
412
+ detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
413
+ bootstrapTargetPath: ".fabric/bootstrap/README.md",
414
+ configPath: "project .claude/settings.json",
415
+ capabilities: {
416
+ bootstrap: true,
417
+ mcp: true,
418
+ hook: true,
419
+ skill: true
420
+ },
421
+ installedCapabilities: {
422
+ hook: true,
423
+ skill: true
424
+ }
425
+ },
426
+ {
427
+ clientKind: "ClaudeCodeDesktop",
428
+ label: "Claude Code Desktop",
429
+ detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
430
+ bootstrapTargetPath: ".fabric/bootstrap/README.md",
431
+ configPath: "desktop Claude config",
432
+ capabilities: {
433
+ bootstrap: true,
434
+ mcp: true,
435
+ hook: false,
436
+ skill: false
437
+ }
438
+ },
439
+ {
440
+ clientKind: "Cursor",
441
+ label: "Cursor",
442
+ detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
443
+ bootstrapTargetPath: ".fabric/bootstrap/README.md",
444
+ configPath: ".cursor/mcp.json",
445
+ capabilities: {
446
+ bootstrap: true,
447
+ mcp: true,
448
+ hook: false,
449
+ skill: false
450
+ }
451
+ },
452
+ {
453
+ clientKind: "CodexCLI",
454
+ label: "Codex CLI",
455
+ detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
456
+ bootstrapTargetPath: ".fabric/bootstrap/README.md",
457
+ configPath: "~/.codex/config.toml",
458
+ capabilities: {
459
+ bootstrap: true,
460
+ mcp: true,
461
+ hook: true,
462
+ skill: true
463
+ },
464
+ installedCapabilities: {
465
+ hook: existsSync5(join5(workspaceRoot, ".codex", "hooks.json")),
466
+ skill: existsSync5(join5(workspaceRoot, ".agents", "skills", "fabric-init", "SKILL.md"))
467
+ }
468
+ }
469
+ ];
470
+ }
471
+
472
+ // src/commands/bootstrap.ts
473
+ var CLIENT_ALIASES = {
474
+ claude: "claude",
475
+ "claude-code": "claude",
476
+ claudecode: "claude",
477
+ claudecli: "claude",
478
+ claudecodecli: "claude",
479
+ claudedesktop: "claude",
480
+ claudecodedesktop: "claude",
481
+ cursor: "cursor",
482
+ codex: "codex",
483
+ "codex-cli": "codex",
484
+ codexcli: "codex"
485
+ };
486
+ var bootstrapCommand = defineCommand({
487
+ meta: {
488
+ name: "bootstrap",
489
+ description: t("cli.bootstrap.description")
490
+ },
491
+ subCommands: {
492
+ install: defineCommand({
493
+ meta: {
494
+ name: "install",
495
+ description: t("cli.bootstrap.install.description")
496
+ },
497
+ args: {
498
+ clients: {
499
+ type: "string",
500
+ description: t("cli.bootstrap.install.args.clients.description")
501
+ }
502
+ },
503
+ async run({ args }) {
504
+ const workspaceRoot = process.cwd();
505
+ const selectedClients = parseClientFilter(args.clients);
506
+ const result = await installBootstrap(workspaceRoot, {
507
+ clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
508
+ });
509
+ if (result.details.length === 0) {
510
+ process.stderr.write(
511
+ `${t("cli.bootstrap.install.no-targets")}
512
+ `
513
+ );
514
+ return;
515
+ }
516
+ for (const detail of result.details) {
517
+ if (detail.action === "skipped") {
518
+ process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
519
+ `);
520
+ continue;
521
+ }
522
+ if (detail.action === "prepended") {
523
+ process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
524
+ `);
525
+ continue;
526
+ }
527
+ process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
528
+ `);
529
+ }
530
+ }
531
+ })
532
+ }
533
+ });
534
+ async function installBootstrap(target, options = {}) {
535
+ const workspaceRoot = resolve5(target);
536
+ const fabricConfig = readFabricConfig(workspaceRoot);
537
+ const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
538
+ const installed = [];
539
+ const skipped = [];
540
+ const details = [];
541
+ await ensureFabricBootstrapGuide(workspaceRoot, options.force);
542
+ for (const bootstrapTarget of targets) {
543
+ details.push({
544
+ client: bootstrapTarget.client,
545
+ path: resolve5(workspaceRoot, FABRIC_GUIDE_PATH),
546
+ action: "skipped"
547
+ });
548
+ skipped.push(bootstrapTarget.client);
549
+ }
550
+ return { installed, skipped, details };
551
+ }
552
+ function parseClientFilter(value) {
553
+ if (value === void 0 || value.trim().length === 0) {
554
+ return null;
555
+ }
556
+ const clients = /* @__PURE__ */ new Set();
557
+ for (const rawClient of value.split(",")) {
558
+ const alias = rawClient.trim().toLowerCase();
559
+ const client = CLIENT_ALIASES[alias];
560
+ if (client === void 0) {
561
+ throw new Error(t("cli.bootstrap.errors.unknown-client", { client: rawClient }));
562
+ }
563
+ clients.add(client);
564
+ }
565
+ return clients;
566
+ }
567
+ function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
568
+ const targets = [];
569
+ const seenClients = /* @__PURE__ */ new Set();
570
+ const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
571
+ for (const clientKind of clientKinds) {
572
+ const bootstrapClient = mapClientKind(clientKind);
573
+ if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
574
+ continue;
575
+ }
576
+ seenClients.add(bootstrapClient);
577
+ targets.push({ client: clientKind, bootstrapClient });
578
+ }
579
+ return targets;
580
+ }
581
+ function mapClientKind(clientKind) {
582
+ switch (clientKind) {
583
+ case "ClaudeCodeCLI":
584
+ case "ClaudeCodeDesktop":
585
+ return "claude";
586
+ case "Cursor":
587
+ return "cursor";
588
+ case "CodexCLI":
589
+ return "codex";
590
+ default:
591
+ return null;
592
+ }
593
+ }
594
+ function mapBootstrapClientToClientKind(client) {
595
+ switch (client) {
596
+ case "claude":
597
+ return "ClaudeCodeCLI";
598
+ case "cursor":
599
+ return "Cursor";
600
+ case "codex":
601
+ return "CodexCLI";
602
+ }
603
+ }
604
+
605
+ // src/commands/config.ts
606
+ import { existsSync as existsSync7 } from "fs";
607
+ import { readFile as readFile3 } from "fs/promises";
608
+ import { resolve as resolve7 } from "path";
609
+ import { fileURLToPath as fileURLToPath3 } from "url";
610
+ import { defineCommand as defineCommand3 } from "citty";
611
+
612
+ // src/commands/hooks.ts
613
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync } from "fs";
614
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, parse as parse2, resolve as resolve6 } from "path";
615
+ import { fileURLToPath as fileURLToPath2 } from "url";
616
+ import { defineCommand as defineCommand2 } from "citty";
617
+ import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText3 } from "@fenglimg/fabric-shared/node/atomic-write";
618
+ var hooksCommand = defineCommand2({
619
+ meta: {
620
+ name: "hooks",
621
+ description: t("cli.hooks.description")
622
+ },
623
+ subCommands: {
624
+ install: defineCommand2({
625
+ meta: {
626
+ name: "install",
627
+ description: t("cli.hooks.install.description")
628
+ },
629
+ args: {
630
+ target: {
631
+ type: "string",
632
+ description: t("cli.hooks.install.args.target.description"),
633
+ default: process.cwd()
634
+ }
635
+ },
636
+ async run({ args }) {
637
+ const result = await installHooks(args.target);
638
+ if (result.hookAction === "skipped") {
639
+ writeStderr(t("cli.hooks.install.hook-skipped", { path: result.hookPath }));
640
+ } else if (result.hookAction === "appended") {
641
+ writeStderr(t("cli.hooks.install.hook-appended", { path: result.hookPath }));
642
+ } else {
643
+ writeStderr(t("cli.hooks.install.hook-created", { path: result.hookPath }));
644
+ }
645
+ if (result.prepareAction === "left") {
646
+ writeStderr(t("cli.hooks.install.prepare-left", { path: result.packageJsonPath }));
647
+ } else {
648
+ writeStderr(t("cli.hooks.install.prepare-added", { path: result.packageJsonPath }));
649
+ }
650
+ }
651
+ })
652
+ }
653
+ });
654
+ async function installHooks(target, options = {}) {
655
+ const normalizedTarget = normalizeTarget2(target);
656
+ assertExistingDirectory(normalizedTarget);
657
+ const huskyDir = join6(normalizedTarget, ".husky");
658
+ const hookPath = join6(huskyDir, "pre-commit");
659
+ const packageJsonPath = join6(normalizedTarget, "package.json");
660
+ if (!existsSync6(packageJsonPath)) {
661
+ throw new Error(t("cli.hooks.errors.package-json-required", { path: packageJsonPath }));
662
+ }
663
+ mkdirSync2(huskyDir, { recursive: true });
664
+ const templateContent = readFileSync2(findTemplatePath2("templates/husky/pre-commit"), "utf8");
665
+ const hookAction = await installHookFile(hookPath, templateContent, options.force);
666
+ chmodSync(hookPath, 493);
667
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
668
+ const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
669
+ const hadPrepare = typeof scripts.prepare === "string" && scripts.prepare.trim().length > 0;
670
+ let prepareAction = "left";
671
+ if (!hadPrepare) {
672
+ scripts.prepare = "husky install";
673
+ packageJson.scripts = scripts;
674
+ await atomicWriteJson2(packageJsonPath, packageJson);
675
+ prepareAction = "added";
676
+ }
677
+ const installed = [];
678
+ const skipped = [];
679
+ if (hookAction === "skipped") {
680
+ skipped.push(hookPath);
681
+ } else {
682
+ installed.push(hookPath);
683
+ }
684
+ if (prepareAction === "left") {
685
+ skipped.push(packageJsonPath);
686
+ } else {
687
+ installed.push(packageJsonPath);
688
+ }
689
+ return {
690
+ installed,
691
+ skipped,
692
+ hookPath,
693
+ packageJsonPath,
694
+ hookAction,
695
+ prepareAction
696
+ };
697
+ }
698
+ function normalizeTarget2(targetInput) {
699
+ return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
700
+ }
701
+ function assertExistingDirectory(target) {
702
+ if (!existsSync6(target) || !statSync(target).isDirectory()) {
703
+ throw new Error(t("cli.shared.target-invalid", { target }));
704
+ }
705
+ }
706
+ async function installHookFile(hookPath, templateContent, force) {
707
+ if (existsSync6(hookPath)) {
708
+ if (force) {
709
+ await atomicWriteText3(hookPath, templateContent);
710
+ return "overwritten";
711
+ }
712
+ const existing = readFileSync2(hookPath, "utf8");
713
+ if (existing.includes("FAB_BIN=")) {
714
+ return "skipped";
715
+ }
716
+ const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
717
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
718
+ await atomicWriteText3(hookPath, `${existing}${separator}# --- Fabric ---
719
+ ${fabricBlock}`);
720
+ return "appended";
721
+ }
722
+ await atomicWriteText3(hookPath, templateContent);
723
+ return "created";
724
+ }
725
+ function findTemplatePath2(relativePath) {
726
+ const currentModuleDir = dirname4(fileURLToPath2(import.meta.url));
727
+ const candidates = [
728
+ ...templateCandidatesFrom2(process.cwd(), relativePath),
729
+ ...templateCandidatesFrom2(currentModuleDir, relativePath)
730
+ ];
731
+ for (const candidate of candidates) {
732
+ if (existsSync6(candidate)) {
733
+ return candidate;
734
+ }
735
+ }
736
+ throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
737
+ }
738
+ function templateCandidatesFrom2(start, relativePath) {
739
+ const candidates = [];
740
+ let current = resolve6(start);
741
+ while (true) {
742
+ candidates.push(join6(current, ...relativePath.split("/")));
743
+ const parent = dirname4(current);
744
+ if (parent === current || parse2(current).root === current) {
745
+ break;
746
+ }
747
+ current = parent;
748
+ }
749
+ return candidates.reverse();
750
+ }
751
+ function writeStderr(message) {
752
+ process.stderr.write(`${message}
753
+ `);
754
+ }
755
+
756
+ // src/commands/config.ts
757
+ var CLIENT_ALIASES2 = {
758
+ claude: "ClaudeCodeCLI",
759
+ claudecodecli: "ClaudeCodeCLI",
760
+ "claude-code-cli": "ClaudeCodeCLI",
761
+ claudecli: "ClaudeCodeCLI",
762
+ claudecodedesktop: "ClaudeCodeDesktop",
763
+ "claude-code-desktop": "ClaudeCodeDesktop",
764
+ claudedesktop: "ClaudeCodeDesktop",
765
+ cursor: "Cursor",
766
+ codexcli: "CodexCLI",
767
+ "codex-cli": "CodexCLI",
768
+ codex: "CodexCLI"
769
+ };
770
+ function parseClientFilter2(value) {
771
+ if (value === void 0 || value.trim().length === 0) {
772
+ return null;
773
+ }
774
+ const clients = /* @__PURE__ */ new Set();
775
+ for (const rawClient of value.split(",")) {
776
+ const alias = rawClient.trim().toLowerCase();
777
+ const clientKind = CLIENT_ALIASES2[alias];
778
+ if (clientKind === void 0) {
779
+ throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
780
+ }
781
+ clients.add(clientKind);
782
+ }
783
+ return clients;
784
+ }
785
+ async function loadFabricConfig(workspaceRoot) {
786
+ const configPath = resolve7(workspaceRoot, "fabric.config.json");
787
+ if (!existsSync7(configPath)) {
788
+ return {};
789
+ }
790
+ const parsed = JSON.parse(await readFile3(configPath, "utf8"));
791
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
792
+ throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
793
+ }
794
+ return parsed;
795
+ }
796
+ function resolveServerPath(override) {
797
+ if (override) return override;
798
+ if (process.env.FAB_SERVER_PATH) return resolve7(process.env.FAB_SERVER_PATH);
799
+ return fileURLToPath3(import.meta.resolve("@fenglimg/fabric-server"));
800
+ }
801
+ function writeStderr2(message) {
802
+ process.stderr.write(`${message}
803
+ `);
804
+ }
805
+ var configCmd = defineCommand3({
806
+ meta: {
807
+ name: "config",
808
+ description: t("cli.config.description")
809
+ },
810
+ subCommands: {
811
+ hooks: hooksCommand,
812
+ install: defineCommand3({
813
+ meta: {
814
+ name: "install",
815
+ description: t("cli.config.install.description")
816
+ },
817
+ args: {
818
+ clients: {
819
+ type: "string",
820
+ description: t("cli.config.install.args.clients.description")
821
+ },
822
+ "dry-run": {
823
+ type: "boolean",
824
+ description: t("cli.config.install.args.dry-run.description"),
825
+ default: false
826
+ }
827
+ },
828
+ async run({ args }) {
829
+ const selectedClients = parseClientFilter2(args.clients);
830
+ const result = await installMcpClients(process.cwd(), {
831
+ clients: selectedClients === null ? void 0 : Array.from(selectedClients),
832
+ dryRun: args["dry-run"]
833
+ });
834
+ if (result.details.length === 0) {
835
+ writeStderr2(t("cli.config.install.no-configs"));
836
+ return;
837
+ }
838
+ for (const detail of result.details) {
839
+ if (detail.action === "skipped") {
840
+ writeStderr2(t("cli.config.install.no-config-path", { client: detail.client }));
841
+ continue;
842
+ }
843
+ if (detail.action === "dry-run" && detail.path !== null) {
844
+ writeStderr2(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
845
+ continue;
846
+ }
847
+ if (detail.path !== null) {
848
+ writeStderr2(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
849
+ }
850
+ }
851
+ }
852
+ })
853
+ }
854
+ });
855
+ async function installMcpClients(target, options = {}) {
856
+ const workspaceRoot = resolve7(target);
857
+ const fabricConfig = await loadFabricConfig(workspaceRoot);
858
+ const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
859
+ const serverPath = resolveServerPath(options.localServerPath);
860
+ const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
861
+ (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
862
+ );
863
+ const installed = [];
864
+ const skipped = [];
865
+ const details = [];
866
+ for (const writer of writers) {
867
+ const configPath = await writer.detect(workspaceRoot);
868
+ if (configPath === null) {
869
+ skipped.push(writer.clientKind);
870
+ details.push({ client: writer.clientKind, path: null, action: "skipped" });
871
+ continue;
872
+ }
873
+ if (options.dryRun) {
874
+ skipped.push(writer.clientKind);
875
+ details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
876
+ continue;
877
+ }
878
+ await writer.write(serverPath, workspaceRoot);
879
+ installed.push(writer.clientKind);
880
+ details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
881
+ }
882
+ return { installed, skipped, details };
883
+ }
884
+
40
885
  // src/scanner/forensic.ts
41
886
  import { execFileSync } from "child_process";
42
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
887
+ import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
43
888
  import { createRequire } from "module";
44
- import { basename, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
889
+ import { basename, extname, isAbsolute as isAbsolute3, join as join7, posix, relative, resolve as resolve8, sep } from "path";
45
890
  import {
46
891
  forensicReportSchema
47
892
  } from "@fenglimg/fabric-shared";
@@ -127,7 +972,7 @@ var parserInitPromise = null;
127
972
  var languagePromiseByKind = {};
128
973
  var parserBundlePromiseByKind = {};
129
974
  async function buildForensicReport(targetInput) {
130
- const target = normalizeTarget(targetInput);
975
+ const target = normalizeTarget3(targetInput);
131
976
  const framework = detectFramework(target);
132
977
  const topology = buildTopology(target);
133
978
  const entryPoints = collectEntryPoints(target, topology.files);
@@ -163,11 +1008,11 @@ async function buildForensicReport(targetInput) {
163
1008
  }
164
1009
  return validation.data;
165
1010
  }
166
- function normalizeTarget(targetInput) {
167
- return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
1011
+ function normalizeTarget3(targetInput) {
1012
+ return isAbsolute3(targetInput) ? targetInput : resolve8(process.cwd(), targetInput);
168
1013
  }
169
1014
  function buildTopology(root) {
170
- assertExistingDirectory(root);
1015
+ assertExistingDirectory2(root);
171
1016
  const byExt = {};
172
1017
  const keyDirs = /* @__PURE__ */ new Set();
173
1018
  const files = [];
@@ -180,7 +1025,7 @@ function buildTopology(root) {
180
1025
  continue;
181
1026
  }
182
1027
  for (const entry of readdirSync(current, { withFileTypes: true })) {
183
- const absolutePath = join(current, entry.name);
1028
+ const absolutePath = join7(current, entry.name);
184
1029
  const relativePath = toPosixPath(relative(root, absolutePath));
185
1030
  if (relativePath.length === 0) {
186
1031
  continue;
@@ -200,7 +1045,7 @@ function buildTopology(root) {
200
1045
  if (!entry.isFile()) {
201
1046
  continue;
202
1047
  }
203
- const stats = statSync(absolutePath);
1048
+ const stats = statSync2(absolutePath);
204
1049
  const extension = extname(entry.name) || "[none]";
205
1050
  byExt[extension] = (byExt[extension] ?? 0) + 1;
206
1051
  totalFiles += 1;
@@ -218,8 +1063,8 @@ function buildTopology(root) {
218
1063
  files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
219
1064
  };
220
1065
  }
221
- function assertExistingDirectory(target) {
222
- if (!existsSync(target) || !statSync(target).isDirectory()) {
1066
+ function assertExistingDirectory2(target) {
1067
+ if (!existsSync8(target) || !statSync2(target).isDirectory()) {
223
1068
  throw new Error(`Target must be an existing directory: ${target}`);
224
1069
  }
225
1070
  }
@@ -268,7 +1113,7 @@ function getEntryPointReason(relativePath) {
268
1113
  async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
269
1114
  const samples = [];
270
1115
  for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
271
- const absolutePath = join(target, ...entryPoint.path.split("/"));
1116
+ const absolutePath = join7(target, ...entryPoint.path.split("/"));
272
1117
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
273
1118
  const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
274
1119
  frameworkKind,
@@ -288,7 +1133,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
288
1133
  }
289
1134
  function readFirstLines(path, lineLimit) {
290
1135
  try {
291
- const lines = readFileSync(path, "utf8").split(/\r?\n/);
1136
+ const lines = readFileSync3(path, "utf8").split(/\r?\n/);
292
1137
  if (lines.at(-1) === "") {
293
1138
  lines.pop();
294
1139
  }
@@ -305,12 +1150,12 @@ function readFirstLines(path, lineLimit) {
305
1150
  }
306
1151
  }
307
1152
  function readPackageDependencies(target) {
308
- const packageJsonPath = join(target, "package.json");
309
- if (!existsSync(packageJsonPath)) {
1153
+ const packageJsonPath = join7(target, "package.json");
1154
+ if (!existsSync8(packageJsonPath)) {
310
1155
  return /* @__PURE__ */ new Map();
311
1156
  }
312
1157
  try {
313
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1158
+ const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
314
1159
  return new Map([
315
1160
  ...Object.entries(packageJson.dependencies ?? {}),
316
1161
  ...Object.entries(packageJson.devDependencies ?? {}),
@@ -646,16 +1491,16 @@ function scoreFrameworkConfidence(input) {
646
1491
  return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
647
1492
  }
648
1493
  function readReadmeInfo(target) {
649
- const readmePath = join(target, "README.md");
650
- const hasContributing = existsSync(join(target, "CONTRIBUTING.md"));
651
- if (!existsSync(readmePath)) {
1494
+ const readmePath = join7(target, "README.md");
1495
+ const hasContributing = existsSync8(join7(target, "CONTRIBUTING.md"));
1496
+ if (!existsSync8(readmePath)) {
652
1497
  return {
653
1498
  quality: "missing",
654
1499
  line_count: 0,
655
1500
  has_contributing: hasContributing
656
1501
  };
657
1502
  }
658
- const readme = readFileSync(readmePath, "utf8");
1503
+ const readme = readFileSync3(readmePath, "utf8");
659
1504
  const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
660
1505
  return {
661
1506
  quality: wordCount >= 200 ? "ok" : "stub",
@@ -888,7 +1733,7 @@ function buildDomainAssertion(codeSamples) {
888
1733
  namedModules.length >= 2 ? "domain-named-components" : null,
889
1734
  namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
890
1735
  ]),
891
- proposedRule: "Preserve domain-specific module names when mirroring structure into AGENTS.md or .fabric/agents/."
1736
+ proposedRule: "Preserve domain-specific module names when mirroring structure into .fabric/rules/."
892
1737
  });
893
1738
  }
894
1739
  function createAssertion(input) {
@@ -1133,10 +1978,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
1133
1978
  return recommendations;
1134
1979
  }
1135
1980
  function readProjectName(target) {
1136
- const packageJsonPath = join(target, "package.json");
1137
- if (existsSync(packageJsonPath)) {
1981
+ const packageJsonPath = join7(target, "package.json");
1982
+ if (existsSync8(packageJsonPath)) {
1138
1983
  try {
1139
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1984
+ const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
1140
1985
  if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
1141
1986
  return packageJson.name;
1142
1987
  }
@@ -1147,7 +1992,7 @@ function readProjectName(target) {
1147
1992
  return basename(target);
1148
1993
  }
1149
1994
  function getCliVersion() {
1150
- return true ? "1.6.0" : "unknown";
1995
+ return true ? "1.8.0-rc.1" : "unknown";
1151
1996
  }
1152
1997
  function sortRecord(record) {
1153
1998
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1157,7 +2002,7 @@ function toPosixPath(path) {
1157
2002
  }
1158
2003
 
1159
2004
  // src/commands/init.ts
1160
- var CLAUDE_INIT_SKILL_TEMPLATE = "templates/claude-skills/agents-md-init/SKILL.md";
2005
+ var CLAUDE_INIT_SKILL_TEMPLATE = "templates/claude-skills/fabric-init/SKILL.md";
1161
2006
  var CLAUDE_INIT_REMINDER_HOOK_TEMPLATE = "templates/claude-hooks/agents-md-init-reminder.cjs";
1162
2007
  var CLAUDE_INIT_REMINDER_COMMAND = ".claude/hooks/agents-md-init-reminder.cjs";
1163
2008
  var CODEX_INIT_SKILL_TEMPLATE = "templates/codex-skills/fabric-init/SKILL.md";
@@ -1165,10 +2010,10 @@ var CODEX_SESSION_START_HOOK_TEMPLATE = "templates/codex-hooks/fabric-session-st
1165
2010
  var CODEX_STOP_HOOK_TEMPLATE = "templates/codex-hooks/fabric-stop-reminder.cjs";
1166
2011
  var CODEX_SESSION_START_COMMAND = ".codex/hooks/fabric-session-start.cjs";
1167
2012
  var CODEX_STOP_COMMAND = ".codex/hooks/fabric-stop-reminder.cjs";
1168
- var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
2013
+ var LOCAL_FABRIC_SERVER_PATH = join8("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1169
2014
  var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
1170
2015
  var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
1171
- var initCommand = defineCommand({
2016
+ var initCommand = defineCommand4({
1172
2017
  meta: {
1173
2018
  name: "init",
1174
2019
  description: t("cli.init.description")
@@ -1227,6 +2072,10 @@ var initCommand = defineCommand({
1227
2072
  type: "string",
1228
2073
  default: "global",
1229
2074
  description: t("cli.init.mcp.install.prompt")
2075
+ },
2076
+ scope: {
2077
+ type: "string",
2078
+ description: t("cli.init.mcp.scope.description")
1230
2079
  }
1231
2080
  },
1232
2081
  async run({ args }) {
@@ -1238,37 +2087,42 @@ async function runInitCommand(args) {
1238
2087
  const logger = createDebugLogger(args.debug);
1239
2088
  const resolution = resolveDevMode(args.target, process.cwd());
1240
2089
  const intent = resolveInitCliIntent(args, resolution.target);
2090
+ if (args.reapply === true) {
2091
+ checkLockOrThrow(intent.target, { force: args.force });
2092
+ }
1241
2093
  logger(`init target source: ${resolution.source}`);
1242
2094
  for (const step of resolution.chain) {
1243
2095
  logger(step);
1244
2096
  }
1245
2097
  if (intent.options.planOnly) {
1246
- writeStderr(t("cli.init.compat.plan"));
2098
+ writeStderr3(t("cli.init.compat.plan"));
1247
2099
  }
1248
2100
  if (args.interactive === false) {
1249
- writeStderr(t("cli.init.compat.interactive"));
2101
+ writeStderr3(t("cli.init.compat.interactive"));
1250
2102
  }
1251
2103
  if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
1252
- writeStderr(t("cli.init.compat.legacy-stage-flags"));
2104
+ writeStderr3(t("cli.init.compat.legacy-stage-flags"));
1253
2105
  }
1254
2106
  const supports = detectClientSupports(intent.target);
1255
2107
  const basePlan = await buildInitExecutionPlan({
1256
2108
  target: intent.target,
1257
2109
  options: intent.options,
1258
2110
  mcpInstallMode: intent.mcpInstallMode,
2111
+ claudeMcpScope: intent.claudeMcpScope,
1259
2112
  interactive: intent.interactiveSummary && !intent.wizardEnabled,
1260
2113
  supports
1261
2114
  });
1262
2115
  const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
1263
2116
  if (plan === null) {
1264
- writeStderr(t("cli.init.wizard.cancelled"));
2117
+ writeStderr3(t("cli.init.wizard.cancelled"));
1265
2118
  throw new Error(t("cli.init.wizard.cancelled"));
1266
2119
  }
1267
2120
  return executeInitExecutionPlan(plan);
1268
2121
  }
1269
2122
  function resolveInitCliIntent(args, targetInput) {
1270
- const target = normalizeTarget2(targetInput);
2123
+ const target = normalizeTarget4(targetInput);
1271
2124
  const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
2125
+ const claudeMcpScope = resolveClaudeMcpScope(args.scope);
1272
2126
  const terminalInteractive = isInteractiveInit();
1273
2127
  const planOnly = args.plan === true;
1274
2128
  const reapply = args.reapply === true;
@@ -1284,21 +2138,34 @@ function resolveInitCliIntent(args, targetInput) {
1284
2138
  target,
1285
2139
  options,
1286
2140
  mcpInstallMode,
2141
+ claudeMcpScope,
1287
2142
  interactiveSummary: args.interactive !== false && terminalInteractive,
1288
2143
  wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
1289
2144
  };
1290
2145
  }
2146
+ function resolveClaudeMcpScope(raw) {
2147
+ if (raw === void 0 || raw === "project") {
2148
+ return "project";
2149
+ }
2150
+ if (raw === "user") {
2151
+ return "user";
2152
+ }
2153
+ writeStderr3(t("cli.init.mcp.scope.invalid", { value: raw }));
2154
+ return "project";
2155
+ }
1291
2156
  async function buildInitExecutionPlan(input) {
1292
2157
  const options = input.options ?? {};
1293
2158
  const scaffold = await buildInitFabricPlan(input.target, options);
1294
2159
  const supports = input.supports ?? detectClientSupports(input.target);
1295
2160
  const mcpInstallMode = input.mcpInstallMode ?? "global";
2161
+ const claudeMcpScope = input.claudeMcpScope ?? "project";
1296
2162
  const stages = [
1297
2163
  { name: "bootstrap", skipped: Boolean(options.skipBootstrap) },
1298
2164
  {
1299
2165
  name: "mcp",
1300
2166
  skipped: Boolean(options.skipMcp),
1301
2167
  installMode: mcpInstallMode,
2168
+ claudeMcpScope,
1302
2169
  localServerPath: mcpInstallMode === "local" ? LOCAL_FABRIC_SERVER_PATH : void 0,
1303
2170
  packageManager: mcpInstallMode === "local" ? detectPackageManager(input.target) : void 0
1304
2171
  },
@@ -1308,6 +2175,7 @@ async function buildInitExecutionPlan(input) {
1308
2175
  target: input.target,
1309
2176
  options,
1310
2177
  mcpInstallMode,
2178
+ claudeMcpScope,
1311
2179
  interactive: input.interactive ?? false,
1312
2180
  supports,
1313
2181
  scaffold,
@@ -1322,10 +2190,10 @@ async function buildInitExecutionPlan(input) {
1322
2190
  }
1323
2191
  async function executeInitExecutionPlan(plan) {
1324
2192
  if (plan.options.force) {
1325
- writeStderr(t("cli.init.force.warning", { path: plan.target }));
2193
+ writeStderr3(t("cli.init.force.warning", { path: plan.target }));
1326
2194
  }
1327
2195
  if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
1328
- writeStderr(formatInitModeBanner(plan.options));
2196
+ writeStderr3(formatInitModeBanner(plan.options));
1329
2197
  }
1330
2198
  if (plan.interactive) {
1331
2199
  printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
@@ -1347,7 +2215,7 @@ async function executeInitExecutionPlan(plan) {
1347
2215
  case "preflight":
1348
2216
  break;
1349
2217
  case "scaffold":
1350
- created = executeInitFabricPlan(plan.scaffold);
2218
+ created = await executeInitFabricPlan(plan.scaffold);
1351
2219
  printInitScaffoldResult(created);
1352
2220
  break;
1353
2221
  case "bootstrap":
@@ -1371,28 +2239,28 @@ async function executeInitExecutionPlan(plan) {
1371
2239
  };
1372
2240
  }
1373
2241
  async function buildInitFabricPlan(target, options) {
1374
- assertExistingDirectory2(target);
1375
- const fabricDir = join2(target, ".fabric");
1376
- const bootstrapPath = join2(fabricDir, "bootstrap", "README.md");
1377
- const forensicPath = join2(fabricDir, "forensic.json");
1378
- const taxonomyPath = join2(fabricDir, "INITIAL_TAXONOMY.md");
1379
- const claudeSkillPath = join2(target, ".claude", "skills", "agents-md-init", "SKILL.md");
1380
- const codexSkillPath = join2(target, ".agents", "skills", "fabric-init", "SKILL.md");
1381
- const codexSessionStartHookPath = join2(target, ".codex", "hooks", "fabric-session-start.cjs");
1382
- const codexStopHookPath = join2(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
1383
- const codexHooksConfigPath = join2(target, ".codex", "hooks.json");
1384
- const claudeHookPath = join2(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
1385
- const claudeSettingsPath = join2(target, ".claude", "settings.json");
1386
- const metaPath = join2(fabricDir, "agents.meta.json");
1387
- const humanLockPath = join2(fabricDir, "human-lock.json");
2242
+ assertExistingDirectory3(target);
2243
+ const fabricDir = join8(target, ".fabric");
2244
+ const bootstrapPath = join8(fabricDir, "bootstrap", "README.md");
2245
+ const forensicPath = join8(fabricDir, "forensic.json");
2246
+ const taxonomyPath = join8(fabricDir, "INITIAL_TAXONOMY.md");
2247
+ const rulesDir = join8(fabricDir, "rules");
2248
+ const eventsPath = join8(fabricDir, "events.jsonl");
2249
+ const claudeSkillPath = join8(target, ".claude", "skills", "fabric-init", "SKILL.md");
2250
+ const codexSkillPath = join8(target, ".agents", "skills", "fabric-init", "SKILL.md");
2251
+ const codexSessionStartHookPath = join8(target, ".codex", "hooks", "fabric-session-start.cjs");
2252
+ const codexStopHookPath = join8(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
2253
+ const codexHooksConfigPath = join8(target, ".codex", "hooks.json");
2254
+ const claudeHookPath = join8(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
2255
+ const claudeSettingsPath = join8(target, ".claude", "settings.json");
2256
+ const metaPath = join8(fabricDir, "agents.meta.json");
1388
2257
  const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
1389
2258
  const bootstrapAction = planFreshPath(bootstrapPath, options);
1390
2259
  const metaAction = planFreshPath(metaPath, options);
1391
2260
  const taxonomyAction = planFreshPath(taxonomyPath, options);
1392
- const humanLockAction = planFreshPath(humanLockPath, options);
2261
+ const eventsAction = planFreshPath(eventsPath, options);
1393
2262
  const forensicAction = planFreshPath(forensicPath, options);
1394
2263
  const forensicReport = await buildForensicReport(target);
1395
- const humanLockTemplate = readFileSync2(findTemplatePath("templates/fabric/human-lock.json"), "utf8");
1396
2264
  const bootstrapContent = await buildFabricBootstrapGuide(target);
1397
2265
  const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
1398
2266
  const bootstrapHash = sha256(bootstrapContent);
@@ -1411,62 +2279,79 @@ async function buildInitFabricPlan(target, options) {
1411
2279
  taxonomyPath,
1412
2280
  taxonomyAction,
1413
2281
  taxonomyContent,
1414
- humanLockPath,
1415
- humanLockAction,
1416
- humanLockContent: humanLockTemplate.endsWith("\n") ? humanLockTemplate : `${humanLockTemplate}
1417
- `,
2282
+ rulesDir,
2283
+ eventsPath,
2284
+ eventsAction,
1418
2285
  forensicPath,
1419
2286
  forensicAction,
1420
2287
  forensicReport,
1421
- claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath(CLAUDE_INIT_SKILL_TEMPLATE), options),
1422
- codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath(CODEX_INIT_SKILL_TEMPLATE), options),
2288
+ claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath3(CLAUDE_INIT_SKILL_TEMPLATE), options),
2289
+ codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath3(CODEX_INIT_SKILL_TEMPLATE), options),
1423
2290
  codexSessionStartHook: buildOptionalTemplateWritePlan(
1424
2291
  codexSessionStartHookPath,
1425
- findTemplatePath(CODEX_SESSION_START_HOOK_TEMPLATE),
2292
+ findTemplatePath3(CODEX_SESSION_START_HOOK_TEMPLATE),
1426
2293
  options,
1427
2294
  true
1428
2295
  ),
1429
2296
  codexStopHook: buildOptionalTemplateWritePlan(
1430
2297
  codexStopHookPath,
1431
- findTemplatePath(CODEX_STOP_HOOK_TEMPLATE),
2298
+ findTemplatePath3(CODEX_STOP_HOOK_TEMPLATE),
1432
2299
  options,
1433
2300
  true
1434
2301
  ),
1435
2302
  codexHooksConfig: buildCodexHooksConfigPlan(codexHooksConfigPath, options),
1436
2303
  claudeHook: buildOptionalTemplateWritePlan(
1437
2304
  claudeHookPath,
1438
- findTemplatePath(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
2305
+ findTemplatePath3(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
1439
2306
  options,
1440
2307
  true
1441
2308
  ),
1442
2309
  claudeSettings: buildClaudeSettingsWritePlan(claudeSettingsPath, options)
1443
2310
  };
1444
2311
  }
1445
- function executeInitFabricPlan(plan) {
2312
+ async function executeInitFabricPlan(plan) {
2313
+ const isReapply = plan.options?.reapply === true;
2314
+ const existingRules = isReapply && existsSync9(plan.rulesDir) ? readdirSync2(plan.rulesDir).filter((f) => f.endsWith(".md")) : [];
2315
+ const preserveMeta = isReapply && existingRules.length > 0;
1446
2316
  if (plan.replaceFabricDir) {
1447
2317
  rmSync(plan.fabricDir, { force: true });
1448
2318
  }
1449
- mkdirSync(plan.fabricDir, { recursive: true });
1450
- mkdirSync(dirname(plan.bootstrapPath), { recursive: true });
2319
+ mkdirSync3(plan.fabricDir, { recursive: true });
2320
+ mkdirSync3(dirname5(plan.bootstrapPath), { recursive: true });
1451
2321
  preparePlannedPath(plan.bootstrapPath, plan.bootstrapAction);
1452
- writeFileSync(plan.bootstrapPath, plan.bootstrapContent, "utf8");
1453
- preparePlannedPath(plan.metaPath, plan.metaAction);
1454
- writeFileSync(plan.metaPath, `${JSON.stringify(plan.meta, null, 2)}
1455
- `, "utf8");
2322
+ await atomicWriteText4(plan.bootstrapPath, plan.bootstrapContent);
2323
+ if (!preserveMeta) {
2324
+ preparePlannedPath(plan.metaPath, plan.metaAction);
2325
+ await atomicWriteJson3(plan.metaPath, plan.meta);
2326
+ }
1456
2327
  preparePlannedPath(plan.taxonomyPath, plan.taxonomyAction);
1457
- writeFileSync(plan.taxonomyPath, ensureTrailingNewline(plan.taxonomyContent), "utf8");
1458
- preparePlannedPath(plan.humanLockPath, plan.humanLockAction);
1459
- writeFileSync(plan.humanLockPath, plan.humanLockContent, "utf8");
2328
+ await atomicWriteText4(plan.taxonomyPath, ensureTrailingNewline2(plan.taxonomyContent));
2329
+ mkdirSync3(plan.rulesDir, { recursive: true });
2330
+ if (isReapply) {
2331
+ if (!existsSync9(plan.eventsPath)) {
2332
+ mkdirSync3(dirname5(plan.eventsPath), { recursive: true });
2333
+ writeFileSync(plan.eventsPath, "", "utf8");
2334
+ }
2335
+ } else {
2336
+ preparePlannedPath(plan.eventsPath, plan.eventsAction);
2337
+ writeFileSync(plan.eventsPath, "", "utf8");
2338
+ }
1460
2339
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
1461
- writeFileSync(plan.forensicPath, `${JSON.stringify(plan.forensicReport, null, 2)}
1462
- `, "utf8");
2340
+ await atomicWriteJson3(plan.forensicPath, plan.forensicReport);
1463
2341
  applyOptionalTemplateWritePlan(plan.claudeSkill);
1464
2342
  applyOptionalTemplateWritePlan(plan.codexSkill);
1465
2343
  applyOptionalTemplateWritePlan(plan.codexSessionStartHook);
1466
2344
  applyOptionalTemplateWritePlan(plan.codexStopHook);
1467
- applyJsonWritePlan(plan.codexHooksConfig);
2345
+ await applyJsonWritePlan(plan.codexHooksConfig);
1468
2346
  applyOptionalTemplateWritePlan(plan.claudeHook);
1469
- applyClaudeSettingsWritePlan(plan.claudeSettings);
2347
+ await applyClaudeSettingsWritePlan(plan.claudeSettings);
2348
+ if (isReapply) {
2349
+ appendReapplyLedgerEvent(plan.eventsPath, {
2350
+ preserved_ledger: true,
2351
+ preserved_meta: preserveMeta,
2352
+ rules_count: existingRules.length
2353
+ });
2354
+ }
1470
2355
  return {
1471
2356
  bootstrapPath: plan.bootstrapPath,
1472
2357
  bootstrapAction: plan.bootstrapAction,
@@ -1474,8 +2359,8 @@ function executeInitFabricPlan(plan) {
1474
2359
  metaAction: plan.metaAction,
1475
2360
  taxonomyPath: plan.taxonomyPath,
1476
2361
  taxonomyAction: plan.taxonomyAction,
1477
- humanLockPath: plan.humanLockPath,
1478
- humanLockAction: plan.humanLockAction,
2362
+ eventsPath: plan.eventsPath,
2363
+ eventsAction: plan.eventsAction,
1479
2364
  forensicPath: plan.forensicPath,
1480
2365
  forensicAction: plan.forensicAction,
1481
2366
  claudeSkillPath: plan.claudeSkill.path,
@@ -1495,7 +2380,7 @@ function executeInitFabricPlan(plan) {
1495
2380
  };
1496
2381
  }
1497
2382
  async function initFabric(target, options) {
1498
- return executeInitFabricPlan(await buildInitFabricPlan(target, options));
2383
+ return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
1499
2384
  }
1500
2385
  function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
1501
2386
  return terminalInteractive && args.interactive !== false && args.yes !== true;
@@ -1506,6 +2391,7 @@ async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter)
1506
2391
  options: basePlan.options,
1507
2392
  supports: basePlan.supports,
1508
2393
  mcpInstallMode: basePlan.mcpInstallMode,
2394
+ claudeMcpScope: basePlan.claudeMcpScope,
1509
2395
  lockedStages: collectLockedWizardStages(args)
1510
2396
  });
1511
2397
  if (selection === null) {
@@ -1520,6 +2406,7 @@ async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter)
1520
2406
  skipHooks: !selection.hooks
1521
2407
  },
1522
2408
  mcpInstallMode: selection.mcp ? selection.mcpInstallMode : basePlan.mcpInstallMode,
2409
+ claudeMcpScope: selection.claudeMcpScope,
1523
2410
  interactive: false,
1524
2411
  supports: basePlan.supports
1525
2412
  });
@@ -1537,19 +2424,19 @@ function printInitScaffoldResult(created) {
1537
2424
  console.log(formatInitPathAction(created.bootstrapPath, created.bootstrapAction));
1538
2425
  console.log(formatInitPathAction(created.metaPath, created.metaAction));
1539
2426
  console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
1540
- console.log(formatInitPathAction(created.humanLockPath, created.humanLockAction));
2427
+ console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
1541
2428
  console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
1542
- writeStderr(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
1543
- writeStderr(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
1544
- writeStderr(
2429
+ writeStderr3(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
2430
+ writeStderr3(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
2431
+ writeStderr3(
1545
2432
  formatOptionalInitPathAction(created.codexSessionStartHookPath, created.codexSessionStartHookAction)
1546
2433
  );
1547
- writeStderr(
2434
+ writeStderr3(
1548
2435
  formatOptionalInitPathAction(created.codexStopHookPath, created.codexStopHookAction)
1549
2436
  );
1550
- writeStderr(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
1551
- writeStderr(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
1552
- writeStderr(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
2437
+ writeStderr3(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
2438
+ writeStderr3(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
2439
+ writeStderr3(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
1553
2440
  }
1554
2441
  function printInitPostSetup(plan, stageResults, finalSupports) {
1555
2442
  if (shouldPrintHooksNextStep(plan.options, stageResults)) {
@@ -1589,8 +2476,8 @@ function buildPlanOnlyScaffoldResult(plan) {
1589
2476
  metaAction: plan.metaAction,
1590
2477
  taxonomyPath: plan.taxonomyPath,
1591
2478
  taxonomyAction: plan.taxonomyAction,
1592
- humanLockPath: plan.humanLockPath,
1593
- humanLockAction: plan.humanLockAction,
2479
+ eventsPath: plan.eventsPath,
2480
+ eventsAction: plan.eventsAction,
1594
2481
  forensicPath: plan.forensicPath,
1595
2482
  forensicAction: plan.forensicAction,
1596
2483
  claudeSkillPath: plan.claudeSkill.path,
@@ -1634,16 +2521,17 @@ async function executeInitStagePlan(plan, stageName) {
1634
2521
  case "mcp": {
1635
2522
  if (stage.installMode === "local") {
1636
2523
  const manager = stage.packageManager ?? detectPackageManager(plan.target);
1637
- writeStderr(t("cli.init.mcp.install.local"));
1638
- writeStderr(t("cli.init.mcp.local.installing", { manager }));
2524
+ writeStderr3(t("cli.init.mcp.install.local"));
2525
+ writeStderr3(t("cli.init.mcp.local.installing", { manager }));
1639
2526
  installLocalFabricServer(plan.target, manager);
1640
- writeStderr(t("cli.init.mcp.local.installed"));
2527
+ writeStderr3(t("cli.init.mcp.local.installed"));
1641
2528
  } else {
1642
- writeStderr(t("cli.init.mcp.install.global"));
2529
+ writeStderr3(t("cli.init.mcp.install.global"));
1643
2530
  }
1644
2531
  const result = await installMcpClients(plan.target, {
1645
2532
  force: plan.options.force,
1646
- localServerPath: stage.localServerPath
2533
+ localServerPath: stage.localServerPath,
2534
+ claudeMcpScope: stage.claudeMcpScope
1647
2535
  });
1648
2536
  if (result.details.length === 0) {
1649
2537
  console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
@@ -1661,15 +2549,15 @@ async function executeInitStagePlan(plan, stageName) {
1661
2549
  return exhaustiveInitStagePlan(stage);
1662
2550
  }
1663
2551
  } catch (error) {
1664
- writeStderr(formatInitStageFailure(stageName, error));
2552
+ writeStderr3(formatInitStageFailure(stageName, error));
1665
2553
  return { name: stageName, disposition: "failed" };
1666
2554
  }
1667
2555
  }
1668
2556
  function shouldReplaceWritableDirectory(path, options) {
1669
- if (!existsSync2(path)) {
2557
+ if (!existsSync9(path)) {
1670
2558
  return false;
1671
2559
  }
1672
- if (statSync2(path).isDirectory()) {
2560
+ if (statSync3(path).isDirectory()) {
1673
2561
  return false;
1674
2562
  }
1675
2563
  if (!options?.force) {
@@ -1678,7 +2566,7 @@ function shouldReplaceWritableDirectory(path, options) {
1678
2566
  return true;
1679
2567
  }
1680
2568
  function planFreshPath(path, options) {
1681
- if (!existsSync2(path)) {
2569
+ if (!existsSync9(path)) {
1682
2570
  return "created";
1683
2571
  }
1684
2572
  if (!options?.force) {
@@ -1687,13 +2575,13 @@ function planFreshPath(path, options) {
1687
2575
  return "overwritten";
1688
2576
  }
1689
2577
  function preparePlannedPath(path, action) {
1690
- mkdirSync(dirname(path), { recursive: true });
1691
- if (action === "overwritten" && existsSync2(path)) {
2578
+ mkdirSync3(dirname5(path), { recursive: true });
2579
+ if (action === "overwritten" && existsSync9(path)) {
1692
2580
  rmSync(path, { recursive: true, force: true });
1693
2581
  }
1694
2582
  }
1695
2583
  function buildOptionalTemplateWritePlan(path, templatePath, options, executable = false) {
1696
- const existed = existsSync2(path);
2584
+ const existed = existsSync9(path);
1697
2585
  if (existed && !options?.force) {
1698
2586
  return { path, action: "skipped", templatePath, executable };
1699
2587
  }
@@ -1708,10 +2596,10 @@ function applyOptionalTemplateWritePlan(plan) {
1708
2596
  if (plan.action === "skipped") {
1709
2597
  return;
1710
2598
  }
1711
- mkdirSync(dirname(plan.path), { recursive: true });
2599
+ mkdirSync3(dirname5(plan.path), { recursive: true });
1712
2600
  copyFileSync(plan.templatePath, plan.path);
1713
2601
  if (plan.executable) {
1714
- chmodSync(plan.path, 493);
2602
+ chmodSync2(plan.path, 493);
1715
2603
  }
1716
2604
  }
1717
2605
  function buildCodexHooksConfigValue() {
@@ -1733,48 +2621,48 @@ function buildCodexHooksConfigValue() {
1733
2621
  };
1734
2622
  }
1735
2623
  function buildCodexHooksConfigPlan(configPath, options) {
1736
- const action = !existsSync2(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
2624
+ const action = !existsSync9(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
1737
2625
  return {
1738
2626
  path: configPath,
1739
2627
  action,
1740
2628
  value: buildCodexHooksConfigValue()
1741
2629
  };
1742
2630
  }
1743
- function applyJsonWritePlan(plan) {
2631
+ async function applyJsonWritePlan(plan) {
1744
2632
  if (plan.action === "skipped") {
1745
2633
  return;
1746
2634
  }
1747
- mkdirSync(dirname(plan.path), { recursive: true });
1748
- writeJsonAtomically(plan.path, plan.value);
2635
+ mkdirSync3(dirname5(plan.path), { recursive: true });
2636
+ await atomicWriteJson3(plan.path, plan.value);
1749
2637
  }
1750
2638
  function buildClaudeSettingsWritePlan(settingsPath, options) {
1751
2639
  let settings;
1752
2640
  let action = "updated";
1753
- if (!existsSync2(settingsPath)) {
2641
+ if (!existsSync9(settingsPath)) {
1754
2642
  settings = {};
1755
2643
  action = "created";
1756
2644
  } else {
1757
2645
  try {
1758
- const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
2646
+ const parsed = JSON.parse(readFileSync4(settingsPath, "utf8"));
1759
2647
  if (!isRecord(parsed)) {
1760
- writeStderr(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
2648
+ writeStderr3(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
1761
2649
  return { path: settingsPath, action: "skipped-invalid", value: null };
1762
2650
  }
1763
2651
  settings = parsed;
1764
2652
  } catch (error) {
1765
2653
  const reason = error instanceof Error ? error.message : "unknown parse error";
1766
- writeStderr(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
2654
+ writeStderr3(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
1767
2655
  return { path: settingsPath, action: "skipped-invalid", value: null };
1768
2656
  }
1769
2657
  }
1770
2658
  if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
1771
- writeStderr(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
2659
+ writeStderr3(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
1772
2660
  return { path: settingsPath, action: "skipped-invalid", value: null };
1773
2661
  }
1774
2662
  const hooks = settings.hooks ?? {};
1775
2663
  const stopHooksValue = hooks.Stop;
1776
2664
  if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
1777
- writeStderr(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
2665
+ writeStderr3(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
1778
2666
  return { path: settingsPath, action: "skipped-invalid", value: null };
1779
2667
  }
1780
2668
  const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
@@ -1805,12 +2693,12 @@ function buildClaudeSettingsWritePlan(settingsPath, options) {
1805
2693
  value: nextSettings
1806
2694
  };
1807
2695
  }
1808
- function applyClaudeSettingsWritePlan(plan) {
2696
+ async function applyClaudeSettingsWritePlan(plan) {
1809
2697
  if (plan.value === null) {
1810
2698
  return;
1811
2699
  }
1812
- mkdirSync(dirname(plan.path), { recursive: true });
1813
- writeJsonAtomically(plan.path, plan.value);
2700
+ mkdirSync3(dirname5(plan.path), { recursive: true });
2701
+ await atomicWriteJson3(plan.path, plan.value);
1814
2702
  }
1815
2703
  function createDefaultInitWizardAdapter() {
1816
2704
  return {
@@ -1858,6 +2746,14 @@ function createDefaultInitWizardAdapter() {
1858
2746
  { value: "local", label: "local", hint: t("cli.init.mcp.install.local") }
1859
2747
  ]
1860
2748
  }) : context.mcpInstallMode,
2749
+ claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
2750
+ message: t("cli.init.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
2751
+ initialValue: context.claudeMcpScope,
2752
+ options: [
2753
+ { value: "project", label: "project", hint: t("cli.init.mcp.scope.project") },
2754
+ { value: "user", label: "user", hint: t("cli.init.mcp.scope.user") }
2755
+ ]
2756
+ }) : context.claudeMcpScope,
1861
2757
  hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
1862
2758
  message: t("cli.init.wizard.stage.hooks", {
1863
2759
  defaultValue: formatPromptDefault(!context.options.skipHooks)
@@ -1924,6 +2820,17 @@ async function selectMcpInstallModeInGroup(options) {
1924
2820
  }
1925
2821
  return result;
1926
2822
  }
2823
+ async function selectClaudeMcpScopeInGroup(options) {
2824
+ const result = await select({
2825
+ message: options.message,
2826
+ initialValue: options.initialValue,
2827
+ options: options.options
2828
+ });
2829
+ if (isCancel(result)) {
2830
+ throw INIT_WIZARD_GROUP_CANCELLED;
2831
+ }
2832
+ return result;
2833
+ }
1927
2834
  function collectLockedWizardStages(args) {
1928
2835
  const lockedStages = [];
1929
2836
  if (args.bootstrap === false) {
@@ -1964,23 +2871,23 @@ function formatInitModeBadge(options) {
1964
2871
  }
1965
2872
  return t("cli.init.mode.badge.default");
1966
2873
  }
1967
- function normalizeTarget2(targetInput) {
1968
- return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
2874
+ function normalizeTarget4(targetInput) {
2875
+ return isAbsolute4(targetInput) ? targetInput : resolve9(process.cwd(), targetInput);
1969
2876
  }
1970
- function assertExistingDirectory2(target) {
1971
- if (!existsSync2(target) || !statSync2(target).isDirectory()) {
2877
+ function assertExistingDirectory3(target) {
2878
+ if (!existsSync9(target) || !statSync3(target).isDirectory()) {
1972
2879
  throw new Error(`Target must be an existing directory: ${target}`);
1973
2880
  }
1974
2881
  }
1975
2882
  function detectPackageManager(cwd) {
1976
- const workspaceRoot = resolve2(cwd);
1977
- if (existsSync2(join2(workspaceRoot, "pnpm-lock.yaml"))) {
2883
+ const workspaceRoot = resolve9(cwd);
2884
+ if (existsSync9(join8(workspaceRoot, "pnpm-lock.yaml"))) {
1978
2885
  return "pnpm";
1979
2886
  }
1980
- if (existsSync2(join2(workspaceRoot, "yarn.lock"))) {
2887
+ if (existsSync9(join8(workspaceRoot, "yarn.lock"))) {
1981
2888
  return "yarn";
1982
2889
  }
1983
- if (existsSync2(join2(workspaceRoot, "package-lock.json"))) {
2890
+ if (existsSync9(join8(workspaceRoot, "package-lock.json"))) {
1984
2891
  return "npm";
1985
2892
  }
1986
2893
  return "npm";
@@ -1989,7 +2896,7 @@ function resolveMcpInstallMode(rawMode) {
1989
2896
  if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
1990
2897
  return rawMode ?? "global";
1991
2898
  }
1992
- writeStderr(t("cli.init.mcp.install.invalid", { value: rawMode }));
2899
+ writeStderr3(t("cli.init.mcp.install.invalid", { value: rawMode }));
1993
2900
  return "global";
1994
2901
  }
1995
2902
  function installLocalFabricServer(target, manager) {
@@ -2016,6 +2923,21 @@ function createInitialMeta(agentsHash) {
2016
2923
  }
2017
2924
  };
2018
2925
  }
2926
+ function appendReapplyLedgerEvent(eventsPath, payload) {
2927
+ const event = {
2928
+ kind: "fabric-event",
2929
+ id: `event:${randomUUID()}`,
2930
+ ts: Date.now(),
2931
+ schema_version: 1,
2932
+ event_type: "reapply_completed",
2933
+ preserved_ledger: payload.preserved_ledger,
2934
+ preserved_meta: payload.preserved_meta,
2935
+ rules_count: payload.rules_count
2936
+ };
2937
+ const line = `${JSON.stringify(event)}
2938
+ `;
2939
+ appendFileSync(eventsPath, line, "utf8");
2940
+ }
2019
2941
  function buildInitialTaxonomyMarkdown(forensicReport) {
2020
2942
  const frameworkInfo = forensicReport.framework;
2021
2943
  const framework = [frameworkInfo?.kind ?? "unknown", frameworkInfo?.subkind ?? ""].filter((value) => value.trim() !== "").join(" / ") || "unknown";
@@ -2066,30 +2988,30 @@ function sanitizeTaxonomyLabel(value) {
2066
2988
  const sanitized = value.replaceAll("\\", "/").split("/").filter(Boolean).join("-").replace(/[^A-Za-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "");
2067
2989
  return sanitized === "" ? "General" : sanitized;
2068
2990
  }
2069
- function ensureTrailingNewline(value) {
2991
+ function ensureTrailingNewline2(value) {
2070
2992
  return value.endsWith("\n") ? value : `${value}
2071
2993
  `;
2072
2994
  }
2073
- function findTemplatePath(relativePath) {
2074
- const currentModuleDir = dirname(fileURLToPath(import.meta.url));
2995
+ function findTemplatePath3(relativePath) {
2996
+ const currentModuleDir = dirname5(fileURLToPath4(import.meta.url));
2075
2997
  const candidates = [
2076
- ...templateCandidatesFrom(process.cwd(), relativePath),
2077
- ...templateCandidatesFrom(currentModuleDir, relativePath)
2998
+ ...templateCandidatesFrom3(process.cwd(), relativePath),
2999
+ ...templateCandidatesFrom3(currentModuleDir, relativePath)
2078
3000
  ];
2079
3001
  for (const candidate of candidates) {
2080
- if (existsSync2(candidate)) {
3002
+ if (existsSync9(candidate)) {
2081
3003
  return candidate;
2082
3004
  }
2083
3005
  }
2084
3006
  throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
2085
3007
  }
2086
- function templateCandidatesFrom(start, relativePath) {
3008
+ function templateCandidatesFrom3(start, relativePath) {
2087
3009
  const candidates = [];
2088
- let current = resolve2(start);
3010
+ let current = resolve9(start);
2089
3011
  while (true) {
2090
- candidates.push(join2(current, ...relativePath.split("/")));
2091
- const parent = dirname(current);
2092
- if (parent === current || parse(current).root === current) {
3012
+ candidates.push(join8(current, ...relativePath.split("/")));
3013
+ const parent = dirname5(current);
3014
+ if (parent === current || parse3(current).root === current) {
2093
3015
  break;
2094
3016
  }
2095
3017
  current = parent;
@@ -2110,12 +3032,6 @@ function isClaudeInitReminderStopEntry(entry) {
2110
3032
  (hook) => isRecord(hook) && hook.type === "command" && typeof hook.command === "string" && hook.command.includes("agents-md-init-reminder.cjs")
2111
3033
  );
2112
3034
  }
2113
- function writeJsonAtomically(path, value) {
2114
- const tempPath = `${path}.${process.pid}.tmp`;
2115
- writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}
2116
- `, "utf8");
2117
- renameSync(tempPath, path);
2118
- }
2119
3035
  function isRecord(value) {
2120
3036
  return typeof value === "object" && value !== null && !Array.isArray(value);
2121
3037
  }
@@ -2187,7 +3103,8 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2187
3103
  console.log(t("cli.init.plan.writes"));
2188
3104
  console.log(` - ${target}/.fabric/bootstrap/README.md`);
2189
3105
  console.log(` - ${target}/.fabric/agents.meta.json`);
2190
- console.log(` - ${target}/.fabric/human-lock.json`);
3106
+ console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
3107
+ console.log(` - ${target}/.fabric/events.jsonl`);
2191
3108
  console.log(` - ${target}/.fabric/forensic.json`);
2192
3109
  }
2193
3110
  function printInitCapabilitySummary(supports, stageResults, options) {
@@ -2351,7 +3268,7 @@ function skippedStageLabel() {
2351
3268
  function failedStageLabel() {
2352
3269
  return paint.error(t("cli.init.stages.failed"));
2353
3270
  }
2354
- function writeStderr(message) {
3271
+ function writeStderr3(message) {
2355
3272
  process.stderr.write(`${message}
2356
3273
  `);
2357
3274
  }