@fenglimg/fabric-cli 1.7.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.
@@ -14,18 +14,21 @@ import {
14
14
  } from "./chunk-QPCRBQ5Y.js";
15
15
 
16
16
  // src/commands/init.ts
17
- import { createHash } from "crypto";
17
+ import { createHash, randomUUID } from "crypto";
18
18
  import * as childProcess from "child_process";
19
- import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
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
20
  import { dirname as dirname5, isAbsolute as isAbsolute4, join as join8, parse as parse3, resolve as resolve9 } from "path";
21
21
  import { fileURLToPath as fileURLToPath4 } from "url";
22
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";
23
24
  import { defineCommand as defineCommand4 } from "citty";
25
+ import { checkLockOrThrow } from "@fenglimg/fabric-server";
24
26
 
25
27
  // src/bootstrap-guide.ts
26
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
28
+ import { existsSync, mkdirSync, readFileSync } from "fs";
27
29
  import { dirname, isAbsolute, join, parse, resolve } from "path";
28
30
  import { fileURLToPath } from "url";
31
+ import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
29
32
  var AGENTS_TEMPLATE_BY_FRAMEWORK = {
30
33
  "cocos-creator": "templates/agents-md/variants/cocos.md",
31
34
  vite: "templates/agents-md/variants/vite.md",
@@ -47,7 +50,7 @@ async function ensureFabricBootstrapGuide(workspaceRoot, force) {
47
50
  return;
48
51
  }
49
52
  mkdirSync(dirname(guidePath), { recursive: true });
50
- writeFileSync(guidePath, await buildFabricBootstrapGuide(workspaceRoot), "utf8");
53
+ await atomicWriteText(guidePath, await buildFabricBootstrapGuide(workspaceRoot));
51
54
  }
52
55
  function findBootstrapTemplatePath(frameworkKind) {
53
56
  const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
@@ -115,9 +118,10 @@ import { homedir as homedir2, platform } from "os";
115
118
 
116
119
  // src/config/json.ts
117
120
  import { existsSync as existsSync2 } from "fs";
118
- import { mkdir, readFile, writeFile } from "fs/promises";
121
+ import { mkdir, readFile } from "fs/promises";
119
122
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
120
123
  import { homedir } from "os";
124
+ import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
121
125
 
122
126
  // src/config/writer.ts
123
127
  function createServerEntry(serverPath) {
@@ -128,6 +132,19 @@ function createServerEntry(serverPath) {
128
132
  }
129
133
 
130
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
+ }
131
148
  function expandHome(filePath) {
132
149
  if (filePath === "~") {
133
150
  return homedir();
@@ -159,12 +176,10 @@ async function readJsonConfig(configPath) {
159
176
  }
160
177
  }
161
178
  async function writeJsonClientConfig(configPath, serverEntry) {
162
- const config = await readJsonConfig(configPath);
163
- const existingServers = config.mcpServers;
164
- config.mcpServers = existingServers !== null && typeof existingServers === "object" && !Array.isArray(existingServers) ? { ...existingServers, fabric: serverEntry } : { fabric: serverEntry };
179
+ const existing = await readJsonConfig(configPath);
180
+ const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
165
181
  await mkdir(dirname2(configPath), { recursive: true });
166
- await writeFile(configPath, `${JSON.stringify(config, null, 2)}
167
- `, "utf8");
182
+ await atomicWriteJson(configPath, merged, { indent: 2 });
168
183
  }
169
184
  var JsonClientConfigWriter = class {
170
185
  configuredPath;
@@ -189,18 +204,21 @@ var JsonClientConfigWriter = class {
189
204
  };
190
205
  var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
191
206
  clientKind = "ClaudeCodeCLI";
192
- constructor(configuredPath) {
207
+ scope;
208
+ constructor(configuredPath, scope = "project") {
193
209
  super(configuredPath);
210
+ this.scope = scope;
194
211
  }
195
- // Writes to project-level .claude/settings.json so MCP is scoped to the project.
196
- // Detection in resolver still checks ~/ to confirm Claude Code is installed.
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.
197
215
  defaultPath(workspaceRoot) {
198
216
  const globalClaudeDir = join2(homedir(), ".claude");
199
217
  const projectClaudeDir = join2(workspaceRoot, ".claude");
200
218
  if (!existsSync2(globalClaudeDir) && !existsSync2(projectClaudeDir)) {
201
219
  return null;
202
220
  }
203
- return join2(projectClaudeDir, "settings.json");
221
+ return this.scope === "user" ? join2(homedir(), ".claude.json") : join2(workspaceRoot, ".mcp.json");
204
222
  }
205
223
  };
206
224
  var CursorWriter = class extends JsonClientConfigWriter {
@@ -213,36 +231,6 @@ var CursorWriter = class extends JsonClientConfigWriter {
213
231
  return existsSync2(cursorDir) ? join2(cursorDir, "mcp.json") : null;
214
232
  }
215
233
  };
216
- var WindsurfWriter = class extends JsonClientConfigWriter {
217
- clientKind = "Windsurf";
218
- constructor(configuredPath) {
219
- super(configuredPath);
220
- }
221
- defaultPath(workspaceRoot) {
222
- const windsurfDir = join2(workspaceRoot, ".windsurf");
223
- return existsSync2(windsurfDir) ? join2(windsurfDir, "mcp.json") : null;
224
- }
225
- };
226
- var RooCodeWriter = class extends JsonClientConfigWriter {
227
- clientKind = "RooCode";
228
- constructor(configuredPath) {
229
- super(configuredPath);
230
- }
231
- defaultPath(workspaceRoot) {
232
- const rooDir = join2(workspaceRoot, ".roo");
233
- return existsSync2(rooDir) ? join2(rooDir, "mcp.json") : null;
234
- }
235
- };
236
- var GeminiCLIWriter = class extends JsonClientConfigWriter {
237
- clientKind = "GeminiCLI";
238
- constructor(configuredPath) {
239
- super(configuredPath);
240
- }
241
- defaultPath(workspaceRoot) {
242
- const geminiDir = join2(homedir(), ".gemini");
243
- return existsSync2(geminiDir) || existsSync2(join2(workspaceRoot, "GEMINI.md")) ? join2(geminiDir, "settings.json") : null;
244
- }
245
- };
246
234
 
247
235
  // src/config/claude-code.ts
248
236
  function getClaudeDesktopConfigPath() {
@@ -279,9 +267,10 @@ var ClaudeCodeDesktopWriter = class {
279
267
 
280
268
  // src/config/toml.ts
281
269
  import { existsSync as existsSync4 } from "fs";
282
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
270
+ import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
283
271
  import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
284
272
  import { homedir as homedir3 } from "os";
273
+ import { atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
285
274
  function expandHome2(filePath) {
286
275
  if (filePath === "~") {
287
276
  return homedir3();
@@ -366,7 +355,7 @@ var CodexTOMLConfigWriter = class {
366
355
  const rawConfig = await readTomlConfigText(configPath);
367
356
  const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
368
357
  await mkdir2(dirname3(configPath), { recursive: true });
369
- await writeFile2(configPath, nextConfig, "utf8");
358
+ await atomicWriteText2(configPath, nextConfig);
370
359
  }
371
360
  };
372
361
 
@@ -380,13 +369,14 @@ function addIfDetected(writers, detected, createWriter, configuredPath) {
380
369
  writers.push(createWriter(configuredPath));
381
370
  }
382
371
  }
383
- function resolveClients(workspaceRoot, fabricConfig = {}) {
372
+ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
384
373
  const clientPaths = fabricConfig.clientPaths;
385
374
  const writers = [];
375
+ const claudeMcpScope = opts.claudeMcpScope ?? "project";
386
376
  addIfDetected(
387
377
  writers,
388
378
  existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude")),
389
- (configuredPath) => new ClaudeCodeCLIWriter(configuredPath),
379
+ (configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
390
380
  hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
391
381
  );
392
382
  addIfDetected(
@@ -401,24 +391,6 @@ function resolveClients(workspaceRoot, fabricConfig = {}) {
401
391
  (configuredPath) => new CursorWriter(configuredPath),
402
392
  hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
403
393
  );
404
- addIfDetected(
405
- writers,
406
- existsSync5(join5(workspaceRoot, ".windsurf")),
407
- (configuredPath) => new WindsurfWriter(configuredPath),
408
- hasExplicitPath(clientPaths, "windsurf") ? clientPaths.windsurf : void 0
409
- );
410
- addIfDetected(
411
- writers,
412
- existsSync5(join5(workspaceRoot, ".roo")),
413
- (configuredPath) => new RooCodeWriter(configuredPath),
414
- hasExplicitPath(clientPaths, "rooCode") ? clientPaths.rooCode : void 0
415
- );
416
- addIfDetected(
417
- writers,
418
- existsSync5(join5(homedir4(), ".gemini")) || existsSync5(join5(workspaceRoot, "GEMINI.md")),
419
- (configuredPath) => new GeminiCLIWriter(configuredPath),
420
- hasExplicitPath(clientPaths, "geminiCLI") ? clientPaths.geminiCLI : void 0
421
- );
422
394
  addIfDetected(
423
395
  writers,
424
396
  existsSync5(join5(homedir4(), ".codex")),
@@ -432,9 +404,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
432
404
  const claudeDetected = existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude"));
433
405
  const claudeDesktopDetected = existsSync5(getClaudeDesktopConfigPath());
434
406
  const cursorDetected = existsSync5(join5(workspaceRoot, ".cursor"));
435
- const windsurfDetected = existsSync5(join5(workspaceRoot, ".windsurf"));
436
- const rooDetected = existsSync5(join5(workspaceRoot, ".roo"));
437
- const geminiDetected = existsSync5(join5(homedir4(), ".gemini")) || existsSync5(join5(workspaceRoot, "GEMINI.md"));
438
407
  const codexDetected = existsSync5(join5(homedir4(), ".codex"));
439
408
  return [
440
409
  {
@@ -480,45 +449,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
480
449
  skill: false
481
450
  }
482
451
  },
483
- {
484
- clientKind: "Windsurf",
485
- label: "Windsurf",
486
- detected: windsurfDetected || hasExplicitPath(clientPaths, "windsurf"),
487
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
488
- configPath: ".windsurf/mcp.json",
489
- capabilities: {
490
- bootstrap: true,
491
- mcp: true,
492
- hook: false,
493
- skill: false
494
- }
495
- },
496
- {
497
- clientKind: "RooCode",
498
- label: "Roo Code",
499
- detected: rooDetected || hasExplicitPath(clientPaths, "rooCode"),
500
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
501
- configPath: ".roo/mcp.json",
502
- capabilities: {
503
- bootstrap: true,
504
- mcp: true,
505
- hook: false,
506
- skill: false
507
- }
508
- },
509
- {
510
- clientKind: "GeminiCLI",
511
- label: "Gemini CLI",
512
- detected: geminiDetected || hasExplicitPath(clientPaths, "geminiCLI"),
513
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
514
- configPath: "~/.gemini/settings.json",
515
- capabilities: {
516
- bootstrap: true,
517
- mcp: true,
518
- hook: false,
519
- skill: false
520
- }
521
- },
522
452
  {
523
453
  clientKind: "CodexCLI",
524
454
  label: "Codex CLI",
@@ -549,13 +479,6 @@ var CLIENT_ALIASES = {
549
479
  claudedesktop: "claude",
550
480
  claudecodedesktop: "claude",
551
481
  cursor: "cursor",
552
- windsurf: "windsurf",
553
- roo: "roo",
554
- "roo-code": "roo",
555
- roocode: "roo",
556
- gemini: "gemini",
557
- "gemini-cli": "gemini",
558
- geminicli: "gemini",
559
482
  codex: "codex",
560
483
  "codex-cli": "codex",
561
484
  codexcli: "codex"
@@ -662,12 +585,6 @@ function mapClientKind(clientKind) {
662
585
  return "claude";
663
586
  case "Cursor":
664
587
  return "cursor";
665
- case "Windsurf":
666
- return "windsurf";
667
- case "RooCode":
668
- return "roo";
669
- case "GeminiCLI":
670
- return "gemini";
671
588
  case "CodexCLI":
672
589
  return "codex";
673
590
  default:
@@ -680,12 +597,6 @@ function mapBootstrapClientToClientKind(client) {
680
597
  return "ClaudeCodeCLI";
681
598
  case "cursor":
682
599
  return "Cursor";
683
- case "windsurf":
684
- return "Windsurf";
685
- case "roo":
686
- return "RooCode";
687
- case "gemini":
688
- return "GeminiCLI";
689
600
  case "codex":
690
601
  return "CodexCLI";
691
602
  }
@@ -699,10 +610,11 @@ import { fileURLToPath as fileURLToPath3 } from "url";
699
610
  import { defineCommand as defineCommand3 } from "citty";
700
611
 
701
612
  // src/commands/hooks.ts
702
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
613
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync } from "fs";
703
614
  import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, parse as parse2, resolve as resolve6 } from "path";
704
615
  import { fileURLToPath as fileURLToPath2 } from "url";
705
616
  import { defineCommand as defineCommand2 } from "citty";
617
+ import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText3 } from "@fenglimg/fabric-shared/node/atomic-write";
706
618
  var hooksCommand = defineCommand2({
707
619
  meta: {
708
620
  name: "hooks",
@@ -750,7 +662,7 @@ async function installHooks(target, options = {}) {
750
662
  }
751
663
  mkdirSync2(huskyDir, { recursive: true });
752
664
  const templateContent = readFileSync2(findTemplatePath2("templates/husky/pre-commit"), "utf8");
753
- const hookAction = installHookFile(hookPath, templateContent, options.force);
665
+ const hookAction = await installHookFile(hookPath, templateContent, options.force);
754
666
  chmodSync(hookPath, 493);
755
667
  const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
756
668
  const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
@@ -759,8 +671,7 @@ async function installHooks(target, options = {}) {
759
671
  if (!hadPrepare) {
760
672
  scripts.prepare = "husky install";
761
673
  packageJson.scripts = scripts;
762
- writeFileSync2(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
763
- `, "utf8");
674
+ await atomicWriteJson2(packageJsonPath, packageJson);
764
675
  prepareAction = "added";
765
676
  }
766
677
  const installed = [];
@@ -792,10 +703,10 @@ function assertExistingDirectory(target) {
792
703
  throw new Error(t("cli.shared.target-invalid", { target }));
793
704
  }
794
705
  }
795
- function installHookFile(hookPath, templateContent, force) {
706
+ async function installHookFile(hookPath, templateContent, force) {
796
707
  if (existsSync6(hookPath)) {
797
708
  if (force) {
798
- writeFileSync2(hookPath, templateContent, "utf8");
709
+ await atomicWriteText3(hookPath, templateContent);
799
710
  return "overwritten";
800
711
  }
801
712
  const existing = readFileSync2(hookPath, "utf8");
@@ -804,11 +715,11 @@ function installHookFile(hookPath, templateContent, force) {
804
715
  }
805
716
  const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
806
717
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
807
- writeFileSync2(hookPath, `${existing}${separator}# --- Fabric ---
808
- ${fabricBlock}`, "utf8");
718
+ await atomicWriteText3(hookPath, `${existing}${separator}# --- Fabric ---
719
+ ${fabricBlock}`);
809
720
  return "appended";
810
721
  }
811
- writeFileSync2(hookPath, templateContent, "utf8");
722
+ await atomicWriteText3(hookPath, templateContent);
812
723
  return "created";
813
724
  }
814
725
  function findTemplatePath2(relativePath) {
@@ -852,13 +763,6 @@ var CLIENT_ALIASES2 = {
852
763
  "claude-code-desktop": "ClaudeCodeDesktop",
853
764
  claudedesktop: "ClaudeCodeDesktop",
854
765
  cursor: "Cursor",
855
- windsurf: "Windsurf",
856
- roocode: "RooCode",
857
- "roo-code": "RooCode",
858
- roo: "RooCode",
859
- geminicli: "GeminiCLI",
860
- "gemini-cli": "GeminiCLI",
861
- gemini: "GeminiCLI",
862
766
  codexcli: "CodexCLI",
863
767
  "codex-cli": "CodexCLI",
864
768
  codex: "CodexCLI"
@@ -953,7 +857,7 @@ async function installMcpClients(target, options = {}) {
953
857
  const fabricConfig = await loadFabricConfig(workspaceRoot);
954
858
  const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
955
859
  const serverPath = resolveServerPath(options.localServerPath);
956
- const writers = resolveClients(workspaceRoot, fabricConfig).filter(
860
+ const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
957
861
  (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
958
862
  );
959
863
  const installed = [];
@@ -2088,7 +1992,7 @@ function readProjectName(target) {
2088
1992
  return basename(target);
2089
1993
  }
2090
1994
  function getCliVersion() {
2091
- return true ? "1.7.0" : "unknown";
1995
+ return true ? "1.8.0-rc.1" : "unknown";
2092
1996
  }
2093
1997
  function sortRecord(record) {
2094
1998
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -2098,7 +2002,7 @@ function toPosixPath(path) {
2098
2002
  }
2099
2003
 
2100
2004
  // src/commands/init.ts
2101
- 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";
2102
2006
  var CLAUDE_INIT_REMINDER_HOOK_TEMPLATE = "templates/claude-hooks/agents-md-init-reminder.cjs";
2103
2007
  var CLAUDE_INIT_REMINDER_COMMAND = ".claude/hooks/agents-md-init-reminder.cjs";
2104
2008
  var CODEX_INIT_SKILL_TEMPLATE = "templates/codex-skills/fabric-init/SKILL.md";
@@ -2168,6 +2072,10 @@ var initCommand = defineCommand4({
2168
2072
  type: "string",
2169
2073
  default: "global",
2170
2074
  description: t("cli.init.mcp.install.prompt")
2075
+ },
2076
+ scope: {
2077
+ type: "string",
2078
+ description: t("cli.init.mcp.scope.description")
2171
2079
  }
2172
2080
  },
2173
2081
  async run({ args }) {
@@ -2179,6 +2087,9 @@ async function runInitCommand(args) {
2179
2087
  const logger = createDebugLogger(args.debug);
2180
2088
  const resolution = resolveDevMode(args.target, process.cwd());
2181
2089
  const intent = resolveInitCliIntent(args, resolution.target);
2090
+ if (args.reapply === true) {
2091
+ checkLockOrThrow(intent.target, { force: args.force });
2092
+ }
2182
2093
  logger(`init target source: ${resolution.source}`);
2183
2094
  for (const step of resolution.chain) {
2184
2095
  logger(step);
@@ -2197,6 +2108,7 @@ async function runInitCommand(args) {
2197
2108
  target: intent.target,
2198
2109
  options: intent.options,
2199
2110
  mcpInstallMode: intent.mcpInstallMode,
2111
+ claudeMcpScope: intent.claudeMcpScope,
2200
2112
  interactive: intent.interactiveSummary && !intent.wizardEnabled,
2201
2113
  supports
2202
2114
  });
@@ -2210,6 +2122,7 @@ async function runInitCommand(args) {
2210
2122
  function resolveInitCliIntent(args, targetInput) {
2211
2123
  const target = normalizeTarget4(targetInput);
2212
2124
  const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
2125
+ const claudeMcpScope = resolveClaudeMcpScope(args.scope);
2213
2126
  const terminalInteractive = isInteractiveInit();
2214
2127
  const planOnly = args.plan === true;
2215
2128
  const reapply = args.reapply === true;
@@ -2225,21 +2138,34 @@ function resolveInitCliIntent(args, targetInput) {
2225
2138
  target,
2226
2139
  options,
2227
2140
  mcpInstallMode,
2141
+ claudeMcpScope,
2228
2142
  interactiveSummary: args.interactive !== false && terminalInteractive,
2229
2143
  wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
2230
2144
  };
2231
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
+ }
2232
2156
  async function buildInitExecutionPlan(input) {
2233
2157
  const options = input.options ?? {};
2234
2158
  const scaffold = await buildInitFabricPlan(input.target, options);
2235
2159
  const supports = input.supports ?? detectClientSupports(input.target);
2236
2160
  const mcpInstallMode = input.mcpInstallMode ?? "global";
2161
+ const claudeMcpScope = input.claudeMcpScope ?? "project";
2237
2162
  const stages = [
2238
2163
  { name: "bootstrap", skipped: Boolean(options.skipBootstrap) },
2239
2164
  {
2240
2165
  name: "mcp",
2241
2166
  skipped: Boolean(options.skipMcp),
2242
2167
  installMode: mcpInstallMode,
2168
+ claudeMcpScope,
2243
2169
  localServerPath: mcpInstallMode === "local" ? LOCAL_FABRIC_SERVER_PATH : void 0,
2244
2170
  packageManager: mcpInstallMode === "local" ? detectPackageManager(input.target) : void 0
2245
2171
  },
@@ -2249,6 +2175,7 @@ async function buildInitExecutionPlan(input) {
2249
2175
  target: input.target,
2250
2176
  options,
2251
2177
  mcpInstallMode,
2178
+ claudeMcpScope,
2252
2179
  interactive: input.interactive ?? false,
2253
2180
  supports,
2254
2181
  scaffold,
@@ -2288,7 +2215,7 @@ async function executeInitExecutionPlan(plan) {
2288
2215
  case "preflight":
2289
2216
  break;
2290
2217
  case "scaffold":
2291
- created = executeInitFabricPlan(plan.scaffold);
2218
+ created = await executeInitFabricPlan(plan.scaffold);
2292
2219
  printInitScaffoldResult(created);
2293
2220
  break;
2294
2221
  case "bootstrap":
@@ -2319,7 +2246,7 @@ async function buildInitFabricPlan(target, options) {
2319
2246
  const taxonomyPath = join8(fabricDir, "INITIAL_TAXONOMY.md");
2320
2247
  const rulesDir = join8(fabricDir, "rules");
2321
2248
  const eventsPath = join8(fabricDir, "events.jsonl");
2322
- const claudeSkillPath = join8(target, ".claude", "skills", "agents-md-init", "SKILL.md");
2249
+ const claudeSkillPath = join8(target, ".claude", "skills", "fabric-init", "SKILL.md");
2323
2250
  const codexSkillPath = join8(target, ".agents", "skills", "fabric-init", "SKILL.md");
2324
2251
  const codexSessionStartHookPath = join8(target, ".codex", "hooks", "fabric-session-start.cjs");
2325
2252
  const codexStopHookPath = join8(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
@@ -2382,32 +2309,49 @@ async function buildInitFabricPlan(target, options) {
2382
2309
  claudeSettings: buildClaudeSettingsWritePlan(claudeSettingsPath, options)
2383
2310
  };
2384
2311
  }
2385
- 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;
2386
2316
  if (plan.replaceFabricDir) {
2387
2317
  rmSync(plan.fabricDir, { force: true });
2388
2318
  }
2389
2319
  mkdirSync3(plan.fabricDir, { recursive: true });
2390
2320
  mkdirSync3(dirname5(plan.bootstrapPath), { recursive: true });
2391
2321
  preparePlannedPath(plan.bootstrapPath, plan.bootstrapAction);
2392
- writeFileSync3(plan.bootstrapPath, plan.bootstrapContent, "utf8");
2393
- preparePlannedPath(plan.metaPath, plan.metaAction);
2394
- writeFileSync3(plan.metaPath, `${JSON.stringify(plan.meta, null, 2)}
2395
- `, "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
+ }
2396
2327
  preparePlannedPath(plan.taxonomyPath, plan.taxonomyAction);
2397
- writeFileSync3(plan.taxonomyPath, ensureTrailingNewline2(plan.taxonomyContent), "utf8");
2328
+ await atomicWriteText4(plan.taxonomyPath, ensureTrailingNewline2(plan.taxonomyContent));
2398
2329
  mkdirSync3(plan.rulesDir, { recursive: true });
2399
- preparePlannedPath(plan.eventsPath, plan.eventsAction);
2400
- writeFileSync3(plan.eventsPath, "", "utf8");
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
+ }
2401
2339
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
2402
- writeFileSync3(plan.forensicPath, `${JSON.stringify(plan.forensicReport, null, 2)}
2403
- `, "utf8");
2340
+ await atomicWriteJson3(plan.forensicPath, plan.forensicReport);
2404
2341
  applyOptionalTemplateWritePlan(plan.claudeSkill);
2405
2342
  applyOptionalTemplateWritePlan(plan.codexSkill);
2406
2343
  applyOptionalTemplateWritePlan(plan.codexSessionStartHook);
2407
2344
  applyOptionalTemplateWritePlan(plan.codexStopHook);
2408
- applyJsonWritePlan(plan.codexHooksConfig);
2345
+ await applyJsonWritePlan(plan.codexHooksConfig);
2409
2346
  applyOptionalTemplateWritePlan(plan.claudeHook);
2410
- 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
+ }
2411
2355
  return {
2412
2356
  bootstrapPath: plan.bootstrapPath,
2413
2357
  bootstrapAction: plan.bootstrapAction,
@@ -2436,7 +2380,7 @@ function executeInitFabricPlan(plan) {
2436
2380
  };
2437
2381
  }
2438
2382
  async function initFabric(target, options) {
2439
- return executeInitFabricPlan(await buildInitFabricPlan(target, options));
2383
+ return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
2440
2384
  }
2441
2385
  function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
2442
2386
  return terminalInteractive && args.interactive !== false && args.yes !== true;
@@ -2447,6 +2391,7 @@ async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter)
2447
2391
  options: basePlan.options,
2448
2392
  supports: basePlan.supports,
2449
2393
  mcpInstallMode: basePlan.mcpInstallMode,
2394
+ claudeMcpScope: basePlan.claudeMcpScope,
2450
2395
  lockedStages: collectLockedWizardStages(args)
2451
2396
  });
2452
2397
  if (selection === null) {
@@ -2461,6 +2406,7 @@ async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter)
2461
2406
  skipHooks: !selection.hooks
2462
2407
  },
2463
2408
  mcpInstallMode: selection.mcp ? selection.mcpInstallMode : basePlan.mcpInstallMode,
2409
+ claudeMcpScope: selection.claudeMcpScope,
2464
2410
  interactive: false,
2465
2411
  supports: basePlan.supports
2466
2412
  });
@@ -2584,7 +2530,8 @@ async function executeInitStagePlan(plan, stageName) {
2584
2530
  }
2585
2531
  const result = await installMcpClients(plan.target, {
2586
2532
  force: plan.options.force,
2587
- localServerPath: stage.localServerPath
2533
+ localServerPath: stage.localServerPath,
2534
+ claudeMcpScope: stage.claudeMcpScope
2588
2535
  });
2589
2536
  if (result.details.length === 0) {
2590
2537
  console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
@@ -2681,12 +2628,12 @@ function buildCodexHooksConfigPlan(configPath, options) {
2681
2628
  value: buildCodexHooksConfigValue()
2682
2629
  };
2683
2630
  }
2684
- function applyJsonWritePlan(plan) {
2631
+ async function applyJsonWritePlan(plan) {
2685
2632
  if (plan.action === "skipped") {
2686
2633
  return;
2687
2634
  }
2688
2635
  mkdirSync3(dirname5(plan.path), { recursive: true });
2689
- writeJsonAtomically(plan.path, plan.value);
2636
+ await atomicWriteJson3(plan.path, plan.value);
2690
2637
  }
2691
2638
  function buildClaudeSettingsWritePlan(settingsPath, options) {
2692
2639
  let settings;
@@ -2746,12 +2693,12 @@ function buildClaudeSettingsWritePlan(settingsPath, options) {
2746
2693
  value: nextSettings
2747
2694
  };
2748
2695
  }
2749
- function applyClaudeSettingsWritePlan(plan) {
2696
+ async function applyClaudeSettingsWritePlan(plan) {
2750
2697
  if (plan.value === null) {
2751
2698
  return;
2752
2699
  }
2753
2700
  mkdirSync3(dirname5(plan.path), { recursive: true });
2754
- writeJsonAtomically(plan.path, plan.value);
2701
+ await atomicWriteJson3(plan.path, plan.value);
2755
2702
  }
2756
2703
  function createDefaultInitWizardAdapter() {
2757
2704
  return {
@@ -2799,6 +2746,14 @@ function createDefaultInitWizardAdapter() {
2799
2746
  { value: "local", label: "local", hint: t("cli.init.mcp.install.local") }
2800
2747
  ]
2801
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,
2802
2757
  hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
2803
2758
  message: t("cli.init.wizard.stage.hooks", {
2804
2759
  defaultValue: formatPromptDefault(!context.options.skipHooks)
@@ -2865,6 +2820,17 @@ async function selectMcpInstallModeInGroup(options) {
2865
2820
  }
2866
2821
  return result;
2867
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
+ }
2868
2834
  function collectLockedWizardStages(args) {
2869
2835
  const lockedStages = [];
2870
2836
  if (args.bootstrap === false) {
@@ -2957,6 +2923,21 @@ function createInitialMeta(agentsHash) {
2957
2923
  }
2958
2924
  };
2959
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
+ }
2960
2941
  function buildInitialTaxonomyMarkdown(forensicReport) {
2961
2942
  const frameworkInfo = forensicReport.framework;
2962
2943
  const framework = [frameworkInfo?.kind ?? "unknown", frameworkInfo?.subkind ?? ""].filter((value) => value.trim() !== "").join(" / ") || "unknown";
@@ -3051,12 +3032,6 @@ function isClaudeInitReminderStopEntry(entry) {
3051
3032
  (hook) => isRecord(hook) && hook.type === "command" && typeof hook.command === "string" && hook.command.includes("agents-md-init-reminder.cjs")
3052
3033
  );
3053
3034
  }
3054
- function writeJsonAtomically(path, value) {
3055
- const tempPath = `${path}.${process.pid}.tmp`;
3056
- writeFileSync3(tempPath, `${JSON.stringify(value, null, 2)}
3057
- `, "utf8");
3058
- renameSync(tempPath, path);
3059
- }
3060
3035
  function isRecord(value) {
3061
3036
  return typeof value === "object" && value !== null && !Array.isArray(value);
3062
3037
  }