@fenglimg/fabric-cli 2.0.0-rc.1 → 2.0.0

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.
@@ -1,172 +1,77 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ClaudeCodeCLIWriter,
4
+ CursorWriter,
5
+ addArchiveSkillPointer,
6
+ createServerEntry,
7
+ hooksCommand,
8
+ installArchiveHintHook,
9
+ installFabricArchiveSkill,
10
+ installFabricImportSkill,
11
+ installFabricReviewSkill,
12
+ installHooks,
13
+ installKnowledgeHintBroadHook,
14
+ installKnowledgeHintNarrowHook,
15
+ mergeClaudeCodeHookConfig,
16
+ mergeCodexHookConfig,
17
+ mergeCursorHookConfig,
18
+ normalizeConfigPath,
19
+ writeJsonClientConfig
20
+ } from "./chunk-74SZWYPH.js";
2
21
  import {
3
22
  detectFramework,
4
23
  runInitScan
5
- } from "./chunk-UHNP7T7W.js";
24
+ } from "./chunk-EYIDD2YS.js";
6
25
  import {
7
- createDebugLogger,
8
26
  displayWidth,
9
27
  padEnd,
10
- paint,
11
- resolveDevMode,
28
+ paint
29
+ } from "./chunk-WWNXR34K.js";
30
+ import {
12
31
  t
13
- } from "./chunk-5LOYBXWD.js";
32
+ } from "./chunk-6ICJICVU.js";
33
+ import {
34
+ createDebugLogger,
35
+ resolveDevMode
36
+ } from "./chunk-OBQU6NHO.js";
14
37
 
15
38
  // src/commands/init.ts
16
39
  import { randomUUID } from "crypto";
17
- import { homedir as homedir5 } from "os";
40
+ import { homedir as homedir4 } from "os";
18
41
  import * as childProcess from "child_process";
19
- import { appendFileSync, existsSync as existsSync8, mkdirSync, rmSync, statSync as statSync3, writeFileSync } from "fs";
20
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join6, resolve as resolve7 } from "path";
42
+ import { appendFileSync, existsSync as existsSync6, mkdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
43
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "path";
21
44
  import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
22
45
  import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
23
- import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
24
- import { defineCommand as defineCommand3 } from "citty";
46
+ import { atomicWriteJson, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
47
+ import { defineCommand as defineCommand2 } from "citty";
25
48
  import { checkLockOrThrow } from "@fenglimg/fabric-server";
26
49
 
27
50
  // src/commands/config.ts
28
- import { existsSync as existsSync6 } from "fs";
29
- import { readFile as readFile3 } from "fs/promises";
30
- import { resolve as resolve5 } from "path";
51
+ import { existsSync as existsSync4 } from "fs";
52
+ import { readFile as readFile2 } from "fs/promises";
53
+ import { resolve as resolve3 } from "path";
31
54
  import { fileURLToPath } from "url";
32
- import { defineCommand as defineCommand2 } from "citty";
55
+ import { defineCommand } from "citty";
33
56
 
34
57
  // src/config/resolver.ts
35
- import { existsSync as existsSync4 } from "fs";
36
- import { join as join4 } from "path";
37
- import { homedir as homedir4 } from "os";
58
+ import { existsSync as existsSync3 } from "fs";
59
+ import { join as join3 } from "path";
60
+ import { homedir as homedir3 } from "os";
38
61
 
39
62
  // src/config/claude-code.ts
40
- import { existsSync as existsSync2 } from "fs";
41
- import { join as join2, resolve as resolve2 } from "path";
42
- import { homedir as homedir2, platform } from "os";
43
-
44
- // src/config/json.ts
45
63
  import { existsSync } from "fs";
46
- import { mkdir, readFile } from "fs/promises";
47
- import { dirname, join, resolve } from "path";
48
- import { homedir } from "os";
49
- import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
50
-
51
- // src/config/writer.ts
52
- function createServerEntry(serverPath) {
53
- return {
54
- command: process.execPath,
55
- args: [serverPath]
56
- };
57
- }
58
-
59
- // src/config/json.ts
60
- function deepMerge(target, source) {
61
- if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
62
- return source;
63
- }
64
- const out = { ...target };
65
- for (const key of Object.keys(source)) {
66
- out[key] = deepMerge(
67
- target[key],
68
- source[key]
69
- );
70
- }
71
- return out;
72
- }
73
- function expandHome(filePath) {
74
- if (filePath === "~") {
75
- return homedir();
76
- }
77
- if (filePath.startsWith("~/")) {
78
- return join(homedir(), filePath.slice(2));
79
- }
80
- return filePath;
81
- }
82
- function normalizeConfigPath(filePath) {
83
- return resolve(expandHome(filePath));
84
- }
85
- async function readJsonConfig(configPath) {
86
- try {
87
- const raw = await readFile(configPath, "utf8");
88
- if (raw.trim().length === 0) {
89
- return {};
90
- }
91
- const parsed = JSON.parse(raw);
92
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
93
- throw new Error(`Expected JSON object in ${configPath}`);
94
- }
95
- return parsed;
96
- } catch (error) {
97
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
98
- return {};
99
- }
100
- throw error;
101
- }
102
- }
103
- async function writeJsonClientConfig(configPath, serverEntry) {
104
- const existing = await readJsonConfig(configPath);
105
- const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
106
- await mkdir(dirname(configPath), { recursive: true });
107
- await atomicWriteJson(configPath, merged, { indent: 2 });
108
- }
109
- var JsonClientConfigWriter = class {
110
- configuredPath;
111
- constructor(configuredPath) {
112
- this.configuredPath = configuredPath;
113
- }
114
- async detect(workspaceRoot, overridePath) {
115
- const explicitPath = overridePath ?? this.configuredPath;
116
- if (explicitPath !== void 0) {
117
- return normalizeConfigPath(explicitPath);
118
- }
119
- const configPath = this.defaultPath(workspaceRoot);
120
- return configPath === null ? null : normalizeConfigPath(configPath);
121
- }
122
- async write(serverPath, workspaceRoot, overridePath) {
123
- const configPath = await this.detect(workspaceRoot, overridePath);
124
- if (configPath === null) {
125
- return;
126
- }
127
- await writeJsonClientConfig(configPath, createServerEntry(serverPath));
128
- }
129
- };
130
- var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
131
- clientKind = "ClaudeCodeCLI";
132
- scope;
133
- constructor(configuredPath, scope = "project") {
134
- super(configuredPath);
135
- this.scope = scope;
136
- }
137
- // Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
138
- // or ~/.claude.json for user scope.
139
- // Detection still checks ~/.claude to confirm Claude Code is installed.
140
- defaultPath(workspaceRoot) {
141
- const globalClaudeDir = join(homedir(), ".claude");
142
- const projectClaudeDir = join(workspaceRoot, ".claude");
143
- if (!existsSync(globalClaudeDir) && !existsSync(projectClaudeDir)) {
144
- return null;
145
- }
146
- return this.scope === "user" ? join(homedir(), ".claude.json") : join(workspaceRoot, ".mcp.json");
147
- }
148
- };
149
- var CursorWriter = class extends JsonClientConfigWriter {
150
- clientKind = "Cursor";
151
- constructor(configuredPath) {
152
- super(configuredPath);
153
- }
154
- defaultPath(workspaceRoot) {
155
- const cursorDir = join(workspaceRoot, ".cursor");
156
- return existsSync(cursorDir) ? join(cursorDir, "mcp.json") : null;
157
- }
158
- };
159
-
160
- // src/config/claude-code.ts
64
+ import { join, resolve } from "path";
65
+ import { homedir, platform } from "os";
161
66
  function getClaudeDesktopConfigPath() {
162
67
  const os = platform();
163
68
  if (os === "darwin") {
164
- return join2(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
69
+ return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
165
70
  }
166
71
  if (os === "win32") {
167
- return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
72
+ return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
168
73
  }
169
- return join2(homedir2(), ".config", "Claude", "claude_desktop_config.json");
74
+ return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
170
75
  }
171
76
  var ClaudeCodeDesktopWriter = class {
172
77
  clientKind = "ClaudeCodeDesktop";
@@ -176,7 +81,7 @@ var ClaudeCodeDesktopWriter = class {
176
81
  }
177
82
  async detect(_workspaceRoot, overridePath) {
178
83
  const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
179
- return existsSync2(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
84
+ return existsSync(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
180
85
  }
181
86
  async write(serverPath, workspaceRoot, overridePath) {
182
87
  const configPath = await this.detect(workspaceRoot, overridePath);
@@ -191,17 +96,17 @@ var ClaudeCodeDesktopWriter = class {
191
96
  };
192
97
 
193
98
  // src/config/toml.ts
194
- import { existsSync as existsSync3 } from "fs";
195
- import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
196
- import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
197
- import { homedir as homedir3 } from "os";
99
+ import { existsSync as existsSync2 } from "fs";
100
+ import { mkdir, readFile } from "fs/promises";
101
+ import { dirname, join as join2, resolve as resolve2 } from "path";
102
+ import { homedir as homedir2 } from "os";
198
103
  import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
199
- function expandHome2(filePath) {
104
+ function expandHome(filePath) {
200
105
  if (filePath === "~") {
201
- return homedir3();
106
+ return homedir2();
202
107
  }
203
108
  if (filePath.startsWith("~/")) {
204
- return join3(homedir3(), filePath.slice(2));
109
+ return join2(homedir2(), filePath.slice(2));
205
110
  }
206
111
  return filePath;
207
112
  }
@@ -250,7 +155,7 @@ ${block}`;
250
155
  }
251
156
  async function readTomlConfigText(configPath) {
252
157
  try {
253
- return await readFile2(configPath, "utf8");
158
+ return await readFile(configPath, "utf8");
254
159
  } catch (error) {
255
160
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
256
161
  return "";
@@ -267,10 +172,10 @@ var CodexTOMLConfigWriter = class {
267
172
  async detect(_workspaceRoot, overridePath) {
268
173
  const explicitPath = overridePath ?? this.configuredPath;
269
174
  if (explicitPath !== void 0) {
270
- return resolve3(expandHome2(explicitPath));
175
+ return resolve2(expandHome(explicitPath));
271
176
  }
272
- const codexDir = join3(homedir3(), ".codex");
273
- return existsSync3(codexDir) ? resolve3(join3(codexDir, "config.toml")) : null;
177
+ const codexDir = join2(homedir2(), ".codex");
178
+ return existsSync2(codexDir) ? resolve2(join2(codexDir, "config.toml")) : null;
274
179
  }
275
180
  async write(serverPath, workspaceRoot, overridePath) {
276
181
  const configPath = await this.detect(workspaceRoot, overridePath);
@@ -279,7 +184,7 @@ var CodexTOMLConfigWriter = class {
279
184
  }
280
185
  const rawConfig = await readTomlConfigText(configPath);
281
186
  const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
282
- await mkdir2(dirname2(configPath), { recursive: true });
187
+ await mkdir(dirname(configPath), { recursive: true });
283
188
  await atomicWriteText(configPath, nextConfig);
284
189
  }
285
190
  };
@@ -300,25 +205,25 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
300
205
  const claudeMcpScope = opts.claudeMcpScope ?? "project";
301
206
  addIfDetected(
302
207
  writers,
303
- existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude")),
208
+ existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude")),
304
209
  (configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
305
210
  hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
306
211
  );
307
212
  addIfDetected(
308
213
  writers,
309
- existsSync4(getClaudeDesktopConfigPath()),
214
+ existsSync3(getClaudeDesktopConfigPath()),
310
215
  (configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
311
216
  hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
312
217
  );
313
218
  addIfDetected(
314
219
  writers,
315
- existsSync4(join4(workspaceRoot, ".cursor")),
220
+ existsSync3(join3(workspaceRoot, ".cursor")),
316
221
  (configuredPath) => new CursorWriter(configuredPath),
317
222
  hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
318
223
  );
319
224
  addIfDetected(
320
225
  writers,
321
- existsSync4(join4(homedir4(), ".codex")),
226
+ existsSync3(join3(homedir3(), ".codex")),
322
227
  (configuredPath) => new CodexTOMLConfigWriter(configuredPath),
323
228
  hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
324
229
  );
@@ -326,10 +231,10 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
326
231
  }
327
232
  function detectClientSupports(workspaceRoot, fabricConfig = {}) {
328
233
  const clientPaths = fabricConfig.clientPaths;
329
- const claudeDetected = existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude"));
330
- const claudeDesktopDetected = existsSync4(getClaudeDesktopConfigPath());
331
- const cursorDetected = existsSync4(join4(workspaceRoot, ".cursor"));
332
- const codexDetected = existsSync4(join4(homedir4(), ".codex"));
234
+ const claudeDetected = existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude"));
235
+ const claudeDesktopDetected = existsSync3(getClaudeDesktopConfigPath());
236
+ const cursorDetected = existsSync3(join3(workspaceRoot, ".cursor"));
237
+ const codexDetected = existsSync3(join3(homedir3(), ".codex"));
333
238
  return [
334
239
  {
335
240
  clientKind: "ClaudeCodeCLI",
@@ -387,7 +292,7 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
387
292
  skill: true
388
293
  },
389
294
  installedCapabilities: {
390
- hook: existsSync4(join4(workspaceRoot, ".codex", "hooks.json")),
295
+ hook: existsSync3(join3(workspaceRoot, ".codex", "hooks.json")),
391
296
  // v2/rc.2: v1 client-side init skill removed; skill-installation probes
392
297
  // will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
393
298
  // fabric-review, fabric-import). Until then there is nothing to probe.
@@ -397,50 +302,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
397
302
  ];
398
303
  }
399
304
 
400
- // src/commands/hooks.ts
401
- import { existsSync as existsSync5, statSync } from "fs";
402
- import { isAbsolute, resolve as resolve4 } from "path";
403
- import { defineCommand } from "citty";
404
- var hooksCommand = defineCommand({
405
- meta: {
406
- name: "hooks",
407
- description: t("cli.hooks.description")
408
- },
409
- subCommands: {
410
- install: defineCommand({
411
- meta: {
412
- name: "install",
413
- description: t("cli.hooks.install.description")
414
- },
415
- args: {
416
- target: {
417
- type: "string",
418
- description: t("cli.hooks.install.args.target.description"),
419
- default: process.cwd()
420
- }
421
- },
422
- async run({ args }) {
423
- await installHooks(args.target);
424
- }
425
- })
426
- }
427
- });
428
- async function installHooks(target, _options = {}) {
429
- const normalizedTarget = normalizeTarget(target);
430
- assertExistingDirectory(normalizedTarget);
431
- throw new Error(
432
- `fab hooks install is not available in v2 (husky pre-commit template removed). Use per-client hooks under .claude/ and .codex/ instead. Target: ${normalizedTarget}`
433
- );
434
- }
435
- function normalizeTarget(targetInput) {
436
- return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
437
- }
438
- function assertExistingDirectory(target) {
439
- if (!existsSync5(target) || !statSync(target).isDirectory()) {
440
- throw new Error(t("cli.shared.target-invalid", { target }));
441
- }
442
- }
443
-
444
305
  // src/commands/config.ts
445
306
  var CLIENT_ALIASES = {
446
307
  claude: "ClaudeCodeCLI",
@@ -471,11 +332,11 @@ function parseClientFilter(value) {
471
332
  return clients;
472
333
  }
473
334
  async function loadFabricConfig(workspaceRoot) {
474
- const configPath = resolve5(workspaceRoot, "fabric.config.json");
475
- if (!existsSync6(configPath)) {
335
+ const configPath = resolve3(workspaceRoot, "fabric.config.json");
336
+ if (!existsSync4(configPath)) {
476
337
  return {};
477
338
  }
478
- const parsed = JSON.parse(await readFile3(configPath, "utf8"));
339
+ const parsed = JSON.parse(await readFile2(configPath, "utf8"));
479
340
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
480
341
  throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
481
342
  }
@@ -483,21 +344,21 @@ async function loadFabricConfig(workspaceRoot) {
483
344
  }
484
345
  function resolveServerPath(override) {
485
346
  if (override) return override;
486
- if (process.env.FAB_SERVER_PATH) return resolve5(process.env.FAB_SERVER_PATH);
347
+ if (process.env.FAB_SERVER_PATH) return resolve3(process.env.FAB_SERVER_PATH);
487
348
  return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
488
349
  }
489
350
  function writeStderr(message) {
490
351
  process.stderr.write(`${message}
491
352
  `);
492
353
  }
493
- var configCmd = defineCommand2({
354
+ var configCmd = defineCommand({
494
355
  meta: {
495
356
  name: "config",
496
357
  description: t("cli.config.description")
497
358
  },
498
359
  subCommands: {
499
360
  hooks: hooksCommand,
500
- install: defineCommand2({
361
+ install: defineCommand({
501
362
  meta: {
502
363
  name: "install",
503
364
  description: t("cli.config.install.description")
@@ -541,7 +402,7 @@ var configCmd = defineCommand2({
541
402
  }
542
403
  });
543
404
  async function installMcpClients(target, options = {}) {
544
- const workspaceRoot = resolve5(target);
405
+ const workspaceRoot = resolve3(target);
545
406
  const fabricConfig = await loadFabricConfig(workspaceRoot);
546
407
  const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
547
408
  const serverPath = resolveServerPath(options.localServerPath);
@@ -572,9 +433,9 @@ async function installMcpClients(target, options = {}) {
572
433
 
573
434
  // src/scanner/forensic.ts
574
435
  import { execFileSync } from "child_process";
575
- import { existsSync as existsSync7, readdirSync, readFileSync, statSync as statSync2 } from "fs";
436
+ import { existsSync as existsSync5, readdirSync, readFileSync, statSync } from "fs";
576
437
  import { createRequire } from "module";
577
- import { basename, extname, isAbsolute as isAbsolute2, join as join5, posix, relative, resolve as resolve6, sep } from "path";
438
+ import { basename, extname, isAbsolute, join as join4, posix, relative, resolve as resolve4, sep } from "path";
578
439
  import {
579
440
  forensicReportSchema
580
441
  } from "@fenglimg/fabric-shared";
@@ -660,7 +521,7 @@ var parserInitPromise = null;
660
521
  var languagePromiseByKind = {};
661
522
  var parserBundlePromiseByKind = {};
662
523
  async function buildForensicReport(targetInput) {
663
- const target = normalizeTarget2(targetInput);
524
+ const target = normalizeTarget(targetInput);
664
525
  const framework = detectFramework(target);
665
526
  const topology = buildTopology(target);
666
527
  const entryPoints = collectEntryPoints(target, topology.files);
@@ -696,11 +557,11 @@ async function buildForensicReport(targetInput) {
696
557
  }
697
558
  return validation.data;
698
559
  }
699
- function normalizeTarget2(targetInput) {
700
- return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
560
+ function normalizeTarget(targetInput) {
561
+ return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
701
562
  }
702
563
  function buildTopology(root) {
703
- assertExistingDirectory2(root);
564
+ assertExistingDirectory(root);
704
565
  const byExt = {};
705
566
  const keyDirs = /* @__PURE__ */ new Set();
706
567
  const files = [];
@@ -713,7 +574,7 @@ function buildTopology(root) {
713
574
  continue;
714
575
  }
715
576
  for (const entry of readdirSync(current, { withFileTypes: true })) {
716
- const absolutePath = join5(current, entry.name);
577
+ const absolutePath = join4(current, entry.name);
717
578
  const relativePath = toPosixPath(relative(root, absolutePath));
718
579
  if (relativePath.length === 0) {
719
580
  continue;
@@ -733,7 +594,7 @@ function buildTopology(root) {
733
594
  if (!entry.isFile()) {
734
595
  continue;
735
596
  }
736
- const stats = statSync2(absolutePath);
597
+ const stats = statSync(absolutePath);
737
598
  const extension = extname(entry.name) || "[none]";
738
599
  byExt[extension] = (byExt[extension] ?? 0) + 1;
739
600
  totalFiles += 1;
@@ -751,8 +612,8 @@ function buildTopology(root) {
751
612
  files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
752
613
  };
753
614
  }
754
- function assertExistingDirectory2(target) {
755
- if (!existsSync7(target) || !statSync2(target).isDirectory()) {
615
+ function assertExistingDirectory(target) {
616
+ if (!existsSync5(target) || !statSync(target).isDirectory()) {
756
617
  throw new Error(`Target must be an existing directory: ${target}`);
757
618
  }
758
619
  }
@@ -801,7 +662,7 @@ function getEntryPointReason(relativePath) {
801
662
  async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
802
663
  const samples = [];
803
664
  for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
804
- const absolutePath = join5(target, ...entryPoint.path.split("/"));
665
+ const absolutePath = join4(target, ...entryPoint.path.split("/"));
805
666
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
806
667
  const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
807
668
  frameworkKind,
@@ -838,8 +699,8 @@ function readFirstLines(path, lineLimit) {
838
699
  }
839
700
  }
840
701
  function readPackageDependencies(target) {
841
- const packageJsonPath = join5(target, "package.json");
842
- if (!existsSync7(packageJsonPath)) {
702
+ const packageJsonPath = join4(target, "package.json");
703
+ if (!existsSync5(packageJsonPath)) {
843
704
  return /* @__PURE__ */ new Map();
844
705
  }
845
706
  try {
@@ -1179,9 +1040,9 @@ function scoreFrameworkConfidence(input) {
1179
1040
  return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
1180
1041
  }
1181
1042
  function readReadmeInfo(target) {
1182
- const readmePath = join5(target, "README.md");
1183
- const hasContributing = existsSync7(join5(target, "CONTRIBUTING.md"));
1184
- if (!existsSync7(readmePath)) {
1043
+ const readmePath = join4(target, "README.md");
1044
+ const hasContributing = existsSync5(join4(target, "CONTRIBUTING.md"));
1045
+ if (!existsSync5(readmePath)) {
1185
1046
  return {
1186
1047
  quality: "missing",
1187
1048
  line_count: 0,
@@ -1666,8 +1527,8 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
1666
1527
  return recommendations;
1667
1528
  }
1668
1529
  function readProjectName(target) {
1669
- const packageJsonPath = join5(target, "package.json");
1670
- if (existsSync7(packageJsonPath)) {
1530
+ const packageJsonPath = join4(target, "package.json");
1531
+ if (existsSync5(packageJsonPath)) {
1671
1532
  try {
1672
1533
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1673
1534
  if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
@@ -1680,7 +1541,7 @@ function readProjectName(target) {
1680
1541
  return basename(target);
1681
1542
  }
1682
1543
  function getCliVersion() {
1683
- return true ? "2.0.0-rc.1" : "unknown";
1544
+ return true ? "2.0.0" : "unknown";
1684
1545
  }
1685
1546
  function sortRecord(record) {
1686
1547
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1699,10 +1560,10 @@ Run \`fabric doctor\` to verify state.
1699
1560
 
1700
1561
  See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
1701
1562
  `;
1702
- var LOCAL_FABRIC_SERVER_PATH = join6("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1563
+ var LOCAL_FABRIC_SERVER_PATH = join5("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1703
1564
  var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
1704
1565
  var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
1705
- var initCommand = defineCommand3({
1566
+ var initCommand = defineCommand2({
1706
1567
  meta: {
1707
1568
  name: "init",
1708
1569
  description: t("cli.init.description")
@@ -1806,10 +1667,44 @@ async function runInitCommand(args) {
1806
1667
  process.exitCode = 130;
1807
1668
  return;
1808
1669
  }
1809
- return executeInitExecutionPlan(plan);
1670
+ const result = await executeInitExecutionPlan(plan);
1671
+ try {
1672
+ await maybeWriteImportSentinel({
1673
+ target: intent.target,
1674
+ planOnly: intent.options.planOnly === true,
1675
+ wizardEnabled: intent.wizardEnabled,
1676
+ terminalInteractive: intent.interactiveSummary
1677
+ });
1678
+ } catch {
1679
+ }
1680
+ if (!intent.options.planOnly) {
1681
+ console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
1682
+ }
1683
+ return result;
1684
+ }
1685
+ async function maybeWriteImportSentinel(opts) {
1686
+ if (opts.planOnly) return;
1687
+ if (process.env.FABRIC_NONINTERACTIVE === "1") return;
1688
+ if (!opts.wizardEnabled || !opts.terminalInteractive) return;
1689
+ if (!Boolean(process.stdin.isTTY)) return;
1690
+ const answer = await confirm({
1691
+ message: "\u4E0B\u6B21\u5F00 AI \u65F6\u8BA9\u6211\u4ECE git log \u62BD\u66F4\u591A\u77E5\u8BC6\u5417?",
1692
+ initialValue: true
1693
+ });
1694
+ if (isCancel(answer) || answer !== true) return;
1695
+ const fabricDir = join5(opts.target, ".fabric");
1696
+ const sentinelPath = join5(fabricDir, ".import-requested");
1697
+ try {
1698
+ if (!existsSync6(fabricDir)) {
1699
+ mkdirSync(fabricDir, { recursive: true });
1700
+ }
1701
+ writeFileSync(sentinelPath, "", "utf8");
1702
+ log.success("\u4E0B\u6B21\u5F00 AI \u4F1A\u770B\u5230\u63D0\u793A");
1703
+ } catch {
1704
+ }
1810
1705
  }
1811
1706
  function resolveInitCliIntent(args, targetInput) {
1812
- const target = normalizeTarget3(targetInput);
1707
+ const target = normalizeTarget2(targetInput);
1813
1708
  const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
1814
1709
  const claudeMcpScope = resolveClaudeMcpScope(args.scope);
1815
1710
  const terminalInteractive = isInteractiveInit();
@@ -1929,20 +1824,20 @@ async function executeInitExecutionPlan(plan) {
1929
1824
  }
1930
1825
  var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
1931
1826
  function resolvePersonalFabricRoot() {
1932
- return process.env.FABRIC_HOME ?? homedir5();
1827
+ return process.env.FABRIC_HOME ?? homedir4();
1933
1828
  }
1934
1829
  async function buildInitFabricPlan(target, options) {
1935
- assertExistingDirectory3(target);
1936
- const fabricDir = join6(target, ".fabric");
1937
- const agentsMdPath = join6(target, "AGENTS.md");
1938
- const agentsMdAction = existsSync8(agentsMdPath) ? "preserved" : "created";
1939
- const knowledgeDir = join6(fabricDir, "knowledge");
1940
- const personalKnowledgeDir = join6(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1941
- const forensicPath = join6(fabricDir, "forensic.json");
1942
- const eventsPath = join6(fabricDir, "events.jsonl");
1943
- const metaPath = join6(fabricDir, "agents.meta.json");
1830
+ assertExistingDirectory2(target);
1831
+ const fabricDir = join5(target, ".fabric");
1832
+ const agentsMdPath = join5(target, "AGENTS.md");
1833
+ const agentsMdAction = existsSync6(agentsMdPath) ? "preserved" : "created";
1834
+ const knowledgeDir = join5(fabricDir, "knowledge");
1835
+ const personalKnowledgeDir = join5(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1836
+ const forensicPath = join5(fabricDir, "forensic.json");
1837
+ const eventsPath = join5(fabricDir, "events.jsonl");
1838
+ const metaPath = join5(fabricDir, "agents.meta.json");
1944
1839
  const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
1945
- const knowledgeDirAction = existsSync8(knowledgeDir) ? "overwritten" : "created";
1840
+ const knowledgeDirAction = existsSync6(knowledgeDir) ? "overwritten" : "created";
1946
1841
  const metaAction = planFreshPath(metaPath, options);
1947
1842
  const eventsAction = planFreshPath(eventsPath, options);
1948
1843
  const forensicAction = planFreshPath(forensicPath, options);
@@ -1974,30 +1869,30 @@ async function executeInitFabricPlan(plan) {
1974
1869
  rmSync(plan.fabricDir, { force: true });
1975
1870
  }
1976
1871
  mkdirSync(plan.fabricDir, { recursive: true });
1977
- if (plan.agentsMdAction === "created" && !existsSync8(plan.agentsMdPath)) {
1872
+ if (plan.agentsMdAction === "created" && !existsSync6(plan.agentsMdPath)) {
1978
1873
  await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
1979
1874
  }
1980
1875
  mkdirSync(plan.knowledgeDir, { recursive: true });
1981
1876
  for (const sub of KNOWLEDGE_SUBDIRS) {
1982
- const teamSubDir = join6(plan.knowledgeDir, sub);
1877
+ const teamSubDir = join5(plan.knowledgeDir, sub);
1983
1878
  mkdirSync(teamSubDir, { recursive: true });
1984
- const teamGitkeep = join6(teamSubDir, ".gitkeep");
1985
- if (!existsSync8(teamGitkeep)) {
1879
+ const teamGitkeep = join5(teamSubDir, ".gitkeep");
1880
+ if (!existsSync6(teamGitkeep)) {
1986
1881
  writeFileSync(teamGitkeep, "", "utf8");
1987
1882
  }
1988
1883
  }
1989
1884
  try {
1990
1885
  mkdirSync(plan.personalKnowledgeDir, { recursive: true });
1991
1886
  for (const sub of KNOWLEDGE_SUBDIRS) {
1992
- mkdirSync(join6(plan.personalKnowledgeDir, sub), { recursive: true });
1887
+ mkdirSync(join5(plan.personalKnowledgeDir, sub), { recursive: true });
1993
1888
  }
1994
1889
  } catch {
1995
1890
  }
1996
1891
  preparePlannedPath(plan.metaPath, plan.metaAction);
1997
- await atomicWriteJson2(plan.metaPath, plan.meta);
1892
+ await atomicWriteJson(plan.metaPath, plan.meta);
1998
1893
  if (isReapply) {
1999
- if (!existsSync8(plan.eventsPath)) {
2000
- mkdirSync(dirname3(plan.eventsPath), { recursive: true });
1894
+ if (!existsSync6(plan.eventsPath)) {
1895
+ mkdirSync(dirname2(plan.eventsPath), { recursive: true });
2001
1896
  writeFileSync(plan.eventsPath, "", "utf8");
2002
1897
  }
2003
1898
  } else {
@@ -2005,7 +1900,7 @@ async function executeInitFabricPlan(plan) {
2005
1900
  writeFileSync(plan.eventsPath, "", "utf8");
2006
1901
  }
2007
1902
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
2008
- await atomicWriteJson2(plan.forensicPath, plan.forensicReport);
1903
+ await atomicWriteJson(plan.forensicPath, plan.forensicReport);
2009
1904
  if (!plan.options?.reapply) {
2010
1905
  try {
2011
1906
  await runInitScan(plan.target, { source: "init" });
@@ -2139,8 +2034,28 @@ async function executeInitStagePlan(plan, stageName) {
2139
2034
  try {
2140
2035
  switch (stage.name) {
2141
2036
  case "bootstrap": {
2142
- console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
2143
- return { name: "bootstrap", disposition: "skipped" };
2037
+ const installResults = [];
2038
+ installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
2039
+ installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
2040
+ installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
2041
+ installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
2042
+ installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
2043
+ installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
2044
+ installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
2045
+ installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
2046
+ installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
2047
+ installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
2048
+ const installedCount = installResults.filter((r) => r.status === "written").length;
2049
+ const skippedCount = installResults.filter((r) => r.status === "skipped").length;
2050
+ const errorCount = installResults.filter((r) => r.status === "error").length;
2051
+ for (const result of installResults) {
2052
+ if (result.status === "error") {
2053
+ writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
2054
+ }
2055
+ }
2056
+ const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
2057
+ console.log(formatInitStageResult("bootstrap", "completed", installedCount, skippedCount, note2));
2058
+ return { name: "bootstrap", disposition: "ran" };
2144
2059
  }
2145
2060
  case "mcp": {
2146
2061
  if (stage.installMode === "local") {
@@ -2178,10 +2093,10 @@ async function executeInitStagePlan(plan, stageName) {
2178
2093
  }
2179
2094
  }
2180
2095
  function shouldReplaceWritableDirectory(path, options) {
2181
- if (!existsSync8(path)) {
2096
+ if (!existsSync6(path)) {
2182
2097
  return false;
2183
2098
  }
2184
- if (statSync3(path).isDirectory()) {
2099
+ if (statSync2(path).isDirectory()) {
2185
2100
  return false;
2186
2101
  }
2187
2102
  if (!options?.force) {
@@ -2190,7 +2105,7 @@ function shouldReplaceWritableDirectory(path, options) {
2190
2105
  return true;
2191
2106
  }
2192
2107
  function planFreshPath(path, options) {
2193
- if (!existsSync8(path)) {
2108
+ if (!existsSync6(path)) {
2194
2109
  return "created";
2195
2110
  }
2196
2111
  if (!options?.force) {
@@ -2199,8 +2114,8 @@ function planFreshPath(path, options) {
2199
2114
  return "overwritten";
2200
2115
  }
2201
2116
  function preparePlannedPath(path, action) {
2202
- mkdirSync(dirname3(path), { recursive: true });
2203
- if (action === "overwritten" && existsSync8(path)) {
2117
+ mkdirSync(dirname2(path), { recursive: true });
2118
+ if (action === "overwritten" && existsSync6(path)) {
2204
2119
  rmSync(path, { recursive: true, force: true });
2205
2120
  }
2206
2121
  }
@@ -2375,23 +2290,23 @@ function formatInitModeBadge(options) {
2375
2290
  }
2376
2291
  return t("cli.init.mode.badge.default");
2377
2292
  }
2378
- function normalizeTarget3(targetInput) {
2379
- return isAbsolute3(targetInput) ? targetInput : resolve7(process.cwd(), targetInput);
2293
+ function normalizeTarget2(targetInput) {
2294
+ return isAbsolute2(targetInput) ? targetInput : resolve5(process.cwd(), targetInput);
2380
2295
  }
2381
- function assertExistingDirectory3(target) {
2382
- if (!existsSync8(target) || !statSync3(target).isDirectory()) {
2296
+ function assertExistingDirectory2(target) {
2297
+ if (!existsSync6(target) || !statSync2(target).isDirectory()) {
2383
2298
  throw new Error(`Target must be an existing directory: ${target}`);
2384
2299
  }
2385
2300
  }
2386
2301
  function detectPackageManager(cwd) {
2387
- const workspaceRoot = resolve7(cwd);
2388
- if (existsSync8(join6(workspaceRoot, "pnpm-lock.yaml"))) {
2302
+ const workspaceRoot = resolve5(cwd);
2303
+ if (existsSync6(join5(workspaceRoot, "pnpm-lock.yaml"))) {
2389
2304
  return "pnpm";
2390
2305
  }
2391
- if (existsSync8(join6(workspaceRoot, "yarn.lock"))) {
2306
+ if (existsSync6(join5(workspaceRoot, "yarn.lock"))) {
2392
2307
  return "yarn";
2393
2308
  }
2394
- if (existsSync8(join6(workspaceRoot, "package-lock.json"))) {
2309
+ if (existsSync6(join5(workspaceRoot, "package-lock.json"))) {
2395
2310
  return "npm";
2396
2311
  }
2397
2312
  return "npm";
@@ -2431,6 +2346,32 @@ function appendReapplyLedgerEvent(eventsPath, payload) {
2431
2346
  `;
2432
2347
  appendFileSync(eventsPath, line, "utf8");
2433
2348
  }
2349
+ async function runBestEffort(step, fn) {
2350
+ try {
2351
+ return await fn();
2352
+ } catch (error) {
2353
+ return [
2354
+ {
2355
+ step,
2356
+ path: "",
2357
+ status: "error",
2358
+ message: error instanceof Error ? error.message : String(error)
2359
+ }
2360
+ ];
2361
+ }
2362
+ }
2363
+ async function runBestEffortSingle(step, fn) {
2364
+ try {
2365
+ return await fn();
2366
+ } catch (error) {
2367
+ return {
2368
+ step,
2369
+ path: "",
2370
+ status: "error",
2371
+ message: error instanceof Error ? error.message : String(error)
2372
+ };
2373
+ }
2374
+ }
2434
2375
  function formatInitStageHeader(message) {
2435
2376
  return `${nextLabel()} ${paint.muted(message)}`;
2436
2377
  }