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

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,19 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- hooksCommand,
4
- installHooks
5
- } from "./chunk-WPTA74BY.js";
3
+ installMcpClients
4
+ } from "./chunk-AIB54QRT.js";
6
5
  import {
7
6
  detectExistingLanguage,
8
- detectFramework,
9
7
  runInitScan
10
- } from "./chunk-5MQ52F42.js";
8
+ } from "./chunk-AXIFEVAS.js";
11
9
  import {
12
- detectClientSupports,
13
- resolveClients
14
- } from "./chunk-HQLEHH4O.js";
15
- import {
16
- addArchiveSkillPointer,
10
+ addFabricKnowledgeBaseSection,
17
11
  installArchiveHintHook,
18
12
  installFabricArchiveSkill,
19
13
  installFabricImportSkill,
@@ -22,175 +16,180 @@ import {
22
16
  installKnowledgeHintNarrowHook,
23
17
  mergeClaudeCodeHookConfig,
24
18
  mergeCodexHookConfig,
25
- mergeCursorHookConfig
26
- } from "./chunk-AW3G7ZH5.js";
19
+ mergeCursorHookConfig,
20
+ readFabricLanguagePreference
21
+ } from "./chunk-UTF4YBDN.js";
22
+ import {
23
+ detectClientSupports
24
+ } from "./chunk-SKSYUHKK.js";
27
25
  import {
28
26
  displayWidth,
27
+ hasActionHint,
29
28
  padEnd,
30
- paint
31
- } from "./chunk-WWNXR34K.js";
29
+ paint,
30
+ renderFabricError
31
+ } from "./chunk-G2CIOLD4.js";
32
+ import {
33
+ t
34
+ } from "./chunk-6ICJICVU.js";
32
35
  import {
33
36
  createDebugLogger,
34
37
  resolveDevMode
35
38
  } from "./chunk-OBQU6NHO.js";
36
- import {
37
- t
38
- } from "./chunk-6ICJICVU.js";
39
39
 
40
- // src/commands/init.ts
40
+ // src/commands/install.ts
41
41
  import { randomUUID } from "crypto";
42
42
  import { homedir } from "os";
43
43
  import * as childProcess from "child_process";
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";
44
+ import { appendFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync, statSync as statSync3, writeFileSync } from "fs";
45
+ import { dirname, isAbsolute as isAbsolute3, join as join3, resolve as resolve3 } from "path";
46
46
  import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
47
47
  import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
48
48
  import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
49
- import { defineCommand as defineCommand2 } from "citty";
49
+ import { defineCommand } from "citty";
50
50
  import { checkLockOrThrow } from "@fenglimg/fabric-server";
51
51
 
52
- // src/commands/config.ts
53
- import { existsSync } from "fs";
54
- import { readFile } from "fs/promises";
55
- import { resolve } from "path";
56
- import { fileURLToPath } from "url";
57
- import { defineCommand } from "citty";
58
- var CLIENT_ALIASES = {
59
- claude: "ClaudeCodeCLI",
60
- claudecodecli: "ClaudeCodeCLI",
61
- "claude-code-cli": "ClaudeCodeCLI",
62
- claudecli: "ClaudeCodeCLI",
63
- claudecodedesktop: "ClaudeCodeDesktop",
64
- "claude-code-desktop": "ClaudeCodeDesktop",
65
- claudedesktop: "ClaudeCodeDesktop",
66
- cursor: "Cursor",
67
- codexcli: "CodexCLI",
68
- "codex-cli": "CodexCLI",
69
- codex: "CodexCLI"
70
- };
71
- function parseClientFilter(value) {
72
- if (value === void 0 || value.trim().length === 0) {
73
- return null;
74
- }
75
- const clients = /* @__PURE__ */ new Set();
76
- for (const rawClient of value.split(",")) {
77
- const alias = rawClient.trim().toLowerCase();
78
- const clientKind = CLIENT_ALIASES[alias];
79
- if (clientKind === void 0) {
80
- throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
52
+ // src/install/hooks-orchestrator.ts
53
+ import { existsSync, statSync } from "fs";
54
+ import { isAbsolute, join, resolve } from "path";
55
+ async function installHooks(target, _options = {}) {
56
+ const normalizedTarget = normalizeTarget(target);
57
+ assertExistingDirectory(normalizedTarget);
58
+ const results = [];
59
+ results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
60
+ results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
61
+ results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
62
+ results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
63
+ results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
64
+ results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
65
+ results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
66
+ results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
67
+ results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
68
+ const fabricLanguage = readFabricLanguagePreference(normalizedTarget);
69
+ results.push(...await runStep(() => addFabricKnowledgeBaseSection(normalizedTarget, fabricLanguage)));
70
+ results.push(...validateHookPaths(normalizedTarget));
71
+ return summarizeResults(results);
72
+ }
73
+ function validateHookPaths(projectRoot) {
74
+ const scripts = [
75
+ { stepSuffix: "", hookFile: "fabric-hint.cjs" },
76
+ { stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
77
+ { stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
78
+ ];
79
+ const clients = [
80
+ {
81
+ client: "claude",
82
+ configRel: join(".claude", "settings.json"),
83
+ hookDir: join(".claude", "hooks")
84
+ },
85
+ {
86
+ client: "codex",
87
+ configRel: join(".codex", "hooks.json"),
88
+ hookDir: join(".codex", "hooks")
89
+ },
90
+ {
91
+ client: "cursor",
92
+ configRel: join(".cursor", "hooks.json"),
93
+ hookDir: join(".cursor", "hooks")
94
+ }
95
+ ];
96
+ const results = [];
97
+ for (const { client, configRel, hookDir } of clients) {
98
+ const configPath = resolve(projectRoot, configRel);
99
+ if (!existsSync(configPath)) {
100
+ results.push({
101
+ step: `hook-validate-${client}`,
102
+ path: configPath,
103
+ status: "skipped",
104
+ message: "missing-config"
105
+ });
106
+ continue;
107
+ }
108
+ for (const { stepSuffix, hookFile } of scripts) {
109
+ const expectedHookPath = resolve(projectRoot, hookDir, hookFile);
110
+ const expectedHookRel = join(hookDir, hookFile);
111
+ const step = `hook-validate-${client}${stepSuffix}`;
112
+ if (!existsSync(expectedHookPath)) {
113
+ results.push({
114
+ step,
115
+ path: expectedHookPath,
116
+ status: "error",
117
+ message: `hook script missing: ${expectedHookRel}`
118
+ });
119
+ continue;
120
+ }
121
+ results.push({ step, path: expectedHookPath, status: "skipped", message: "ok" });
81
122
  }
82
- clients.add(clientKind);
83
123
  }
84
- return clients;
124
+ return results;
85
125
  }
86
- async function loadFabricConfig(workspaceRoot) {
87
- const configPath = resolve(workspaceRoot, "fabric.config.json");
88
- if (!existsSync(configPath)) {
89
- return {};
90
- }
91
- const parsed = JSON.parse(await readFile(configPath, "utf8"));
92
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
93
- throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
126
+ async function runStep(fn) {
127
+ try {
128
+ return await fn();
129
+ } catch (error) {
130
+ return [
131
+ {
132
+ step: "hook-install",
133
+ path: "",
134
+ status: "error",
135
+ message: error instanceof Error ? error.message : String(error)
136
+ }
137
+ ];
94
138
  }
95
- return parsed;
96
- }
97
- function resolveServerPath(override) {
98
- if (override) return override;
99
- if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
100
- return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
101
- }
102
- function writeStderr(message) {
103
- process.stderr.write(`${message}
104
- `);
105
139
  }
106
- var configCmd = defineCommand({
107
- meta: {
108
- name: "config",
109
- description: t("cli.config.description")
110
- },
111
- subCommands: {
112
- hooks: hooksCommand,
113
- install: defineCommand({
114
- meta: {
115
- name: "install",
116
- description: t("cli.config.install.description")
117
- },
118
- args: {
119
- clients: {
120
- type: "string",
121
- description: t("cli.config.install.args.clients.description")
122
- },
123
- "dry-run": {
124
- type: "boolean",
125
- description: t("cli.config.install.args.dry-run.description"),
126
- default: false
127
- }
128
- },
129
- async run({ args }) {
130
- const selectedClients = parseClientFilter(args.clients);
131
- const result = await installMcpClients(process.cwd(), {
132
- clients: selectedClients === null ? void 0 : Array.from(selectedClients),
133
- dryRun: args["dry-run"]
134
- });
135
- if (result.details.length === 0) {
136
- writeStderr(t("cli.config.install.no-configs"));
137
- return;
138
- }
139
- for (const detail of result.details) {
140
- if (detail.action === "skipped") {
141
- writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
142
- continue;
143
- }
144
- if (detail.action === "dry-run" && detail.path !== null) {
145
- writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
146
- continue;
147
- }
148
- if (detail.path !== null) {
149
- writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
150
- }
151
- }
152
- }
153
- })
140
+ async function runSingleStep(step, fn) {
141
+ try {
142
+ return await fn();
143
+ } catch (error) {
144
+ return {
145
+ step,
146
+ path: "",
147
+ status: "error",
148
+ message: error instanceof Error ? error.message : String(error)
149
+ };
154
150
  }
155
- });
156
- async function installMcpClients(target, options = {}) {
157
- const workspaceRoot = resolve(target);
158
- const fabricConfig = await loadFabricConfig(workspaceRoot);
159
- const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
160
- const serverPath = resolveServerPath(options.localServerPath);
161
- const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
162
- (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
163
- );
151
+ }
152
+ function summarizeResults(results) {
164
153
  const installed = [];
165
154
  const skipped = [];
166
- const details = [];
167
- for (const writer of writers) {
168
- const configPath = await writer.detect(workspaceRoot);
169
- if (configPath === null) {
170
- skipped.push(writer.clientKind);
171
- details.push({ client: writer.clientKind, path: null, action: "skipped" });
172
- continue;
173
- }
174
- if (options.dryRun) {
175
- skipped.push(writer.clientKind);
176
- details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
177
- continue;
155
+ const errors = [];
156
+ for (const r of results) {
157
+ switch (r.status) {
158
+ case "written":
159
+ installed.push(r.path);
160
+ break;
161
+ case "skipped":
162
+ skipped.push(r.path);
163
+ break;
164
+ case "error":
165
+ errors.push(`${r.step} ${r.path}: ${r.message ?? "unknown error"}`);
166
+ break;
178
167
  }
179
- await writer.write(serverPath, workspaceRoot);
180
- installed.push(writer.clientKind);
181
- details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
182
168
  }
183
- return { installed, skipped, details };
169
+ return { installed, skipped, errors };
170
+ }
171
+ function normalizeTarget(targetInput) {
172
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
173
+ }
174
+ function assertExistingDirectory(target) {
175
+ if (!existsSync(target) || !statSync(target).isDirectory()) {
176
+ throw new Error(t("cli.shared.target-invalid", { target }));
177
+ }
184
178
  }
185
179
 
186
180
  // src/scanner/forensic.ts
187
181
  import { execFileSync } from "child_process";
188
- import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
182
+ import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
189
183
  import { createRequire } from "module";
190
- import { basename, extname, isAbsolute, join, posix, relative, resolve as resolve2, sep } from "path";
184
+ import { basename, extname, isAbsolute as isAbsolute2, join as join2, posix, relative, resolve as resolve2, sep } from "path";
191
185
  import {
192
186
  forensicReportSchema
193
187
  } from "@fenglimg/fabric-shared";
188
+
189
+ // src/scanner/detector.ts
190
+ import { detectFramework } from "@fenglimg/fabric-shared/node";
191
+
192
+ // src/scanner/forensic.ts
194
193
  var require2 = createRequire(import.meta.url);
195
194
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
196
195
  ".fabric",
@@ -273,7 +272,7 @@ var parserInitPromise = null;
273
272
  var languagePromiseByKind = {};
274
273
  var parserBundlePromiseByKind = {};
275
274
  async function buildForensicReport(targetInput) {
276
- const target = normalizeTarget(targetInput);
275
+ const target = normalizeTarget2(targetInput);
277
276
  const framework = detectFramework(target);
278
277
  const topology = buildTopology(target);
279
278
  const entryPoints = collectEntryPoints(target, topology.files);
@@ -309,11 +308,11 @@ async function buildForensicReport(targetInput) {
309
308
  }
310
309
  return validation.data;
311
310
  }
312
- function normalizeTarget(targetInput) {
313
- return isAbsolute(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
311
+ function normalizeTarget2(targetInput) {
312
+ return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
314
313
  }
315
314
  function buildTopology(root) {
316
- assertExistingDirectory(root);
315
+ assertExistingDirectory2(root);
317
316
  const byExt = {};
318
317
  const keyDirs = /* @__PURE__ */ new Set();
319
318
  const files = [];
@@ -326,7 +325,7 @@ function buildTopology(root) {
326
325
  continue;
327
326
  }
328
327
  for (const entry of readdirSync(current, { withFileTypes: true })) {
329
- const absolutePath = join(current, entry.name);
328
+ const absolutePath = join2(current, entry.name);
330
329
  const relativePath = toPosixPath(relative(root, absolutePath));
331
330
  if (relativePath.length === 0) {
332
331
  continue;
@@ -346,7 +345,7 @@ function buildTopology(root) {
346
345
  if (!entry.isFile()) {
347
346
  continue;
348
347
  }
349
- const stats = statSync(absolutePath);
348
+ const stats = statSync2(absolutePath);
350
349
  const extension = extname(entry.name) || "[none]";
351
350
  byExt[extension] = (byExt[extension] ?? 0) + 1;
352
351
  totalFiles += 1;
@@ -364,8 +363,8 @@ function buildTopology(root) {
364
363
  files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
365
364
  };
366
365
  }
367
- function assertExistingDirectory(target) {
368
- if (!existsSync2(target) || !statSync(target).isDirectory()) {
366
+ function assertExistingDirectory2(target) {
367
+ if (!existsSync2(target) || !statSync2(target).isDirectory()) {
369
368
  throw new Error(`Target must be an existing directory: ${target}`);
370
369
  }
371
370
  }
@@ -414,7 +413,7 @@ function getEntryPointReason(relativePath) {
414
413
  async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
415
414
  const samples = [];
416
415
  for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
417
- const absolutePath = join(target, ...entryPoint.path.split("/"));
416
+ const absolutePath = join2(target, ...entryPoint.path.split("/"));
418
417
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
419
418
  const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
420
419
  frameworkKind,
@@ -451,7 +450,7 @@ function readFirstLines(path, lineLimit) {
451
450
  }
452
451
  }
453
452
  function readPackageDependencies(target) {
454
- const packageJsonPath = join(target, "package.json");
453
+ const packageJsonPath = join2(target, "package.json");
455
454
  if (!existsSync2(packageJsonPath)) {
456
455
  return /* @__PURE__ */ new Map();
457
456
  }
@@ -792,8 +791,8 @@ function scoreFrameworkConfidence(input) {
792
791
  return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
793
792
  }
794
793
  function readReadmeInfo(target) {
795
- const readmePath = join(target, "README.md");
796
- const hasContributing = existsSync2(join(target, "CONTRIBUTING.md"));
794
+ const readmePath = join2(target, "README.md");
795
+ const hasContributing = existsSync2(join2(target, "CONTRIBUTING.md"));
797
796
  if (!existsSync2(readmePath)) {
798
797
  return {
799
798
  quality: "missing",
@@ -1279,7 +1278,7 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
1279
1278
  return recommendations;
1280
1279
  }
1281
1280
  function readProjectName(target) {
1282
- const packageJsonPath = join(target, "package.json");
1281
+ const packageJsonPath = join2(target, "package.json");
1283
1282
  if (existsSync2(packageJsonPath)) {
1284
1283
  try {
1285
1284
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
@@ -1293,7 +1292,7 @@ function readProjectName(target) {
1293
1292
  return basename(target);
1294
1293
  }
1295
1294
  function getCliVersion() {
1296
- return true ? "2.0.0-rc.11" : "unknown";
1295
+ return true ? "2.0.0-rc.15" : "unknown";
1297
1296
  }
1298
1297
  function sortRecord(record) {
1299
1298
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1302,7 +1301,7 @@ function toPosixPath(path) {
1302
1301
  return path.split(sep).join("/");
1303
1302
  }
1304
1303
 
1305
- // src/commands/init.ts
1304
+ // src/commands/install.ts
1306
1305
  var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
1307
1306
 
1308
1307
  This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
@@ -1312,99 +1311,60 @@ Run \`fabric doctor\` to verify state.
1312
1311
 
1313
1312
  See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
1314
1313
  `;
1315
- var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1314
+ var LOCAL_FABRIC_SERVER_PATH = join3("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1316
1315
  var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
1317
1316
  var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
1318
- var initCommand = defineCommand2({
1317
+ var installCommand = defineCommand({
1319
1318
  meta: {
1320
- name: "init",
1321
- description: t("cli.init.description")
1319
+ name: "install",
1320
+ description: t("cli.install.description")
1322
1321
  },
1323
1322
  args: {
1324
- target: {
1325
- type: "string",
1326
- description: t("cli.init.args.target.description")
1327
- },
1328
1323
  debug: {
1329
1324
  type: "boolean",
1330
- description: t("cli.init.args.debug.description"),
1325
+ description: t("cli.install.args.debug.description"),
1331
1326
  default: false
1332
1327
  },
1333
- force: {
1328
+ "dry-run": {
1334
1329
  type: "boolean",
1335
- description: t("cli.init.args.force.description"),
1330
+ description: t("cli.install.args.dry-run.description"),
1336
1331
  default: false
1337
1332
  },
1338
- yes: {
1339
- type: "boolean",
1340
- description: t("cli.init.args.yes.description"),
1341
- default: false
1342
- },
1343
- plan: {
1344
- type: "boolean",
1345
- description: t("cli.init.args.plan.description"),
1346
- default: false
1333
+ target: {
1334
+ type: "string",
1335
+ description: t("cli.install.args.target.description")
1347
1336
  },
1348
- reapply: {
1337
+ yes: {
1349
1338
  type: "boolean",
1350
- description: t("cli.init.args.reapply.description"),
1339
+ description: t("cli.install.args.yes.description"),
1351
1340
  default: false
1352
- },
1353
- bootstrap: {
1354
- type: "boolean",
1355
- default: true,
1356
- negativeDescription: t("cli.init.args.no-bootstrap.description")
1357
- },
1358
- mcp: {
1359
- type: "boolean",
1360
- default: true,
1361
- negativeDescription: t("cli.init.args.no-mcp.description")
1362
- },
1363
- hooks: {
1364
- type: "boolean",
1365
- default: true,
1366
- negativeDescription: t("cli.init.args.no-hooks.description")
1367
- },
1368
- interactive: {
1369
- type: "boolean",
1370
- description: t("cli.init.args.interactive.description"),
1371
- default: true
1372
- },
1373
- "mcp-install": {
1374
- type: "string",
1375
- default: "global",
1376
- description: t("cli.init.mcp.install.prompt")
1377
- },
1378
- scope: {
1379
- type: "string",
1380
- description: t("cli.init.mcp.scope.description")
1381
1341
  }
1382
1342
  },
1383
1343
  async run({ args }) {
1384
1344
  await runInitCommand(args);
1385
1345
  }
1386
1346
  });
1387
- var init_default = initCommand;
1347
+ var install_default = installCommand;
1388
1348
  async function runInitCommand(args) {
1389
1349
  const logger = createDebugLogger(args.debug);
1390
1350
  const resolution = resolveDevMode(args.target, process.cwd());
1391
1351
  const intent = resolveInitCliIntent(args, resolution.target);
1392
- if (args.reapply === true) {
1393
- checkLockOrThrow(intent.target, { force: args.force });
1352
+ const fabricInitialized = existsSync3(join3(intent.target, ".fabric", "events.jsonl"));
1353
+ if (fabricInitialized) {
1354
+ try {
1355
+ checkLockOrThrow(intent.target);
1356
+ } catch (err) {
1357
+ if (hasActionHint(err)) {
1358
+ renderFabricError(err);
1359
+ process.exit(1);
1360
+ }
1361
+ throw err;
1362
+ }
1394
1363
  }
1395
1364
  logger(`init target source: ${resolution.source}`);
1396
1365
  for (const step of resolution.chain) {
1397
1366
  logger(step);
1398
1367
  }
1399
- if (intent.options.planOnly) {
1400
- writeStderr2(t("cli.init.compat.plan"));
1401
- }
1402
- if (args.interactive === false) {
1403
- writeStderr2(t("cli.init.compat.interactive"));
1404
- }
1405
- if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
1406
- writeStderr2(t("cli.init.compat.legacy-stage-flags"));
1407
- }
1408
1368
  const supports = detectClientSupports(intent.target);
1409
1369
  const basePlan = await buildInitExecutionPlan({
1410
1370
  target: intent.target,
@@ -1414,7 +1374,7 @@ async function runInitCommand(args) {
1414
1374
  interactive: intent.interactiveSummary && !intent.wizardEnabled,
1415
1375
  supports
1416
1376
  });
1417
- const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
1377
+ const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, createDefaultInitWizardAdapter()) : basePlan;
1418
1378
  if (plan === null) {
1419
1379
  process.exitCode = 130;
1420
1380
  return;
@@ -1426,7 +1386,7 @@ async function runInitCommand(args) {
1426
1386
  return result;
1427
1387
  }
1428
1388
  function writeDefaultFabricConfig(fabricDir, targetRoot) {
1429
- const target = join2(fabricDir, "fabric-config.json");
1389
+ const target = join3(fabricDir, "fabric-config.json");
1430
1390
  if (existsSync3(target)) return;
1431
1391
  const detectedLanguage = detectExistingLanguage(targetRoot);
1432
1392
  const FABRIC_CONFIG_DEFAULTS = {
@@ -1434,7 +1394,7 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
1434
1394
  // README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
1435
1395
  // can edit `.fabric/fabric-config.json` to override. See
1436
1396
  // packages/shared/src/schemas/fabric-config.ts for the enum.
1437
- knowledge_language: detectedLanguage,
1397
+ fabric_language: detectedLanguage,
1438
1398
  // fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
1439
1399
  // since last knowledge_proposed event.
1440
1400
  archive_hint_hours: 24,
@@ -1491,43 +1451,27 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
1491
1451
  mkdirSync(fabricDir, { recursive: true });
1492
1452
  writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
1493
1453
  log.info(
1494
- `Detected and fixated knowledge_language = ${detectedLanguage}; edit ${target} to override.`
1454
+ `Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
1495
1455
  );
1496
1456
  }
1497
1457
  function resolveInitCliIntent(args, targetInput) {
1498
- const target = normalizeTarget2(targetInput);
1499
- const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
1500
- const claudeMcpScope = resolveClaudeMcpScope(args.scope);
1458
+ const target = normalizeTarget3(targetInput);
1459
+ const mcpInstallMode = "global";
1460
+ const claudeMcpScope = "project";
1501
1461
  const terminalInteractive = isInteractiveInit();
1502
- const planOnly = args.plan === true;
1503
- const reapply = args.reapply === true;
1462
+ const planOnly = args["dry-run"] === true;
1504
1463
  const options = {
1505
- force: reapply ? true : args.force,
1506
- skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
1507
- skipMcp: args.mcp === false ? true : args.skipMcp,
1508
- skipHooks: args.hooks === false ? true : args.skipHooks,
1509
- planOnly,
1510
- reapply
1464
+ planOnly
1511
1465
  };
1512
1466
  return {
1513
1467
  target,
1514
1468
  options,
1515
1469
  mcpInstallMode,
1516
1470
  claudeMcpScope,
1517
- interactiveSummary: args.interactive !== false && terminalInteractive,
1471
+ interactiveSummary: terminalInteractive,
1518
1472
  wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
1519
1473
  };
1520
1474
  }
1521
- function resolveClaudeMcpScope(raw) {
1522
- if (raw === void 0 || raw === "project") {
1523
- return "project";
1524
- }
1525
- if (raw === "user") {
1526
- return "user";
1527
- }
1528
- writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
1529
- return "project";
1530
- }
1531
1475
  async function buildInitExecutionPlan(input) {
1532
1476
  const options = input.options ?? {};
1533
1477
  const scaffold = await buildInitFabricPlan(input.target, options);
@@ -1564,17 +1508,17 @@ async function buildInitExecutionPlan(input) {
1564
1508
  };
1565
1509
  }
1566
1510
  async function executeInitExecutionPlan(plan) {
1567
- if (plan.options.force) {
1568
- writeStderr2(t("cli.init.force.warning", { path: plan.target }));
1569
- }
1570
- if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
1571
- writeStderr2(formatInitModeBanner(plan.options));
1572
- }
1573
1511
  if (plan.interactive) {
1574
1512
  printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
1575
1513
  }
1514
+ const scaffoldStates = [
1515
+ { path: plan.scaffold.metaPath, state: plan.scaffold.metaState },
1516
+ { path: plan.scaffold.eventsPath, state: plan.scaffold.eventsState },
1517
+ { path: plan.scaffold.forensicPath, state: plan.scaffold.forensicState }
1518
+ ];
1576
1519
  if (plan.options.planOnly) {
1577
1520
  printInitPlanPreview(plan);
1521
+ printInitDiffStateTable(scaffoldStates);
1578
1522
  return {
1579
1523
  plan,
1580
1524
  created: buildPlanOnlyScaffoldResult(plan.scaffold),
@@ -1582,6 +1526,17 @@ async function executeInitExecutionPlan(plan) {
1582
1526
  finalSupports: plan.supports
1583
1527
  };
1584
1528
  }
1529
+ if (existsSync3(plan.scaffold.fabricDir) && !statSync3(plan.scaffold.fabricDir).isDirectory()) {
1530
+ throw new Error(
1531
+ t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
1532
+ );
1533
+ }
1534
+ const drifted = scaffoldStates.find(
1535
+ (entry) => entry.state === "drifted" || entry.state === "user-modified"
1536
+ );
1537
+ if (drifted !== void 0) {
1538
+ throw new Error(t("cli.install.diff.drift-abort", { path: drifted.path }));
1539
+ }
1585
1540
  let created = null;
1586
1541
  const stageResults = [];
1587
1542
  let finalSupports = plan.supports;
@@ -1606,6 +1561,11 @@ async function executeInitExecutionPlan(plan) {
1606
1561
  exhaustiveInitExecutionStep(step);
1607
1562
  }
1608
1563
  }
1564
+ if (scaffoldStates.every((entry) => entry.state === "present-canonical")) {
1565
+ console.log(
1566
+ t("cli.install.diff.canonical", { count: String(scaffoldStates.length) })
1567
+ );
1568
+ }
1609
1569
  return {
1610
1570
  plan,
1611
1571
  created: created ?? unreachableInitScaffold(),
@@ -1618,20 +1578,23 @@ function resolvePersonalFabricRoot() {
1618
1578
  return process.env.FABRIC_HOME ?? homedir();
1619
1579
  }
1620
1580
  async function buildInitFabricPlan(target, options) {
1621
- assertExistingDirectory2(target);
1622
- const fabricDir = join2(target, ".fabric");
1623
- const agentsMdPath = join2(target, "AGENTS.md");
1581
+ assertExistingDirectory3(target);
1582
+ const fabricDir = join3(target, ".fabric");
1583
+ const agentsMdPath = join3(target, "AGENTS.md");
1624
1584
  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");
1585
+ const knowledgeDir = join3(fabricDir, "knowledge");
1586
+ const personalKnowledgeDir = join3(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1587
+ const forensicPath = join3(fabricDir, "forensic.json");
1588
+ const eventsPath = join3(fabricDir, "events.jsonl");
1589
+ const metaPath = join3(fabricDir, "agents.meta.json");
1630
1590
  const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
1631
1591
  const knowledgeDirAction = existsSync3(knowledgeDir) ? "overwritten" : "created";
1632
- const metaAction = planFreshPath(metaPath, options);
1633
- const eventsAction = planFreshPath(eventsPath, options);
1634
- const forensicAction = planFreshPath(forensicPath, options);
1592
+ const metaClassification = classifyFreshPath(metaPath, "structural");
1593
+ const eventsClassification = classifyFreshPath(eventsPath, "presence");
1594
+ const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
1595
+ const metaAction = diffStateToWriteAction(metaClassification.state);
1596
+ const eventsAction = diffStateToWriteAction(eventsClassification.state);
1597
+ const forensicAction = diffStateToWriteAction(forensicClassification.state);
1635
1598
  const forensicReport = await buildForensicReport(target);
1636
1599
  const meta = createInitialMeta();
1637
1600
  return {
@@ -1651,11 +1614,13 @@ async function buildInitFabricPlan(target, options) {
1651
1614
  eventsAction,
1652
1615
  forensicPath,
1653
1616
  forensicAction,
1654
- forensicReport
1617
+ forensicReport,
1618
+ metaState: metaClassification.state,
1619
+ eventsState: eventsClassification.state,
1620
+ forensicState: forensicClassification.state
1655
1621
  };
1656
1622
  }
1657
1623
  async function executeInitFabricPlan(plan) {
1658
- const isReapply = plan.options?.reapply === true;
1659
1624
  if (plan.replaceFabricDir) {
1660
1625
  rmSync(plan.fabricDir, { force: true });
1661
1626
  }
@@ -1666,9 +1631,9 @@ async function executeInitFabricPlan(plan) {
1666
1631
  }
1667
1632
  mkdirSync(plan.knowledgeDir, { recursive: true });
1668
1633
  for (const sub of KNOWLEDGE_SUBDIRS) {
1669
- const teamSubDir = join2(plan.knowledgeDir, sub);
1634
+ const teamSubDir = join3(plan.knowledgeDir, sub);
1670
1635
  mkdirSync(teamSubDir, { recursive: true });
1671
- const teamGitkeep = join2(teamSubDir, ".gitkeep");
1636
+ const teamGitkeep = join3(teamSubDir, ".gitkeep");
1672
1637
  if (!existsSync3(teamGitkeep)) {
1673
1638
  writeFileSync(teamGitkeep, "", "utf8");
1674
1639
  }
@@ -1676,36 +1641,49 @@ async function executeInitFabricPlan(plan) {
1676
1641
  try {
1677
1642
  mkdirSync(plan.personalKnowledgeDir, { recursive: true });
1678
1643
  for (const sub of KNOWLEDGE_SUBDIRS) {
1679
- mkdirSync(join2(plan.personalKnowledgeDir, sub), { recursive: true });
1644
+ mkdirSync(join3(plan.personalKnowledgeDir, sub), { recursive: true });
1680
1645
  }
1681
1646
  } catch {
1682
1647
  }
1683
- preparePlannedPath(plan.metaPath, plan.metaAction);
1684
- await atomicWriteJson(plan.metaPath, plan.meta);
1685
- if (isReapply) {
1686
- if (!existsSync3(plan.eventsPath)) {
1687
- mkdirSync(dirname(plan.eventsPath), { recursive: true });
1688
- writeFileSync(plan.eventsPath, "", "utf8");
1689
- }
1690
- } else {
1648
+ if (plan.metaState === "missing") {
1649
+ preparePlannedPath(plan.metaPath, plan.metaAction);
1650
+ await atomicWriteJson(plan.metaPath, plan.meta);
1651
+ }
1652
+ if (plan.eventsState === "missing") {
1691
1653
  preparePlannedPath(plan.eventsPath, plan.eventsAction);
1654
+ mkdirSync(dirname(plan.eventsPath), { recursive: true });
1692
1655
  writeFileSync(plan.eventsPath, "", "utf8");
1693
1656
  }
1694
1657
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
1695
1658
  await atomicWriteJson(plan.forensicPath, plan.forensicReport);
1696
- if (!plan.options?.reapply) {
1659
+ const wasCanonicalReRun = plan.metaState === "present-canonical" && plan.eventsState === "present-canonical";
1660
+ if (!wasCanonicalReRun) {
1697
1661
  try {
1698
1662
  await runInitScan(plan.target, { source: "init" });
1699
1663
  } catch (error) {
1700
- writeStderr2(
1664
+ writeStderr(
1701
1665
  `[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
1702
1666
  );
1703
1667
  }
1704
1668
  }
1705
- if (isReapply) {
1706
- appendReapplyLedgerEvent(plan.eventsPath, {
1707
- preserved_ledger: true
1708
- });
1669
+ if (existsSync3(plan.eventsPath)) {
1670
+ const applied = [];
1671
+ const canonical = [];
1672
+ const drifted = [];
1673
+ for (const entry of [
1674
+ { path: plan.metaPath, state: plan.metaState },
1675
+ { path: plan.eventsPath, state: plan.eventsState },
1676
+ { path: plan.forensicPath, state: plan.forensicState }
1677
+ ]) {
1678
+ if (entry.state === "missing") {
1679
+ applied.push(entry.path);
1680
+ } else if (entry.state === "present-canonical") {
1681
+ canonical.push(entry.path);
1682
+ } else {
1683
+ drifted.push(entry.path);
1684
+ }
1685
+ }
1686
+ appendInstallDiffLedgerEvent(plan.eventsPath, { applied, canonical, drifted });
1709
1687
  }
1710
1688
  return {
1711
1689
  agentsMdPath: plan.agentsMdPath,
@@ -1725,16 +1703,16 @@ async function initFabric(target, options) {
1725
1703
  return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
1726
1704
  }
1727
1705
  function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
1728
- return terminalInteractive && args.interactive !== false && args.yes !== true;
1706
+ return terminalInteractive && args.yes !== true;
1729
1707
  }
1730
- async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter) {
1708
+ async function resolveInitExecutionPlanWithWizard(basePlan, wizardAdapter) {
1731
1709
  const selection = await wizardAdapter.run({
1732
1710
  target: basePlan.target,
1733
1711
  options: basePlan.options,
1734
1712
  supports: basePlan.supports,
1735
1713
  mcpInstallMode: basePlan.mcpInstallMode,
1736
1714
  claudeMcpScope: basePlan.claudeMcpScope,
1737
- lockedStages: collectLockedWizardStages(args)
1715
+ lockedStages: []
1738
1716
  });
1739
1717
  if (selection === null) {
1740
1718
  return null;
@@ -1772,27 +1750,36 @@ function printInitScaffoldResult(created) {
1772
1750
  function printInitPostSetup(plan, stageResults, finalSupports) {
1773
1751
  if (shouldPrintHooksNextStep(plan.options, stageResults)) {
1774
1752
  console.log(
1775
- t("cli.init.next-step", {
1753
+ t("cli.install.next-step", {
1776
1754
  label: nextLabel(),
1777
- message: paint.muted(t("cli.init.next-step.message"))
1755
+ message: paint.muted(t("cli.install.next-step.message"))
1778
1756
  })
1779
1757
  );
1780
1758
  }
1781
1759
  console.log(
1782
- t("cli.init.reason-message", {
1760
+ t("cli.install.reason-message", {
1783
1761
  label: reasonLabel(),
1784
1762
  message: paint.muted(formatInitReasonMessage(finalSupports))
1785
1763
  })
1786
1764
  );
1787
1765
  printInitStageSummary(stageResults);
1788
1766
  printInitCapabilitySummary(finalSupports, stageResults, plan.options);
1767
+ const fabricLanguage = readFabricLanguagePreference(plan.target);
1768
+ console.log(
1769
+ paint.muted(t("cli.install.language_preference_hint", { value: fabricLanguage }))
1770
+ );
1771
+ }
1772
+ function printInitDiffStateTable(entries) {
1773
+ for (const entry of entries) {
1774
+ console.log(` ${formatDiffFileState(entry.state)} ${entry.path}`);
1775
+ }
1789
1776
  }
1790
1777
  function printInitPlanPreview(plan) {
1791
- console.log(t("cli.init.plan.preview-title"));
1778
+ console.log(t("cli.install.plan.preview-title"));
1792
1779
  printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
1793
1780
  console.log(
1794
- t("cli.init.plan.preview-result", {
1795
- mode: plan.options.reapply ? t("cli.init.mode.reapply") : t("cli.init.mode.default"),
1781
+ t("cli.install.plan.preview-result", {
1782
+ mode: t("cli.install.mode.default"),
1796
1783
  bootstrap: yesNoLabel(!plan.options.skipBootstrap),
1797
1784
  mcp: yesNoLabel(!plan.options.skipMcp),
1798
1785
  hooks: yesNoLabel(!plan.options.skipHooks)
@@ -1822,7 +1809,7 @@ async function executeInitStagePlan(plan, stageName) {
1822
1809
  if (stage.skipped) {
1823
1810
  return { name: stageName, disposition: "skipped" };
1824
1811
  }
1825
- console.log(formatInitStageHeader(t(`cli.init.stages.${stageName}`)));
1812
+ console.log(formatInitStageHeader(t(`cli.install.stages.${stageName}`)));
1826
1813
  try {
1827
1814
  switch (stage.name) {
1828
1815
  case "bootstrap": {
@@ -1836,13 +1823,14 @@ async function executeInitStagePlan(plan, stageName) {
1836
1823
  installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
1837
1824
  installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
1838
1825
  installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
1839
- installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
1826
+ const fabricLanguage = readFabricLanguagePreference(plan.target);
1827
+ installResults.push(...await runBestEffort("section", () => addFabricKnowledgeBaseSection(plan.target, fabricLanguage)));
1840
1828
  const installedCount = installResults.filter((r) => r.status === "written").length;
1841
1829
  const skippedCount = installResults.filter((r) => r.status === "skipped").length;
1842
1830
  const errorCount = installResults.filter((r) => r.status === "error").length;
1843
1831
  for (const result of installResults) {
1844
1832
  if (result.status === "error") {
1845
- writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
1833
+ writeStderr(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
1846
1834
  }
1847
1835
  }
1848
1836
  const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
@@ -1852,15 +1840,14 @@ async function executeInitStagePlan(plan, stageName) {
1852
1840
  case "mcp": {
1853
1841
  if (stage.installMode === "local") {
1854
1842
  const manager = stage.packageManager ?? detectPackageManager(plan.target);
1855
- writeStderr2(t("cli.init.mcp.install.local"));
1856
- writeStderr2(t("cli.init.mcp.local.installing", { manager }));
1843
+ writeStderr(t("cli.install.mcp.install.local"));
1844
+ writeStderr(t("cli.install.mcp.local.installing", { manager }));
1857
1845
  installLocalFabricServer(plan.target, manager);
1858
- writeStderr2(t("cli.init.mcp.local.installed"));
1846
+ writeStderr(t("cli.install.mcp.local.installed"));
1859
1847
  } else {
1860
- writeStderr2(t("cli.init.mcp.install.global"));
1848
+ writeStderr(t("cli.install.mcp.install.global"));
1861
1849
  }
1862
1850
  const result = await installMcpClients(plan.target, {
1863
- force: plan.options.force,
1864
1851
  localServerPath: stage.localServerPath,
1865
1852
  claudeMcpScope: stage.claudeMcpScope
1866
1853
  });
@@ -1872,7 +1859,7 @@ async function executeInitStagePlan(plan, stageName) {
1872
1859
  return { name: "mcp", disposition: "ran" };
1873
1860
  }
1874
1861
  case "hooks": {
1875
- const result = await installHooks(plan.target, { force: plan.options.force });
1862
+ const result = await installHooks(plan.target);
1876
1863
  console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
1877
1864
  return { name: "hooks", disposition: "ran" };
1878
1865
  }
@@ -1880,30 +1867,66 @@ async function executeInitStagePlan(plan, stageName) {
1880
1867
  return exhaustiveInitStagePlan(stage);
1881
1868
  }
1882
1869
  } catch (error) {
1883
- writeStderr2(formatInitStageFailure(stageName, error));
1870
+ writeStderr(formatInitStageFailure(stageName, error));
1884
1871
  return { name: stageName, disposition: "failed" };
1885
1872
  }
1886
1873
  }
1887
- function shouldReplaceWritableDirectory(path, options) {
1874
+ function shouldReplaceWritableDirectory(path, _options) {
1888
1875
  if (!existsSync3(path)) {
1889
1876
  return false;
1890
1877
  }
1891
- if (statSync2(path).isDirectory()) {
1878
+ if (statSync3(path).isDirectory()) {
1892
1879
  return false;
1893
1880
  }
1894
- if (!options?.force) {
1895
- throw new Error(t("cli.init.errors.abort-existing", { path }));
1896
- }
1897
- return true;
1881
+ return false;
1898
1882
  }
1899
- function planFreshPath(path, options) {
1883
+ function classifyFreshPath(path, strategy) {
1900
1884
  if (!existsSync3(path)) {
1901
- return "created";
1885
+ return { path, state: "missing" };
1886
+ }
1887
+ let stat;
1888
+ try {
1889
+ stat = statSync3(path);
1890
+ } catch (error) {
1891
+ return {
1892
+ path,
1893
+ state: "user-modified",
1894
+ reason: error instanceof Error ? error.message : String(error)
1895
+ };
1896
+ }
1897
+ if (!stat.isFile()) {
1898
+ return { path, state: "user-modified", reason: "expected a file" };
1899
+ }
1900
+ if (strategy === "presence" || strategy === "always-rewrite") {
1901
+ return { path, state: "present-canonical" };
1902
1902
  }
1903
- if (!options?.force) {
1904
- throw new Error(t("cli.init.errors.abort-existing", { path }));
1903
+ try {
1904
+ const raw = readFileSync2(path, "utf8");
1905
+ const parsed = JSON.parse(raw);
1906
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1907
+ return { path, state: "user-modified", reason: "not a JSON object" };
1908
+ }
1909
+ const record = parsed;
1910
+ const hasRevision = typeof record["revision"] === "string";
1911
+ const hasNodes = record["nodes"] !== void 0 && record["nodes"] !== null && typeof record["nodes"] === "object" && !Array.isArray(record["nodes"]);
1912
+ const hasCounters = record["counters"] !== void 0 && record["counters"] !== null && typeof record["counters"] === "object" && !Array.isArray(record["counters"]);
1913
+ if (!hasRevision || !hasNodes || !hasCounters) {
1914
+ return { path, state: "drifted", reason: "missing required AgentsMeta fields" };
1915
+ }
1916
+ return { path, state: "present-canonical" };
1917
+ } catch (error) {
1918
+ return {
1919
+ path,
1920
+ state: "user-modified",
1921
+ reason: error instanceof Error ? error.message : String(error)
1922
+ };
1905
1923
  }
1906
- return "overwritten";
1924
+ }
1925
+ function diffStateToWriteAction(_state) {
1926
+ return "created";
1927
+ }
1928
+ function formatDiffFileState(state) {
1929
+ return t(`cli.install.diff.state.${state}`);
1907
1930
  }
1908
1931
  function preparePlannedPath(path, action) {
1909
1932
  mkdirSync(dirname(path), { recursive: true });
@@ -1914,59 +1937,59 @@ function preparePlannedPath(path, action) {
1914
1937
  function createDefaultInitWizardAdapter() {
1915
1938
  return {
1916
1939
  async run(context) {
1917
- intro(t("cli.init.wizard.intro"));
1940
+ intro(t("cli.install.wizard.intro"));
1918
1941
  note(
1919
- t("cli.init.wizard.overview.body", {
1942
+ t("cli.install.wizard.overview.body", {
1920
1943
  target: context.target,
1921
1944
  mode: formatInitModeBadge(context.options)
1922
1945
  }),
1923
- t("cli.init.wizard.overview.title")
1946
+ t("cli.install.wizard.overview.title")
1924
1947
  );
1925
1948
  printInitPlanSummary(context.target, context.options, context.mcpInstallMode, context.supports);
1926
- log.step(t("cli.init.wizard.step.target"));
1949
+ log.step(t("cli.install.wizard.step.target"));
1927
1950
  const continueWithTarget = await confirm({
1928
- message: t("cli.init.wizard.target.confirm", { target: context.target }),
1951
+ message: t("cli.install.wizard.target.confirm", { target: context.target }),
1929
1952
  initialValue: true
1930
1953
  });
1931
1954
  if (isCancel(continueWithTarget) || !continueWithTarget) {
1932
1955
  emitInitWizardCancellation();
1933
1956
  return null;
1934
1957
  }
1935
- log.step(t("cli.init.wizard.step.plan"));
1958
+ log.step(t("cli.install.wizard.step.plan"));
1936
1959
  let groupedSelection;
1937
1960
  try {
1938
1961
  groupedSelection = await group(
1939
1962
  {
1940
1963
  bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
1941
- message: t("cli.init.wizard.stage.bootstrap", {
1964
+ message: t("cli.install.wizard.stage.bootstrap", {
1942
1965
  defaultValue: formatPromptDefault(!context.options.skipBootstrap)
1943
1966
  }),
1944
1967
  initialValue: !context.options.skipBootstrap
1945
1968
  }),
1946
1969
  mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
1947
- message: t("cli.init.wizard.stage.mcp", {
1970
+ message: t("cli.install.wizard.stage.mcp", {
1948
1971
  defaultValue: formatPromptDefault(!context.options.skipMcp)
1949
1972
  }),
1950
1973
  initialValue: !context.options.skipMcp
1951
1974
  }),
1952
1975
  mcpInstallMode: async ({ results }) => results.mcp ? selectMcpInstallModeInGroup({
1953
- message: t("cli.init.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
1976
+ message: t("cli.install.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
1954
1977
  initialValue: context.mcpInstallMode,
1955
1978
  options: [
1956
- { value: "global", label: "global", hint: t("cli.init.mcp.install.global") },
1957
- { value: "local", label: "local", hint: t("cli.init.mcp.install.local") }
1979
+ { value: "global", label: "global", hint: t("cli.install.mcp.install.global") },
1980
+ { value: "local", label: "local", hint: t("cli.install.mcp.install.local") }
1958
1981
  ]
1959
1982
  }) : context.mcpInstallMode,
1960
1983
  claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
1961
- message: t("cli.init.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
1984
+ message: t("cli.install.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
1962
1985
  initialValue: context.claudeMcpScope,
1963
1986
  options: [
1964
- { value: "project", label: "project", hint: t("cli.init.mcp.scope.project") },
1965
- { value: "user", label: "user", hint: t("cli.init.mcp.scope.user") }
1987
+ { value: "project", label: "project", hint: t("cli.install.mcp.scope.project") },
1988
+ { value: "user", label: "user", hint: t("cli.install.mcp.scope.user") }
1966
1989
  ]
1967
1990
  }) : context.claudeMcpScope,
1968
1991
  hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
1969
- message: t("cli.init.wizard.stage.hooks", {
1992
+ message: t("cli.install.wizard.stage.hooks", {
1970
1993
  defaultValue: formatPromptDefault(!context.options.skipHooks)
1971
1994
  }),
1972
1995
  initialValue: !context.options.skipHooks
@@ -1995,23 +2018,23 @@ function createDefaultInitWizardAdapter() {
1995
2018
  skipMcp: !groupedSelection.mcp,
1996
2019
  skipHooks: !groupedSelection.hooks
1997
2020
  };
1998
- log.step(t("cli.init.wizard.step.review"));
2021
+ log.step(t("cli.install.wizard.step.review"));
1999
2022
  printInitPlanSummary(context.target, previewOptions, groupedSelection.mcpInstallMode, context.supports);
2000
2023
  const confirmed = await confirm({
2001
- message: t("cli.init.wizard.execute.confirm"),
2024
+ message: t("cli.install.wizard.execute.confirm"),
2002
2025
  initialValue: true
2003
2026
  });
2004
2027
  if (isCancel(confirmed) || !confirmed) {
2005
2028
  emitInitWizardCancellation();
2006
2029
  return null;
2007
2030
  }
2008
- outro(t("cli.init.wizard.outro"));
2031
+ outro(t("cli.install.wizard.outro"));
2009
2032
  return groupedSelection;
2010
2033
  }
2011
2034
  };
2012
2035
  }
2013
2036
  function emitInitWizardCancellation() {
2014
- cancel(t("cli.init.wizard.cancelled"));
2037
+ cancel(t("cli.install.wizard.cancelled"));
2015
2038
  }
2016
2039
  async function confirmInGroup(options) {
2017
2040
  const result = await confirm(options);
@@ -2042,74 +2065,42 @@ async function selectClaudeMcpScopeInGroup(options) {
2042
2065
  }
2043
2066
  return result;
2044
2067
  }
2045
- function collectLockedWizardStages(args) {
2046
- const lockedStages = [];
2047
- if (args.bootstrap === false) {
2048
- lockedStages.push("bootstrap");
2049
- }
2050
- if (args.mcp === false) {
2051
- lockedStages.push("mcp");
2052
- }
2053
- if (args.hooks === false) {
2054
- lockedStages.push("hooks");
2055
- }
2056
- return lockedStages;
2057
- }
2058
2068
  function formatPromptDefault(value) {
2059
2069
  return value ? "Y/n" : "y/N";
2060
2070
  }
2061
2071
  function formatInitModeBanner(options) {
2062
- if (options.planOnly && options.reapply) {
2063
- return t("cli.init.plan.mode-banner.plan-reapply");
2064
- }
2065
2072
  if (options.planOnly) {
2066
- return t("cli.init.plan.mode-banner.plan");
2067
- }
2068
- if (options.reapply) {
2069
- return t("cli.init.plan.mode-banner.reapply");
2073
+ return t("cli.install.plan.mode-banner.plan");
2070
2074
  }
2071
- return t("cli.init.plan.mode-banner.default");
2075
+ return t("cli.install.plan.mode-banner.default");
2072
2076
  }
2073
2077
  function formatInitModeBadge(options) {
2074
- if (options.planOnly && options.reapply) {
2075
- return t("cli.init.mode.badge.plan-reapply");
2076
- }
2077
2078
  if (options.planOnly) {
2078
- return t("cli.init.mode.badge.plan");
2079
- }
2080
- if (options.reapply) {
2081
- return t("cli.init.mode.badge.reapply");
2079
+ return t("cli.install.mode.badge.plan");
2082
2080
  }
2083
- return t("cli.init.mode.badge.default");
2081
+ return t("cli.install.mode.badge.default");
2084
2082
  }
2085
- function normalizeTarget2(targetInput) {
2086
- return isAbsolute2(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
2083
+ function normalizeTarget3(targetInput) {
2084
+ return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
2087
2085
  }
2088
- function assertExistingDirectory2(target) {
2089
- if (!existsSync3(target) || !statSync2(target).isDirectory()) {
2086
+ function assertExistingDirectory3(target) {
2087
+ if (!existsSync3(target) || !statSync3(target).isDirectory()) {
2090
2088
  throw new Error(`Target must be an existing directory: ${target}`);
2091
2089
  }
2092
2090
  }
2093
2091
  function detectPackageManager(cwd) {
2094
2092
  const workspaceRoot = resolve3(cwd);
2095
- if (existsSync3(join2(workspaceRoot, "pnpm-lock.yaml"))) {
2093
+ if (existsSync3(join3(workspaceRoot, "pnpm-lock.yaml"))) {
2096
2094
  return "pnpm";
2097
2095
  }
2098
- if (existsSync3(join2(workspaceRoot, "yarn.lock"))) {
2096
+ if (existsSync3(join3(workspaceRoot, "yarn.lock"))) {
2099
2097
  return "yarn";
2100
2098
  }
2101
- if (existsSync3(join2(workspaceRoot, "package-lock.json"))) {
2099
+ if (existsSync3(join3(workspaceRoot, "package-lock.json"))) {
2102
2100
  return "npm";
2103
2101
  }
2104
2102
  return "npm";
2105
2103
  }
2106
- function resolveMcpInstallMode(rawMode) {
2107
- if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
2108
- return rawMode ?? "global";
2109
- }
2110
- writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
2111
- return "global";
2112
- }
2113
2104
  function installLocalFabricServer(target, manager) {
2114
2105
  const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
2115
2106
  childProcess.execFileSync(manager, installArgs, {
@@ -2125,14 +2116,16 @@ function createInitialMeta() {
2125
2116
  counters: defaultAgentsMetaCounters()
2126
2117
  };
2127
2118
  }
2128
- function appendReapplyLedgerEvent(eventsPath, payload) {
2119
+ function appendInstallDiffLedgerEvent(eventsPath, payload) {
2129
2120
  const event = {
2130
2121
  kind: "fabric-event",
2131
2122
  id: `event:${randomUUID()}`,
2132
2123
  ts: Date.now(),
2133
2124
  schema_version: 1,
2134
- event_type: "reapply_completed",
2135
- preserved_ledger: payload.preserved_ledger
2125
+ event_type: "install_diff_applied",
2126
+ applied: payload.applied,
2127
+ canonical: payload.canonical,
2128
+ drifted: payload.drifted
2136
2129
  };
2137
2130
  const line = `${JSON.stringify(event)}
2138
2131
  `;
@@ -2183,7 +2176,7 @@ function printInitStageSummary(stageResults) {
2183
2176
  console.log(formatInitStageSummaryLine("failed", collectInitStageNames(stageResults, "failed")));
2184
2177
  }
2185
2178
  function formatInitStageSummaryLine(disposition, stages) {
2186
- const label = disposition === "ran" ? paint.success(t("cli.init.stages.summary.ran")) : disposition === "skipped" ? paint.muted(t("cli.init.stages.summary.skipped")) : paint.error(t("cli.init.stages.summary.failed"));
2179
+ const label = disposition === "ran" ? paint.success(t("cli.install.stages.summary.ran")) : disposition === "skipped" ? paint.muted(t("cli.install.stages.summary.skipped")) : paint.error(t("cli.install.stages.summary.failed"));
2187
2180
  return `${label}: ${stages.length > 0 ? stages.join(", ") : t("cli.shared.none")}`;
2188
2181
  }
2189
2182
  function collectInitStageNames(stageResults, disposition) {
@@ -2196,11 +2189,11 @@ function isInteractiveInit() {
2196
2189
  return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
2197
2190
  }
2198
2191
  function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2199
- console.log(t("cli.init.plan.title"));
2192
+ console.log(t("cli.install.plan.title"));
2200
2193
  console.log(formatInitModeBanner(options));
2201
- console.log(t("cli.init.plan.target", { target }));
2194
+ console.log(t("cli.install.plan.target", { target }));
2202
2195
  console.log(
2203
- t("cli.init.plan.actions", {
2196
+ t("cli.install.plan.actions", {
2204
2197
  bootstrap: yesNoLabel(!options.skipBootstrap),
2205
2198
  mcp: yesNoLabel(!options.skipMcp),
2206
2199
  hooks: yesNoLabel(!options.skipHooks),
@@ -2209,11 +2202,11 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2209
2202
  );
2210
2203
  const detected = supports.filter((support) => support.detected);
2211
2204
  console.log(
2212
- t("cli.init.plan.detected", {
2205
+ t("cli.install.plan.detected", {
2213
2206
  clients: detected.length > 0 ? detected.map((support) => support.label).join(", ") : t("cli.shared.none")
2214
2207
  })
2215
2208
  );
2216
- console.log(t("cli.init.plan.writes"));
2209
+ console.log(t("cli.install.plan.writes"));
2217
2210
  console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
2218
2211
  console.log(` - ${target}/.fabric/agents.meta.json`);
2219
2212
  console.log(` - ${target}/.fabric/events.jsonl`);
@@ -2223,18 +2216,18 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2223
2216
  function printInitCapabilitySummary(supports, stageResults, options) {
2224
2217
  const detected = supports.filter((support) => support.detected);
2225
2218
  if (detected.length === 0) {
2226
- console.log(t("cli.init.capabilities.none"));
2219
+ console.log(t("cli.install.capabilities.none"));
2227
2220
  return;
2228
2221
  }
2229
- console.log(t("cli.init.capabilities.title"));
2222
+ console.log(t("cli.install.capabilities.title"));
2230
2223
  const rows = detected.map((support) => toCapabilityRow(support, stageResults, options));
2231
2224
  const headers = {
2232
- client: t("cli.init.capabilities.header.client"),
2233
- bootstrap: t("cli.init.capabilities.header.bootstrap"),
2234
- mcp: t("cli.init.capabilities.header.mcp"),
2235
- hook: t("cli.init.capabilities.header.hook"),
2236
- skill: t("cli.init.capabilities.header.skill"),
2237
- followUp: t("cli.init.capabilities.header.follow-up")
2225
+ client: t("cli.install.capabilities.header.client"),
2226
+ bootstrap: t("cli.install.capabilities.header.bootstrap"),
2227
+ mcp: t("cli.install.capabilities.header.mcp"),
2228
+ hook: t("cli.install.capabilities.header.hook"),
2229
+ skill: t("cli.install.capabilities.header.skill"),
2230
+ followUp: t("cli.install.capabilities.header.follow-up")
2238
2231
  };
2239
2232
  const widths = {
2240
2233
  client: Math.max(displayWidth(headers.client), ...rows.map((row) => displayWidth(row.client))),
@@ -2252,8 +2245,8 @@ function printInitCapabilitySummary(supports, stageResults, options) {
2252
2245
  }
2253
2246
  function toCapabilityRow(support, stageResults, options) {
2254
2247
  const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
2255
- const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.init.capabilities.status.na");
2256
- const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.init.capabilities.status.na");
2248
+ const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.install.capabilities.status.na");
2249
+ const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.install.capabilities.status.na");
2257
2250
  const hook = capabilityInstallStatus(support, "hook");
2258
2251
  const skill = capabilityInstallStatus(support, "skill");
2259
2252
  return {
@@ -2262,14 +2255,14 @@ function toCapabilityRow(support, stageResults, options) {
2262
2255
  mcp,
2263
2256
  hook,
2264
2257
  skill,
2265
- followUp: hasInstalledCapability(support, "skill") ? t("cli.init.capabilities.follow-up.ready") : support.capabilities.skill ? t("cli.init.capabilities.follow-up.install") : t("cli.init.capabilities.follow-up.manual")
2258
+ followUp: hasInstalledCapability(support, "skill") ? t("cli.install.capabilities.follow-up.ready") : support.capabilities.skill ? t("cli.install.capabilities.follow-up.install") : t("cli.install.capabilities.follow-up.manual")
2266
2259
  };
2267
2260
  }
2268
2261
  function capabilityInstallStatus(support, capability) {
2269
2262
  if (!support.capabilities[capability]) {
2270
- return t("cli.init.capabilities.status.na");
2263
+ return t("cli.install.capabilities.status.na");
2271
2264
  }
2272
- return hasInstalledCapability(support, capability) ? t("cli.init.capabilities.status.installed") : t("cli.init.capabilities.status.supported");
2265
+ return hasInstalledCapability(support, capability) ? t("cli.install.capabilities.status.installed") : t("cli.install.capabilities.status.supported");
2273
2266
  }
2274
2267
  function hasInstalledCapability(support, capability) {
2275
2268
  return support.installedCapabilities?.[capability] === true;
@@ -2277,15 +2270,15 @@ function hasInstalledCapability(support, capability) {
2277
2270
  function capabilityStatus(disposition) {
2278
2271
  switch (disposition) {
2279
2272
  case "ran":
2280
- return t("cli.init.capabilities.status.ready");
2273
+ return t("cli.install.capabilities.status.ready");
2281
2274
  case "skipped":
2282
- return t("cli.init.capabilities.status.skipped");
2275
+ return t("cli.install.capabilities.status.skipped");
2283
2276
  case "failed":
2284
- return t("cli.init.capabilities.status.failed");
2277
+ return t("cli.install.capabilities.status.failed");
2285
2278
  case null:
2286
- return t("cli.init.capabilities.status.na");
2279
+ return t("cli.install.capabilities.status.na");
2287
2280
  default:
2288
- return t("cli.init.capabilities.status.ready");
2281
+ return t("cli.install.capabilities.status.ready");
2289
2282
  }
2290
2283
  }
2291
2284
  function formatCapabilityTableRow(row, widths) {
@@ -2311,21 +2304,21 @@ function formatCapabilityDivider(widths) {
2311
2304
  function formatInitReasonMessage(supports) {
2312
2305
  const detected = supports.filter((support) => support.detected);
2313
2306
  if (detected.some((support) => support.capabilities.skill)) {
2314
- return t("cli.init.reason-message.installable-body");
2307
+ return t("cli.install.reason-message.installable-body");
2315
2308
  }
2316
- return t("cli.init.reason-message.manual-body");
2309
+ return t("cli.install.reason-message.manual-body");
2317
2310
  }
2318
2311
  function yesNoLabel(value) {
2319
2312
  return value ? t("cli.shared.yes") : t("cli.shared.no");
2320
2313
  }
2321
2314
  function formatInitPathAction(path, action) {
2322
- return t("cli.init.created-path", { label: labelForInitWriteAction(action), path });
2315
+ return t("cli.install.created-path", { label: labelForInitWriteAction(action), path });
2323
2316
  }
2324
2317
  function formatAgentsMdAction(path, action) {
2325
2318
  if (action === "preserved") {
2326
- return t("cli.init.skipped-existing-path", { label: skippedLabel(), path });
2319
+ return t("cli.install.skipped-existing-path", { label: skippedLabel(), path });
2327
2320
  }
2328
- return t("cli.init.created-path", { label: createdLabel(), path });
2321
+ return t("cli.install.created-path", { label: createdLabel(), path });
2329
2322
  }
2330
2323
  function labelForInitWriteAction(action) {
2331
2324
  return action === "overwritten" ? overwrittenLabel() : createdLabel();
@@ -2343,18 +2336,18 @@ function reasonLabel() {
2343
2336
  return paint.human(t("cli.shared.reason"));
2344
2337
  }
2345
2338
  function overwrittenLabel() {
2346
- return paint.warn(t("cli.init.force.overwritten"));
2339
+ return paint.warn(t("cli.install.label.overwritten"));
2347
2340
  }
2348
2341
  function completedStageLabel() {
2349
- return paint.success(t("cli.init.stages.completed"));
2342
+ return paint.success(t("cli.install.stages.completed"));
2350
2343
  }
2351
2344
  function skippedStageLabel() {
2352
- return paint.muted(t("cli.init.stages.skipped"));
2345
+ return paint.muted(t("cli.install.stages.skipped"));
2353
2346
  }
2354
2347
  function failedStageLabel() {
2355
- return paint.error(t("cli.init.stages.failed"));
2348
+ return paint.error(t("cli.install.stages.failed"));
2356
2349
  }
2357
- function writeStderr2(message) {
2350
+ function writeStderr(message) {
2358
2351
  process.stderr.write(`${message}
2359
2352
  `);
2360
2353
  }
@@ -2362,12 +2355,12 @@ export {
2362
2355
  buildInitExecutionPlan,
2363
2356
  buildInitFabricPlan,
2364
2357
  createDefaultInitWizardAdapter,
2365
- init_default as default,
2358
+ install_default as default,
2366
2359
  detectPackageManager,
2367
2360
  executeInitExecutionPlan,
2368
2361
  executeInitFabricPlan,
2369
- initCommand,
2370
2362
  initFabric,
2363
+ installCommand,
2371
2364
  resolveInitExecutionPlanWithWizard,
2372
2365
  runInitCommand,
2373
2366
  shouldUseInitWizard