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

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 (32) hide show
  1. package/README.md +6 -6
  2. package/dist/{chunk-UHNP7T7W.js → chunk-5MQ52F42.js} +347 -86
  3. package/dist/chunk-6ICJICVU.js +10 -0
  4. package/dist/chunk-AW3G7ZH5.js +576 -0
  5. package/dist/chunk-HQLEHH4O.js +321 -0
  6. package/dist/{chunk-5LOYBXWD.js → chunk-OBQU6NHO.js} +2 -52
  7. package/dist/chunk-WPTA74BY.js +184 -0
  8. package/dist/chunk-WWNXR34K.js +49 -0
  9. package/dist/doctor-RILCO5OG.js +282 -0
  10. package/dist/hooks-NX32PPEN.js +13 -0
  11. package/dist/index.js +8 -5
  12. package/dist/{init-DRHUYHYA.js → init-C56PWHID.js} +225 -491
  13. package/dist/plan-context-hint-QMUPAXIB.js +98 -0
  14. package/dist/{scan-HU2EGITF.js → scan-66EKMNAY.js} +6 -2
  15. package/dist/{serve-3LXXSBFR.js → serve-NGLXHDYC.js} +8 -4
  16. package/dist/uninstall-DBAR2JBS.js +1082 -0
  17. package/package.json +3 -3
  18. package/templates/bootstrap/CLAUDE.md +1 -1
  19. package/templates/bootstrap/codex-AGENTS-header.md +1 -1
  20. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +1 -1
  21. package/templates/hooks/configs/README.md +73 -0
  22. package/templates/hooks/configs/claude-code.json +37 -0
  23. package/templates/hooks/configs/codex-hooks.json +20 -0
  24. package/templates/hooks/configs/cursor-hooks.json +20 -0
  25. package/templates/hooks/fabric-hint.cjs +1337 -0
  26. package/templates/hooks/knowledge-hint-broad.cjs +612 -0
  27. package/templates/hooks/knowledge-hint-narrow.cjs +826 -0
  28. package/templates/hooks/lib/session-digest-writer.cjs +172 -0
  29. package/templates/skills/fabric-archive/SKILL.md +640 -0
  30. package/templates/skills/fabric-import/SKILL.md +850 -0
  31. package/templates/skills/fabric-review/SKILL.md +717 -0
  32. package/dist/doctor-DUHWLAYD.js +0 -98
@@ -1,447 +1,60 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ hooksCommand,
4
+ installHooks
5
+ } from "./chunk-WPTA74BY.js";
6
+ import {
7
+ detectExistingLanguage,
3
8
  detectFramework,
4
9
  runInitScan
5
- } from "./chunk-UHNP7T7W.js";
10
+ } from "./chunk-5MQ52F42.js";
11
+ import {
12
+ detectClientSupports,
13
+ resolveClients
14
+ } from "./chunk-HQLEHH4O.js";
15
+ import {
16
+ addArchiveSkillPointer,
17
+ installArchiveHintHook,
18
+ installFabricArchiveSkill,
19
+ installFabricImportSkill,
20
+ installFabricReviewSkill,
21
+ installKnowledgeHintBroadHook,
22
+ installKnowledgeHintNarrowHook,
23
+ mergeClaudeCodeHookConfig,
24
+ mergeCodexHookConfig,
25
+ mergeCursorHookConfig
26
+ } from "./chunk-AW3G7ZH5.js";
6
27
  import {
7
- createDebugLogger,
8
28
  displayWidth,
9
29
  padEnd,
10
- paint,
11
- resolveDevMode,
30
+ paint
31
+ } from "./chunk-WWNXR34K.js";
32
+ import {
33
+ createDebugLogger,
34
+ resolveDevMode
35
+ } from "./chunk-OBQU6NHO.js";
36
+ import {
12
37
  t
13
- } from "./chunk-5LOYBXWD.js";
38
+ } from "./chunk-6ICJICVU.js";
14
39
 
15
40
  // src/commands/init.ts
16
41
  import { randomUUID } from "crypto";
17
- import { homedir as homedir5 } from "os";
42
+ import { homedir } from "os";
18
43
  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";
44
+ import { appendFileSync, existsSync as existsSync3, mkdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
45
+ import { dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve3 } from "path";
21
46
  import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
22
47
  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";
48
+ import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
49
+ import { defineCommand as defineCommand2 } from "citty";
25
50
  import { checkLockOrThrow } from "@fenglimg/fabric-server";
26
51
 
27
52
  // 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";
31
- import { fileURLToPath } from "url";
32
- import { defineCommand as defineCommand2 } from "citty";
33
-
34
- // 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";
38
-
39
- // 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
53
  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
161
- function getClaudeDesktopConfigPath() {
162
- const os = platform();
163
- if (os === "darwin") {
164
- return join2(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
165
- }
166
- if (os === "win32") {
167
- return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
168
- }
169
- return join2(homedir2(), ".config", "Claude", "claude_desktop_config.json");
170
- }
171
- var ClaudeCodeDesktopWriter = class {
172
- clientKind = "ClaudeCodeDesktop";
173
- configuredPath;
174
- constructor(configuredPath) {
175
- this.configuredPath = configuredPath;
176
- }
177
- async detect(_workspaceRoot, overridePath) {
178
- const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
179
- return existsSync2(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
180
- }
181
- async write(serverPath, workspaceRoot, overridePath) {
182
- const configPath = await this.detect(workspaceRoot, overridePath);
183
- if (configPath === null) {
184
- return;
185
- }
186
- await writeJsonClientConfig(configPath, {
187
- command: process.execPath,
188
- args: [serverPath]
189
- });
190
- }
191
- };
192
-
193
- // 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";
198
- import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
199
- function expandHome2(filePath) {
200
- if (filePath === "~") {
201
- return homedir3();
202
- }
203
- if (filePath.startsWith("~/")) {
204
- return join3(homedir3(), filePath.slice(2));
205
- }
206
- return filePath;
207
- }
208
- function escapeTomlString(value) {
209
- return JSON.stringify(value);
210
- }
211
- function serializeTomlStringArray(values) {
212
- return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
213
- }
214
- function serializeTomlInlineTable(values) {
215
- const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
216
- return `{ ${entries.join(", ")} }`;
217
- }
218
- function serializeCodexServerBlock(serverName, serverEntry) {
219
- const lines = [
220
- `[mcp_servers.${serverName}]`,
221
- `command = ${escapeTomlString(serverEntry.command)}`,
222
- `args = ${serializeTomlStringArray(serverEntry.args)}`
223
- ];
224
- if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
225
- lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
226
- }
227
- return `${lines.join("\n")}
228
- `;
229
- }
230
- function trimTrailingBlankLines(value) {
231
- return value.replace(/\s+$/u, "");
232
- }
233
- function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
234
- const block = serializeCodexServerBlock(serverName, serverEntry);
235
- const normalized = rawConfig.replace(/\r\n/g, "\n");
236
- const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
237
- const currentPattern = new RegExp(
238
- String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
239
- "g"
240
- );
241
- const withoutLegacy = normalized.replace(legacyPattern, "");
242
- const withoutExisting = withoutLegacy.replace(currentPattern, "");
243
- const trimmed = trimTrailingBlankLines(withoutExisting);
244
- if (trimmed.length === 0) {
245
- return block;
246
- }
247
- return `${trimmed}
248
-
249
- ${block}`;
250
- }
251
- async function readTomlConfigText(configPath) {
252
- try {
253
- return await readFile2(configPath, "utf8");
254
- } catch (error) {
255
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
256
- return "";
257
- }
258
- throw error;
259
- }
260
- }
261
- var CodexTOMLConfigWriter = class {
262
- clientKind = "CodexCLI";
263
- configuredPath;
264
- constructor(configuredPath) {
265
- this.configuredPath = configuredPath;
266
- }
267
- async detect(_workspaceRoot, overridePath) {
268
- const explicitPath = overridePath ?? this.configuredPath;
269
- if (explicitPath !== void 0) {
270
- return resolve3(expandHome2(explicitPath));
271
- }
272
- const codexDir = join3(homedir3(), ".codex");
273
- return existsSync3(codexDir) ? resolve3(join3(codexDir, "config.toml")) : null;
274
- }
275
- async write(serverPath, workspaceRoot, overridePath) {
276
- const configPath = await this.detect(workspaceRoot, overridePath);
277
- if (configPath === null) {
278
- return;
279
- }
280
- const rawConfig = await readTomlConfigText(configPath);
281
- const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
282
- await mkdir2(dirname2(configPath), { recursive: true });
283
- await atomicWriteText(configPath, nextConfig);
284
- }
285
- };
286
-
287
- // src/config/resolver.ts
288
- import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
289
- function hasExplicitPath(clientPaths, key) {
290
- return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
291
- }
292
- function addIfDetected(writers, detected, createWriter, configuredPath) {
293
- if (configuredPath !== void 0 || detected) {
294
- writers.push(createWriter(configuredPath));
295
- }
296
- }
297
- function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
298
- const clientPaths = fabricConfig.clientPaths;
299
- const writers = [];
300
- const claudeMcpScope = opts.claudeMcpScope ?? "project";
301
- addIfDetected(
302
- writers,
303
- existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude")),
304
- (configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
305
- hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
306
- );
307
- addIfDetected(
308
- writers,
309
- existsSync4(getClaudeDesktopConfigPath()),
310
- (configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
311
- hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
312
- );
313
- addIfDetected(
314
- writers,
315
- existsSync4(join4(workspaceRoot, ".cursor")),
316
- (configuredPath) => new CursorWriter(configuredPath),
317
- hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
318
- );
319
- addIfDetected(
320
- writers,
321
- existsSync4(join4(homedir4(), ".codex")),
322
- (configuredPath) => new CodexTOMLConfigWriter(configuredPath),
323
- hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
324
- );
325
- return writers;
326
- }
327
- function detectClientSupports(workspaceRoot, fabricConfig = {}) {
328
- 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"));
333
- return [
334
- {
335
- clientKind: "ClaudeCodeCLI",
336
- label: "Claude Code CLI",
337
- detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
338
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
339
- configPath: "project .claude/settings.json",
340
- capabilities: {
341
- bootstrap: true,
342
- mcp: true,
343
- hook: true,
344
- skill: true
345
- },
346
- installedCapabilities: {
347
- hook: true,
348
- skill: true
349
- }
350
- },
351
- {
352
- clientKind: "ClaudeCodeDesktop",
353
- label: "Claude Code Desktop",
354
- detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
355
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
356
- configPath: "desktop Claude config",
357
- capabilities: {
358
- bootstrap: true,
359
- mcp: true,
360
- hook: false,
361
- skill: false
362
- }
363
- },
364
- {
365
- clientKind: "Cursor",
366
- label: "Cursor",
367
- detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
368
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
369
- configPath: ".cursor/mcp.json",
370
- capabilities: {
371
- bootstrap: true,
372
- mcp: true,
373
- hook: false,
374
- skill: false
375
- }
376
- },
377
- {
378
- clientKind: "CodexCLI",
379
- label: "Codex CLI",
380
- detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
381
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
382
- configPath: "~/.codex/config.toml",
383
- capabilities: {
384
- bootstrap: true,
385
- mcp: true,
386
- hook: true,
387
- skill: true
388
- },
389
- installedCapabilities: {
390
- hook: existsSync4(join4(workspaceRoot, ".codex", "hooks.json")),
391
- // v2/rc.2: v1 client-side init skill removed; skill-installation probes
392
- // will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
393
- // fabric-review, fabric-import). Until then there is nothing to probe.
394
- skill: false
395
- }
396
- }
397
- ];
398
- }
399
-
400
- // src/commands/hooks.ts
401
- import { existsSync as existsSync5, statSync } from "fs";
402
- import { isAbsolute, resolve as resolve4 } from "path";
54
+ import { readFile } from "fs/promises";
55
+ import { resolve } from "path";
56
+ import { fileURLToPath } from "url";
403
57
  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
- // src/commands/config.ts
445
58
  var CLIENT_ALIASES = {
446
59
  claude: "ClaudeCodeCLI",
447
60
  claudecodecli: "ClaudeCodeCLI",
@@ -471,11 +84,11 @@ function parseClientFilter(value) {
471
84
  return clients;
472
85
  }
473
86
  async function loadFabricConfig(workspaceRoot) {
474
- const configPath = resolve5(workspaceRoot, "fabric.config.json");
475
- if (!existsSync6(configPath)) {
87
+ const configPath = resolve(workspaceRoot, "fabric.config.json");
88
+ if (!existsSync(configPath)) {
476
89
  return {};
477
90
  }
478
- const parsed = JSON.parse(await readFile3(configPath, "utf8"));
91
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
479
92
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
480
93
  throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
481
94
  }
@@ -483,21 +96,21 @@ async function loadFabricConfig(workspaceRoot) {
483
96
  }
484
97
  function resolveServerPath(override) {
485
98
  if (override) return override;
486
- if (process.env.FAB_SERVER_PATH) return resolve5(process.env.FAB_SERVER_PATH);
99
+ if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
487
100
  return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
488
101
  }
489
102
  function writeStderr(message) {
490
103
  process.stderr.write(`${message}
491
104
  `);
492
105
  }
493
- var configCmd = defineCommand2({
106
+ var configCmd = defineCommand({
494
107
  meta: {
495
108
  name: "config",
496
109
  description: t("cli.config.description")
497
110
  },
498
111
  subCommands: {
499
112
  hooks: hooksCommand,
500
- install: defineCommand2({
113
+ install: defineCommand({
501
114
  meta: {
502
115
  name: "install",
503
116
  description: t("cli.config.install.description")
@@ -541,7 +154,7 @@ var configCmd = defineCommand2({
541
154
  }
542
155
  });
543
156
  async function installMcpClients(target, options = {}) {
544
- const workspaceRoot = resolve5(target);
157
+ const workspaceRoot = resolve(target);
545
158
  const fabricConfig = await loadFabricConfig(workspaceRoot);
546
159
  const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
547
160
  const serverPath = resolveServerPath(options.localServerPath);
@@ -572,9 +185,9 @@ async function installMcpClients(target, options = {}) {
572
185
 
573
186
  // src/scanner/forensic.ts
574
187
  import { execFileSync } from "child_process";
575
- import { existsSync as existsSync7, readdirSync, readFileSync, statSync as statSync2 } from "fs";
188
+ import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
576
189
  import { createRequire } from "module";
577
- import { basename, extname, isAbsolute as isAbsolute2, join as join5, posix, relative, resolve as resolve6, sep } from "path";
190
+ import { basename, extname, isAbsolute, join, posix, relative, resolve as resolve2, sep } from "path";
578
191
  import {
579
192
  forensicReportSchema
580
193
  } from "@fenglimg/fabric-shared";
@@ -660,7 +273,7 @@ var parserInitPromise = null;
660
273
  var languagePromiseByKind = {};
661
274
  var parserBundlePromiseByKind = {};
662
275
  async function buildForensicReport(targetInput) {
663
- const target = normalizeTarget2(targetInput);
276
+ const target = normalizeTarget(targetInput);
664
277
  const framework = detectFramework(target);
665
278
  const topology = buildTopology(target);
666
279
  const entryPoints = collectEntryPoints(target, topology.files);
@@ -696,11 +309,11 @@ async function buildForensicReport(targetInput) {
696
309
  }
697
310
  return validation.data;
698
311
  }
699
- function normalizeTarget2(targetInput) {
700
- return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
312
+ function normalizeTarget(targetInput) {
313
+ return isAbsolute(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
701
314
  }
702
315
  function buildTopology(root) {
703
- assertExistingDirectory2(root);
316
+ assertExistingDirectory(root);
704
317
  const byExt = {};
705
318
  const keyDirs = /* @__PURE__ */ new Set();
706
319
  const files = [];
@@ -713,7 +326,7 @@ function buildTopology(root) {
713
326
  continue;
714
327
  }
715
328
  for (const entry of readdirSync(current, { withFileTypes: true })) {
716
- const absolutePath = join5(current, entry.name);
329
+ const absolutePath = join(current, entry.name);
717
330
  const relativePath = toPosixPath(relative(root, absolutePath));
718
331
  if (relativePath.length === 0) {
719
332
  continue;
@@ -733,7 +346,7 @@ function buildTopology(root) {
733
346
  if (!entry.isFile()) {
734
347
  continue;
735
348
  }
736
- const stats = statSync2(absolutePath);
349
+ const stats = statSync(absolutePath);
737
350
  const extension = extname(entry.name) || "[none]";
738
351
  byExt[extension] = (byExt[extension] ?? 0) + 1;
739
352
  totalFiles += 1;
@@ -751,8 +364,8 @@ function buildTopology(root) {
751
364
  files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
752
365
  };
753
366
  }
754
- function assertExistingDirectory2(target) {
755
- if (!existsSync7(target) || !statSync2(target).isDirectory()) {
367
+ function assertExistingDirectory(target) {
368
+ if (!existsSync2(target) || !statSync(target).isDirectory()) {
756
369
  throw new Error(`Target must be an existing directory: ${target}`);
757
370
  }
758
371
  }
@@ -801,7 +414,7 @@ function getEntryPointReason(relativePath) {
801
414
  async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
802
415
  const samples = [];
803
416
  for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
804
- const absolutePath = join5(target, ...entryPoint.path.split("/"));
417
+ const absolutePath = join(target, ...entryPoint.path.split("/"));
805
418
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
806
419
  const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
807
420
  frameworkKind,
@@ -838,8 +451,8 @@ function readFirstLines(path, lineLimit) {
838
451
  }
839
452
  }
840
453
  function readPackageDependencies(target) {
841
- const packageJsonPath = join5(target, "package.json");
842
- if (!existsSync7(packageJsonPath)) {
454
+ const packageJsonPath = join(target, "package.json");
455
+ if (!existsSync2(packageJsonPath)) {
843
456
  return /* @__PURE__ */ new Map();
844
457
  }
845
458
  try {
@@ -1179,9 +792,9 @@ function scoreFrameworkConfidence(input) {
1179
792
  return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
1180
793
  }
1181
794
  function readReadmeInfo(target) {
1182
- const readmePath = join5(target, "README.md");
1183
- const hasContributing = existsSync7(join5(target, "CONTRIBUTING.md"));
1184
- if (!existsSync7(readmePath)) {
795
+ const readmePath = join(target, "README.md");
796
+ const hasContributing = existsSync2(join(target, "CONTRIBUTING.md"));
797
+ if (!existsSync2(readmePath)) {
1185
798
  return {
1186
799
  quality: "missing",
1187
800
  line_count: 0,
@@ -1666,8 +1279,8 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
1666
1279
  return recommendations;
1667
1280
  }
1668
1281
  function readProjectName(target) {
1669
- const packageJsonPath = join5(target, "package.json");
1670
- if (existsSync7(packageJsonPath)) {
1282
+ const packageJsonPath = join(target, "package.json");
1283
+ if (existsSync2(packageJsonPath)) {
1671
1284
  try {
1672
1285
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1673
1286
  if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
@@ -1680,7 +1293,7 @@ function readProjectName(target) {
1680
1293
  return basename(target);
1681
1294
  }
1682
1295
  function getCliVersion() {
1683
- return true ? "2.0.0-rc.1" : "unknown";
1296
+ return true ? "2.0.0-rc.11" : "unknown";
1684
1297
  }
1685
1298
  function sortRecord(record) {
1686
1299
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1699,10 +1312,10 @@ Run \`fabric doctor\` to verify state.
1699
1312
 
1700
1313
  See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
1701
1314
  `;
1702
- var LOCAL_FABRIC_SERVER_PATH = join6("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1315
+ var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1703
1316
  var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
1704
1317
  var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
1705
- var initCommand = defineCommand3({
1318
+ var initCommand = defineCommand2({
1706
1319
  meta: {
1707
1320
  name: "init",
1708
1321
  description: t("cli.init.description")
@@ -1806,10 +1419,83 @@ async function runInitCommand(args) {
1806
1419
  process.exitCode = 130;
1807
1420
  return;
1808
1421
  }
1809
- return executeInitExecutionPlan(plan);
1422
+ const result = await executeInitExecutionPlan(plan);
1423
+ if (!intent.options.planOnly) {
1424
+ console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
1425
+ }
1426
+ return result;
1427
+ }
1428
+ function writeDefaultFabricConfig(fabricDir, targetRoot) {
1429
+ const target = join2(fabricDir, "fabric-config.json");
1430
+ if (existsSync3(target)) return;
1431
+ const detectedLanguage = detectExistingLanguage(targetRoot);
1432
+ const FABRIC_CONFIG_DEFAULTS = {
1433
+ // Scan/import language policy. Fixated at init time by probing
1434
+ // README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
1435
+ // can edit `.fabric/fabric-config.json` to override. See
1436
+ // packages/shared/src/schemas/fabric-config.ts for the enum.
1437
+ knowledge_language: detectedLanguage,
1438
+ // fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
1439
+ // since last knowledge_proposed event.
1440
+ archive_hint_hours: 24,
1441
+ // fabric-hint Stop hook cooldown after ANY signal fires, in hours.
1442
+ archive_hint_cooldown_hours: 12,
1443
+ // fabric-hint Stop hook Signal B (review): pending-count cutoff.
1444
+ review_hint_pending_count: 10,
1445
+ // fabric-hint Stop hook Signal B (review): pending-age cutoff in days.
1446
+ review_hint_pending_age_days: 7,
1447
+ // fabric-hint Stop hook Signal D (maintenance): days since last doctor.
1448
+ maintenance_hint_days: 14,
1449
+ // fabric-hint Stop hook Signal D (maintenance): cooldown between
1450
+ // reminders, in days.
1451
+ maintenance_hint_cooldown_days: 7,
1452
+ // fabric-hint Stop hook Signal A (archive): edit-count branch threshold;
1453
+ // PreToolUse fires recorded in .fabric/.cache/edit-counter since the
1454
+ // last knowledge_proposed event.
1455
+ archive_edit_threshold: 20,
1456
+ // fabric-hint Stop hook Signal C (import) + doctor lint #22: canonical
1457
+ // knowledge node count below this value flags an underseeded workspace.
1458
+ underseed_node_threshold: 10,
1459
+ // rc.9+ (skill-contract-fix B1): fabric-import first-run git-history
1460
+ // window in months. Default 60 captures the bulk of a mature repo's
1461
+ // signal in one pass; lower to 12-24 for fresh / small repos.
1462
+ import_window_first_run_months: 60,
1463
+ // rc.9+ (skill-contract-fix B1): fabric-import rerun window in months.
1464
+ // Default 2; raise to 6 if the workspace pauses imports for long stretches.
1465
+ import_window_rerun_months: 2,
1466
+ // rc.9+ (skill-contract-fix B1): hard cap on pending entries produced
1467
+ // per fabric-import invocation. Default 10 matches one-sitting triage.
1468
+ import_max_pending_per_run: 10,
1469
+ // rc.9+ (skill-contract-fix B1): hard cap on commits scanned per
1470
+ // fabric-import invocation. Default 500 covers ~2 months of typical churn.
1471
+ import_max_commits_scan: 500,
1472
+ // rc.9+ (skill-contract-fix B1): canonical-node count above which
1473
+ // fabric-import suggests review over importing more. Default 50.
1474
+ import_skip_canonical_threshold: 50,
1475
+ // rc.9+ (skill-contract-fix B1): max candidates per fabric-archive batch.
1476
+ // Default 8 keeps each batch reviewable in one sitting.
1477
+ archive_max_candidates_per_batch: 8,
1478
+ // rc.9+ (skill-contract-fix B1): max recently-touched paths in
1479
+ // fabric-archive's relevance digest. Default 20.
1480
+ archive_max_recent_paths: 20,
1481
+ // rc.9+ (skill-contract-fix B1): max prior fabric-archive sessions
1482
+ // summarised in the digest the skill loads on start. Default 10.
1483
+ archive_digest_max_sessions: 10,
1484
+ // rc.9+ (skill-contract-fix B1): max review results per topic cluster
1485
+ // in fabric-review. Default 8.
1486
+ review_topic_result_cap: 8,
1487
+ // rc.9+ (skill-contract-fix B1): age (days) above which a pending entry
1488
+ // is considered stale by fabric-review. Default 14.
1489
+ review_stale_pending_days: 14
1490
+ };
1491
+ mkdirSync(fabricDir, { recursive: true });
1492
+ writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
1493
+ log.info(
1494
+ `Detected and fixated knowledge_language = ${detectedLanguage}; edit ${target} to override.`
1495
+ );
1810
1496
  }
1811
1497
  function resolveInitCliIntent(args, targetInput) {
1812
- const target = normalizeTarget3(targetInput);
1498
+ const target = normalizeTarget2(targetInput);
1813
1499
  const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
1814
1500
  const claudeMcpScope = resolveClaudeMcpScope(args.scope);
1815
1501
  const terminalInteractive = isInteractiveInit();
@@ -1929,20 +1615,20 @@ async function executeInitExecutionPlan(plan) {
1929
1615
  }
1930
1616
  var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
1931
1617
  function resolvePersonalFabricRoot() {
1932
- return process.env.FABRIC_HOME ?? homedir5();
1618
+ return process.env.FABRIC_HOME ?? homedir();
1933
1619
  }
1934
1620
  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");
1621
+ assertExistingDirectory2(target);
1622
+ const fabricDir = join2(target, ".fabric");
1623
+ const agentsMdPath = join2(target, "AGENTS.md");
1624
+ const agentsMdAction = existsSync3(agentsMdPath) ? "preserved" : "created";
1625
+ const knowledgeDir = join2(fabricDir, "knowledge");
1626
+ const personalKnowledgeDir = join2(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1627
+ const forensicPath = join2(fabricDir, "forensic.json");
1628
+ const eventsPath = join2(fabricDir, "events.jsonl");
1629
+ const metaPath = join2(fabricDir, "agents.meta.json");
1944
1630
  const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
1945
- const knowledgeDirAction = existsSync8(knowledgeDir) ? "overwritten" : "created";
1631
+ const knowledgeDirAction = existsSync3(knowledgeDir) ? "overwritten" : "created";
1946
1632
  const metaAction = planFreshPath(metaPath, options);
1947
1633
  const eventsAction = planFreshPath(eventsPath, options);
1948
1634
  const forensicAction = planFreshPath(forensicPath, options);
@@ -1974,30 +1660,31 @@ async function executeInitFabricPlan(plan) {
1974
1660
  rmSync(plan.fabricDir, { force: true });
1975
1661
  }
1976
1662
  mkdirSync(plan.fabricDir, { recursive: true });
1977
- if (plan.agentsMdAction === "created" && !existsSync8(plan.agentsMdPath)) {
1978
- await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
1663
+ writeDefaultFabricConfig(plan.fabricDir, plan.target);
1664
+ if (plan.agentsMdAction === "created" && !existsSync3(plan.agentsMdPath)) {
1665
+ await atomicWriteText(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
1979
1666
  }
1980
1667
  mkdirSync(plan.knowledgeDir, { recursive: true });
1981
1668
  for (const sub of KNOWLEDGE_SUBDIRS) {
1982
- const teamSubDir = join6(plan.knowledgeDir, sub);
1669
+ const teamSubDir = join2(plan.knowledgeDir, sub);
1983
1670
  mkdirSync(teamSubDir, { recursive: true });
1984
- const teamGitkeep = join6(teamSubDir, ".gitkeep");
1985
- if (!existsSync8(teamGitkeep)) {
1671
+ const teamGitkeep = join2(teamSubDir, ".gitkeep");
1672
+ if (!existsSync3(teamGitkeep)) {
1986
1673
  writeFileSync(teamGitkeep, "", "utf8");
1987
1674
  }
1988
1675
  }
1989
1676
  try {
1990
1677
  mkdirSync(plan.personalKnowledgeDir, { recursive: true });
1991
1678
  for (const sub of KNOWLEDGE_SUBDIRS) {
1992
- mkdirSync(join6(plan.personalKnowledgeDir, sub), { recursive: true });
1679
+ mkdirSync(join2(plan.personalKnowledgeDir, sub), { recursive: true });
1993
1680
  }
1994
1681
  } catch {
1995
1682
  }
1996
1683
  preparePlannedPath(plan.metaPath, plan.metaAction);
1997
- await atomicWriteJson2(plan.metaPath, plan.meta);
1684
+ await atomicWriteJson(plan.metaPath, plan.meta);
1998
1685
  if (isReapply) {
1999
- if (!existsSync8(plan.eventsPath)) {
2000
- mkdirSync(dirname3(plan.eventsPath), { recursive: true });
1686
+ if (!existsSync3(plan.eventsPath)) {
1687
+ mkdirSync(dirname(plan.eventsPath), { recursive: true });
2001
1688
  writeFileSync(plan.eventsPath, "", "utf8");
2002
1689
  }
2003
1690
  } else {
@@ -2005,7 +1692,7 @@ async function executeInitFabricPlan(plan) {
2005
1692
  writeFileSync(plan.eventsPath, "", "utf8");
2006
1693
  }
2007
1694
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
2008
- await atomicWriteJson2(plan.forensicPath, plan.forensicReport);
1695
+ await atomicWriteJson(plan.forensicPath, plan.forensicReport);
2009
1696
  if (!plan.options?.reapply) {
2010
1697
  try {
2011
1698
  await runInitScan(plan.target, { source: "init" });
@@ -2139,8 +1826,28 @@ async function executeInitStagePlan(plan, stageName) {
2139
1826
  try {
2140
1827
  switch (stage.name) {
2141
1828
  case "bootstrap": {
2142
- console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
2143
- return { name: "bootstrap", disposition: "skipped" };
1829
+ const installResults = [];
1830
+ installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
1831
+ installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
1832
+ installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
1833
+ installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
1834
+ installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
1835
+ installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
1836
+ installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
1837
+ installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
1838
+ installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
1839
+ installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
1840
+ const installedCount = installResults.filter((r) => r.status === "written").length;
1841
+ const skippedCount = installResults.filter((r) => r.status === "skipped").length;
1842
+ const errorCount = installResults.filter((r) => r.status === "error").length;
1843
+ for (const result of installResults) {
1844
+ if (result.status === "error") {
1845
+ writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
1846
+ }
1847
+ }
1848
+ const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
1849
+ console.log(formatInitStageResult("bootstrap", "completed", installedCount, skippedCount, note2));
1850
+ return { name: "bootstrap", disposition: "ran" };
2144
1851
  }
2145
1852
  case "mcp": {
2146
1853
  if (stage.installMode === "local") {
@@ -2178,10 +1885,10 @@ async function executeInitStagePlan(plan, stageName) {
2178
1885
  }
2179
1886
  }
2180
1887
  function shouldReplaceWritableDirectory(path, options) {
2181
- if (!existsSync8(path)) {
1888
+ if (!existsSync3(path)) {
2182
1889
  return false;
2183
1890
  }
2184
- if (statSync3(path).isDirectory()) {
1891
+ if (statSync2(path).isDirectory()) {
2185
1892
  return false;
2186
1893
  }
2187
1894
  if (!options?.force) {
@@ -2190,7 +1897,7 @@ function shouldReplaceWritableDirectory(path, options) {
2190
1897
  return true;
2191
1898
  }
2192
1899
  function planFreshPath(path, options) {
2193
- if (!existsSync8(path)) {
1900
+ if (!existsSync3(path)) {
2194
1901
  return "created";
2195
1902
  }
2196
1903
  if (!options?.force) {
@@ -2199,8 +1906,8 @@ function planFreshPath(path, options) {
2199
1906
  return "overwritten";
2200
1907
  }
2201
1908
  function preparePlannedPath(path, action) {
2202
- mkdirSync(dirname3(path), { recursive: true });
2203
- if (action === "overwritten" && existsSync8(path)) {
1909
+ mkdirSync(dirname(path), { recursive: true });
1910
+ if (action === "overwritten" && existsSync3(path)) {
2204
1911
  rmSync(path, { recursive: true, force: true });
2205
1912
  }
2206
1913
  }
@@ -2375,23 +2082,23 @@ function formatInitModeBadge(options) {
2375
2082
  }
2376
2083
  return t("cli.init.mode.badge.default");
2377
2084
  }
2378
- function normalizeTarget3(targetInput) {
2379
- return isAbsolute3(targetInput) ? targetInput : resolve7(process.cwd(), targetInput);
2085
+ function normalizeTarget2(targetInput) {
2086
+ return isAbsolute2(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
2380
2087
  }
2381
- function assertExistingDirectory3(target) {
2382
- if (!existsSync8(target) || !statSync3(target).isDirectory()) {
2088
+ function assertExistingDirectory2(target) {
2089
+ if (!existsSync3(target) || !statSync2(target).isDirectory()) {
2383
2090
  throw new Error(`Target must be an existing directory: ${target}`);
2384
2091
  }
2385
2092
  }
2386
2093
  function detectPackageManager(cwd) {
2387
- const workspaceRoot = resolve7(cwd);
2388
- if (existsSync8(join6(workspaceRoot, "pnpm-lock.yaml"))) {
2094
+ const workspaceRoot = resolve3(cwd);
2095
+ if (existsSync3(join2(workspaceRoot, "pnpm-lock.yaml"))) {
2389
2096
  return "pnpm";
2390
2097
  }
2391
- if (existsSync8(join6(workspaceRoot, "yarn.lock"))) {
2098
+ if (existsSync3(join2(workspaceRoot, "yarn.lock"))) {
2392
2099
  return "yarn";
2393
2100
  }
2394
- if (existsSync8(join6(workspaceRoot, "package-lock.json"))) {
2101
+ if (existsSync3(join2(workspaceRoot, "package-lock.json"))) {
2395
2102
  return "npm";
2396
2103
  }
2397
2104
  return "npm";
@@ -2431,6 +2138,32 @@ function appendReapplyLedgerEvent(eventsPath, payload) {
2431
2138
  `;
2432
2139
  appendFileSync(eventsPath, line, "utf8");
2433
2140
  }
2141
+ async function runBestEffort(step, fn) {
2142
+ try {
2143
+ return await fn();
2144
+ } catch (error) {
2145
+ return [
2146
+ {
2147
+ step,
2148
+ path: "",
2149
+ status: "error",
2150
+ message: error instanceof Error ? error.message : String(error)
2151
+ }
2152
+ ];
2153
+ }
2154
+ }
2155
+ async function runBestEffortSingle(step, fn) {
2156
+ try {
2157
+ return await fn();
2158
+ } catch (error) {
2159
+ return {
2160
+ step,
2161
+ path: "",
2162
+ status: "error",
2163
+ message: error instanceof Error ? error.message : String(error)
2164
+ };
2165
+ }
2166
+ }
2434
2167
  function formatInitStageHeader(message) {
2435
2168
  return `${nextLabel()} ${paint.muted(message)}`;
2436
2169
  }
@@ -2485,6 +2218,7 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2485
2218
  console.log(` - ${target}/.fabric/agents.meta.json`);
2486
2219
  console.log(` - ${target}/.fabric/events.jsonl`);
2487
2220
  console.log(` - ${target}/.fabric/forensic.json`);
2221
+ console.log(` - ${target}/.fabric/fabric-config.json`);
2488
2222
  }
2489
2223
  function printInitCapabilitySummary(supports, stageResults, options) {
2490
2224
  const detected = supports.filter((support) => support.detected);