@fenglimg/fabric-cli 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -5
  3. package/dist/chunk-BATF4PEJ.js +361 -0
  4. package/dist/{chunk-OBQU6NHO.js → chunk-COI5VDFU.js} +0 -18
  5. package/dist/chunk-D25XJ4BC.js +880 -0
  6. package/dist/chunk-MF3OTILQ.js +544 -0
  7. package/dist/chunk-PWLW3B57.js +18 -0
  8. package/dist/config-XJIPZNUP.js +13 -0
  9. package/dist/doctor-EJDSEJSS.js +810 -0
  10. package/dist/index.js +15 -8
  11. package/dist/{init-BIRSIOXO.js → install-EKWMFLUU.js} +622 -711
  12. package/dist/metrics-ACEQFPDU.js +122 -0
  13. package/dist/onboard-coverage-MFCAEBDO.js +220 -0
  14. package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-FC6P3WFE.js} +34 -28
  15. package/dist/uninstall-MH7ZIB6M.js +1064 -0
  16. package/package.json +30 -5
  17. package/templates/hooks/cite-policy-evict.cjs +231 -0
  18. package/templates/hooks/configs/README.md +29 -6
  19. package/templates/hooks/configs/claude-code.json +14 -3
  20. package/templates/hooks/configs/codex-hooks.json +6 -3
  21. package/templates/hooks/configs/cursor-hooks.json +8 -10
  22. package/templates/hooks/fabric-hint.cjs +833 -105
  23. package/templates/hooks/knowledge-hint-broad.cjs +509 -135
  24. package/templates/hooks/knowledge-hint-narrow.cjs +791 -26
  25. package/templates/hooks/lib/banner-i18n.cjs +309 -0
  26. package/templates/hooks/lib/cite-contract-reminder.cjs +173 -0
  27. package/templates/hooks/lib/cite-line-parser.cjs +158 -0
  28. package/templates/hooks/lib/client-adapter.cjs +106 -0
  29. package/templates/hooks/lib/config-cache.cjs +107 -0
  30. package/templates/hooks/lib/state-store.cjs +84 -0
  31. package/templates/hooks/lib/summary-fallback.cjs +210 -0
  32. package/templates/skills/fabric-archive/SKILL.md +93 -419
  33. package/templates/skills/fabric-archive/ref/dry-run-scope.md +16 -0
  34. package/templates/skills/fabric-archive/ref/e5-cron-recap.md +58 -0
  35. package/templates/skills/fabric-archive/ref/i18n-policy.md +86 -0
  36. package/templates/skills/fabric-archive/ref/phase-0-range-resolution.md +156 -0
  37. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +218 -0
  38. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +62 -0
  39. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +68 -0
  40. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +108 -0
  41. package/templates/skills/fabric-archive/ref/phase-3-classify.md +63 -0
  42. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +78 -0
  43. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +89 -0
  44. package/templates/skills/fabric-archive/ref/rc-history.md +38 -0
  45. package/templates/skills/fabric-archive/ref/worked-examples.md +78 -0
  46. package/templates/skills/fabric-import/SKILL.md +75 -516
  47. package/templates/skills/fabric-import/ref/checkpoint-state.md +85 -0
  48. package/templates/skills/fabric-import/ref/i18n-policy.md +79 -0
  49. package/templates/skills/fabric-import/ref/output-contract.md +61 -0
  50. package/templates/skills/fabric-import/ref/phase-2-mining.md +213 -0
  51. package/templates/skills/fabric-import/ref/phase-3-dedup.md +75 -0
  52. package/templates/skills/fabric-import/ref/state-recovery.md +57 -0
  53. package/templates/skills/fabric-import/ref/worked-examples.md +127 -0
  54. package/templates/skills/fabric-review/SKILL.md +86 -284
  55. package/templates/skills/fabric-review/ref/askuserquestion-policy.md +66 -0
  56. package/templates/skills/fabric-review/ref/i18n-policy.md +111 -0
  57. package/templates/skills/fabric-review/ref/modify-flow.md +103 -0
  58. package/templates/skills/fabric-review/ref/output-contract.md +58 -0
  59. package/templates/skills/fabric-review/ref/per-mode-flows.md +155 -0
  60. package/templates/skills/fabric-review/ref/semantic-check.md +26 -0
  61. package/templates/skills/fabric-review/ref/worked-examples.md +95 -0
  62. package/templates/skills/lib/shared-policy.md +69 -0
  63. package/dist/chunk-6ICJICVU.js +0 -10
  64. package/dist/chunk-74SZWYPH.js +0 -658
  65. package/dist/chunk-EYIDD2YS.js +0 -1000
  66. package/dist/doctor-T7JWODKG.js +0 -282
  67. package/dist/hooks-Y74Y5LQS.js +0 -12
  68. package/dist/scan-LMK3UCWL.js +0 -22
  69. package/dist/serve-H554BHLG.js +0 -124
  70. package/templates/agents-md/AGENTS.md.template +0 -59
  71. package/templates/bootstrap/CLAUDE.md +0 -8
  72. package/templates/bootstrap/codex-AGENTS-header.md +0 -6
  73. package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
@@ -1,444 +1,253 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ClaudeCodeCLIWriter,
4
- CursorWriter,
5
- addArchiveSkillPointer,
6
- createServerEntry,
7
- hooksCommand,
3
+ installMcpClients
4
+ } from "./chunk-BATF4PEJ.js";
5
+ import {
6
+ cleanupDeprecatedSkills,
8
7
  installArchiveHintHook,
8
+ installCitePolicyEvictHook,
9
9
  installFabricArchiveSkill,
10
10
  installFabricImportSkill,
11
11
  installFabricReviewSkill,
12
- installHooks,
12
+ installHookLibs,
13
13
  installKnowledgeHintBroadHook,
14
14
  installKnowledgeHintNarrowHook,
15
+ installSharedSkillLib,
15
16
  mergeClaudeCodeHookConfig,
16
17
  mergeCodexHookConfig,
17
18
  mergeCursorHookConfig,
18
- normalizeConfigPath,
19
- writeJsonClientConfig
20
- } from "./chunk-74SZWYPH.js";
19
+ readFabricLanguagePreference,
20
+ writeClaudeBootstrapThinShell,
21
+ writeCodexBootstrapManagedBlock,
22
+ writeCursorBootstrapManagedBlock,
23
+ writeFabricAgentsSnapshot
24
+ } from "./chunk-D25XJ4BC.js";
21
25
  import {
22
- detectFramework,
23
- runInitScan
24
- } from "./chunk-EYIDD2YS.js";
26
+ detectClientSupports
27
+ } from "./chunk-MF3OTILQ.js";
25
28
  import {
26
29
  displayWidth,
27
30
  padEnd,
28
31
  paint
29
32
  } from "./chunk-WWNXR34K.js";
30
- import {
31
- t
32
- } from "./chunk-6ICJICVU.js";
33
33
  import {
34
34
  createDebugLogger,
35
35
  resolveDevMode
36
- } from "./chunk-OBQU6NHO.js";
36
+ } from "./chunk-COI5VDFU.js";
37
+ import {
38
+ t
39
+ } from "./chunk-PWLW3B57.js";
37
40
 
38
- // src/commands/init.ts
41
+ // src/commands/install.ts
39
42
  import { randomUUID } from "crypto";
40
- import { homedir as homedir4 } from "os";
43
+ import { homedir } from "os";
41
44
  import * as childProcess from "child_process";
42
- import { appendFileSync, existsSync as existsSync6, mkdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
43
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "path";
45
+ import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, rmSync, statSync as statSync4, writeFileSync } from "fs";
46
+ import { dirname, isAbsolute as isAbsolute3, join as join4, resolve as resolve3 } from "path";
44
47
  import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
45
48
  import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
46
- import { atomicWriteJson, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
47
- import { defineCommand as defineCommand2 } from "citty";
48
- import { checkLockOrThrow } from "@fenglimg/fabric-server";
49
-
50
- // src/commands/config.ts
51
- import { existsSync as existsSync4 } from "fs";
52
- import { readFile as readFile2 } from "fs/promises";
53
- import { resolve as resolve3 } from "path";
54
- import { fileURLToPath } from "url";
49
+ import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
55
50
  import { defineCommand } from "citty";
56
51
 
57
- // src/config/resolver.ts
58
- import { existsSync as existsSync3 } from "fs";
59
- import { join as join3 } from "path";
60
- import { homedir as homedir3 } from "os";
61
-
62
- // src/config/claude-code.ts
63
- import { existsSync } from "fs";
64
- import { join, resolve } from "path";
65
- import { homedir, platform } from "os";
66
- function getClaudeDesktopConfigPath() {
67
- const os = platform();
68
- if (os === "darwin") {
69
- return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
70
- }
71
- if (os === "win32") {
72
- return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
73
- }
74
- return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
75
- }
76
- var ClaudeCodeDesktopWriter = class {
77
- clientKind = "ClaudeCodeDesktop";
78
- configuredPath;
79
- constructor(configuredPath) {
80
- this.configuredPath = configuredPath;
81
- }
82
- async detect(_workspaceRoot, overridePath) {
83
- const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
84
- return existsSync(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
85
- }
86
- async write(serverPath, workspaceRoot, overridePath) {
87
- const configPath = await this.detect(workspaceRoot, overridePath);
88
- if (configPath === null) {
89
- return;
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(() => installSharedSkillLib(normalizedTarget)));
63
+ results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
64
+ results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
65
+ results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
66
+ results.push(...await runStep(() => installCitePolicyEvictHook(normalizedTarget)));
67
+ results.push(...await runStep(() => installHookLibs(normalizedTarget)));
68
+ results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
69
+ results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
70
+ results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
71
+ results.push(await runSingleStep("bootstrap-snapshot", () => writeFabricAgentsSnapshot(normalizedTarget)));
72
+ results.push(await runSingleStep("bootstrap-claude", () => writeClaudeBootstrapThinShell(normalizedTarget)));
73
+ results.push(await runSingleStep("bootstrap-codex", () => writeCodexBootstrapManagedBlock(normalizedTarget)));
74
+ results.push(await runSingleStep("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(normalizedTarget)));
75
+ results.push(...validateHookPaths(normalizedTarget));
76
+ return summarizeResults(results);
77
+ }
78
+ function validateHookPaths(projectRoot) {
79
+ const scripts = [
80
+ { stepSuffix: "", hookFile: "fabric-hint.cjs" },
81
+ { stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
82
+ { stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
83
+ ];
84
+ const clients = [
85
+ {
86
+ client: "claude",
87
+ configRel: join(".claude", "settings.json"),
88
+ hookDir: join(".claude", "hooks")
89
+ },
90
+ {
91
+ client: "codex",
92
+ configRel: join(".codex", "hooks.json"),
93
+ hookDir: join(".codex", "hooks")
94
+ },
95
+ {
96
+ client: "cursor",
97
+ configRel: join(".cursor", "hooks.json"),
98
+ hookDir: join(".cursor", "hooks")
90
99
  }
91
- await writeJsonClientConfig(configPath, {
92
- command: process.execPath,
93
- args: [serverPath]
94
- });
95
- }
96
- };
97
-
98
- // src/config/toml.ts
99
- import { existsSync as existsSync2 } from "fs";
100
- import { mkdir, readFile } from "fs/promises";
101
- import { dirname, join as join2, resolve as resolve2 } from "path";
102
- import { homedir as homedir2 } from "os";
103
- import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
104
- function expandHome(filePath) {
105
- if (filePath === "~") {
106
- return homedir2();
107
- }
108
- if (filePath.startsWith("~/")) {
109
- return join2(homedir2(), filePath.slice(2));
110
- }
111
- return filePath;
112
- }
113
- function escapeTomlString(value) {
114
- return JSON.stringify(value);
115
- }
116
- function serializeTomlStringArray(values) {
117
- return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
118
- }
119
- function serializeTomlInlineTable(values) {
120
- const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
121
- return `{ ${entries.join(", ")} }`;
122
- }
123
- function serializeCodexServerBlock(serverName, serverEntry) {
124
- const lines = [
125
- `[mcp_servers.${serverName}]`,
126
- `command = ${escapeTomlString(serverEntry.command)}`,
127
- `args = ${serializeTomlStringArray(serverEntry.args)}`
128
100
  ];
129
- if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
130
- lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
101
+ const results = [];
102
+ for (const { client, configRel, hookDir } of clients) {
103
+ const configPath = resolve(projectRoot, configRel);
104
+ if (!existsSync(configPath)) {
105
+ results.push({
106
+ step: `hook-validate-${client}`,
107
+ path: configPath,
108
+ status: "skipped",
109
+ message: "missing-config"
110
+ });
111
+ continue;
112
+ }
113
+ for (const { stepSuffix, hookFile } of scripts) {
114
+ const expectedHookPath = resolve(projectRoot, hookDir, hookFile);
115
+ const expectedHookRel = join(hookDir, hookFile);
116
+ const step = `hook-validate-${client}${stepSuffix}`;
117
+ if (!existsSync(expectedHookPath)) {
118
+ results.push({
119
+ step,
120
+ path: expectedHookPath,
121
+ status: "error",
122
+ message: `hook script missing: ${expectedHookRel}`
123
+ });
124
+ continue;
125
+ }
126
+ results.push({ step, path: expectedHookPath, status: "skipped", message: "ok" });
127
+ }
131
128
  }
132
- return `${lines.join("\n")}
133
- `;
129
+ return results;
134
130
  }
135
- function trimTrailingBlankLines(value) {
136
- return value.replace(/\s+$/u, "");
137
- }
138
- function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
139
- const block = serializeCodexServerBlock(serverName, serverEntry);
140
- const normalized = rawConfig.replace(/\r\n/g, "\n");
141
- const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
142
- const currentPattern = new RegExp(
143
- String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
144
- "g"
145
- );
146
- const withoutLegacy = normalized.replace(legacyPattern, "");
147
- const withoutExisting = withoutLegacy.replace(currentPattern, "");
148
- const trimmed = trimTrailingBlankLines(withoutExisting);
149
- if (trimmed.length === 0) {
150
- return block;
131
+ async function runStep(fn) {
132
+ try {
133
+ return await fn();
134
+ } catch (error) {
135
+ return [
136
+ {
137
+ step: "hook-install",
138
+ path: "",
139
+ status: "error",
140
+ message: error instanceof Error ? error.message : String(error)
141
+ }
142
+ ];
151
143
  }
152
- return `${trimmed}
153
-
154
- ${block}`;
155
144
  }
156
- async function readTomlConfigText(configPath) {
145
+ async function runSingleStep(step, fn) {
157
146
  try {
158
- return await readFile(configPath, "utf8");
147
+ return await fn();
159
148
  } catch (error) {
160
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
161
- return "";
162
- }
163
- throw error;
149
+ return {
150
+ step,
151
+ path: "",
152
+ status: "error",
153
+ message: error instanceof Error ? error.message : String(error)
154
+ };
164
155
  }
165
156
  }
166
- var CodexTOMLConfigWriter = class {
167
- clientKind = "CodexCLI";
168
- configuredPath;
169
- constructor(configuredPath) {
170
- this.configuredPath = configuredPath;
171
- }
172
- async detect(_workspaceRoot, overridePath) {
173
- const explicitPath = overridePath ?? this.configuredPath;
174
- if (explicitPath !== void 0) {
175
- return resolve2(expandHome(explicitPath));
157
+ function summarizeResults(results) {
158
+ const installed = [];
159
+ const skipped = [];
160
+ const errors = [];
161
+ for (const r of results) {
162
+ switch (r.status) {
163
+ case "written":
164
+ installed.push(r.path);
165
+ break;
166
+ case "skipped":
167
+ skipped.push(r.path);
168
+ break;
169
+ case "error":
170
+ errors.push(`${r.step} ${r.path}: ${r.message ?? "unknown error"}`);
171
+ break;
176
172
  }
177
- const codexDir = join2(homedir2(), ".codex");
178
- return existsSync2(codexDir) ? resolve2(join2(codexDir, "config.toml")) : null;
179
173
  }
180
- async write(serverPath, workspaceRoot, overridePath) {
181
- const configPath = await this.detect(workspaceRoot, overridePath);
182
- if (configPath === null) {
183
- return;
184
- }
185
- const rawConfig = await readTomlConfigText(configPath);
186
- const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
187
- await mkdir(dirname(configPath), { recursive: true });
188
- await atomicWriteText(configPath, nextConfig);
174
+ return { installed, skipped, errors };
175
+ }
176
+ function normalizeTarget(targetInput) {
177
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
178
+ }
179
+ function assertExistingDirectory(target) {
180
+ if (!existsSync(target) || !statSync(target).isDirectory()) {
181
+ throw new Error(t("cli.shared.target-invalid", { target }));
189
182
  }
190
- };
191
-
192
- // src/config/resolver.ts
193
- import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
194
- function hasExplicitPath(clientPaths, key) {
195
- return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
196
- }
197
- function addIfDetected(writers, detected, createWriter, configuredPath) {
198
- if (configuredPath !== void 0 || detected) {
199
- writers.push(createWriter(configuredPath));
200
- }
201
- }
202
- function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
203
- const clientPaths = fabricConfig.clientPaths;
204
- const writers = [];
205
- const claudeMcpScope = opts.claudeMcpScope ?? "project";
206
- addIfDetected(
207
- writers,
208
- existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude")),
209
- (configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
210
- hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
211
- );
212
- addIfDetected(
213
- writers,
214
- existsSync3(getClaudeDesktopConfigPath()),
215
- (configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
216
- hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
217
- );
218
- addIfDetected(
219
- writers,
220
- existsSync3(join3(workspaceRoot, ".cursor")),
221
- (configuredPath) => new CursorWriter(configuredPath),
222
- hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
223
- );
224
- addIfDetected(
225
- writers,
226
- existsSync3(join3(homedir3(), ".codex")),
227
- (configuredPath) => new CodexTOMLConfigWriter(configuredPath),
228
- hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
229
- );
230
- return writers;
231
- }
232
- function detectClientSupports(workspaceRoot, fabricConfig = {}) {
233
- const clientPaths = fabricConfig.clientPaths;
234
- const claudeDetected = existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude"));
235
- const claudeDesktopDetected = existsSync3(getClaudeDesktopConfigPath());
236
- const cursorDetected = existsSync3(join3(workspaceRoot, ".cursor"));
237
- const codexDetected = existsSync3(join3(homedir3(), ".codex"));
238
- return [
239
- {
240
- clientKind: "ClaudeCodeCLI",
241
- label: "Claude Code CLI",
242
- detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
243
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
244
- configPath: "project .claude/settings.json",
245
- capabilities: {
246
- bootstrap: true,
247
- mcp: true,
248
- hook: true,
249
- skill: true
250
- },
251
- installedCapabilities: {
252
- hook: true,
253
- skill: true
254
- }
255
- },
256
- {
257
- clientKind: "ClaudeCodeDesktop",
258
- label: "Claude Code Desktop",
259
- detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
260
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
261
- configPath: "desktop Claude config",
262
- capabilities: {
263
- bootstrap: true,
264
- mcp: true,
265
- hook: false,
266
- skill: false
267
- }
268
- },
269
- {
270
- clientKind: "Cursor",
271
- label: "Cursor",
272
- detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
273
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
274
- configPath: ".cursor/mcp.json",
275
- capabilities: {
276
- bootstrap: true,
277
- mcp: true,
278
- hook: false,
279
- skill: false
280
- }
281
- },
282
- {
283
- clientKind: "CodexCLI",
284
- label: "Codex CLI",
285
- detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
286
- bootstrapTargetPath: ".fabric/bootstrap/README.md",
287
- configPath: "~/.codex/config.toml",
288
- capabilities: {
289
- bootstrap: true,
290
- mcp: true,
291
- hook: true,
292
- skill: true
293
- },
294
- installedCapabilities: {
295
- hook: existsSync3(join3(workspaceRoot, ".codex", "hooks.json")),
296
- // v2/rc.2: v1 client-side init skill removed; skill-installation probes
297
- // will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
298
- // fabric-review, fabric-import). Until then there is nothing to probe.
299
- skill: false
300
- }
301
- }
302
- ];
303
183
  }
304
184
 
305
- // src/commands/config.ts
306
- var CLIENT_ALIASES = {
307
- claude: "ClaudeCodeCLI",
308
- claudecodecli: "ClaudeCodeCLI",
309
- "claude-code-cli": "ClaudeCodeCLI",
310
- claudecli: "ClaudeCodeCLI",
311
- claudecodedesktop: "ClaudeCodeDesktop",
312
- "claude-code-desktop": "ClaudeCodeDesktop",
313
- claudedesktop: "ClaudeCodeDesktop",
314
- cursor: "Cursor",
315
- codexcli: "CodexCLI",
316
- "codex-cli": "CodexCLI",
317
- codex: "CodexCLI"
318
- };
319
- function parseClientFilter(value) {
320
- if (value === void 0 || value.trim().length === 0) {
321
- return null;
322
- }
323
- const clients = /* @__PURE__ */ new Set();
324
- for (const rawClient of value.split(",")) {
325
- const alias = rawClient.trim().toLowerCase();
326
- const clientKind = CLIENT_ALIASES[alias];
327
- if (clientKind === void 0) {
328
- throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
185
+ // src/lib/detect-language.ts
186
+ import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
187
+ import { join as join2 } from "path";
188
+ function detectExistingLanguage(target) {
189
+ const ZH_CN_RATIO_THRESHOLD = 0.3;
190
+ const samples = [];
191
+ const readmePath = join2(target, "README.md");
192
+ if (existsSync2(readmePath)) {
193
+ try {
194
+ samples.push(readFileSync(readmePath, "utf8"));
195
+ } catch {
329
196
  }
330
- clients.add(clientKind);
331
197
  }
332
- return clients;
333
- }
334
- async function loadFabricConfig(workspaceRoot) {
335
- const configPath = resolve3(workspaceRoot, "fabric.config.json");
336
- if (!existsSync4(configPath)) {
337
- return {};
338
- }
339
- const parsed = JSON.parse(await readFile2(configPath, "utf8"));
340
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
341
- throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
342
- }
343
- return parsed;
344
- }
345
- function resolveServerPath(override) {
346
- if (override) return override;
347
- if (process.env.FAB_SERVER_PATH) return resolve3(process.env.FAB_SERVER_PATH);
348
- return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
349
- }
350
- function writeStderr(message) {
351
- process.stderr.write(`${message}
352
- `);
353
- }
354
- var configCmd = defineCommand({
355
- meta: {
356
- name: "config",
357
- description: t("cli.config.description")
358
- },
359
- subCommands: {
360
- hooks: hooksCommand,
361
- install: defineCommand({
362
- meta: {
363
- name: "install",
364
- description: t("cli.config.install.description")
365
- },
366
- args: {
367
- clients: {
368
- type: "string",
369
- description: t("cli.config.install.args.clients.description")
370
- },
371
- "dry-run": {
372
- type: "boolean",
373
- description: t("cli.config.install.args.dry-run.description"),
374
- default: false
375
- }
376
- },
377
- async run({ args }) {
378
- const selectedClients = parseClientFilter(args.clients);
379
- const result = await installMcpClients(process.cwd(), {
380
- clients: selectedClients === null ? void 0 : Array.from(selectedClients),
381
- dryRun: args["dry-run"]
382
- });
383
- if (result.details.length === 0) {
384
- writeStderr(t("cli.config.install.no-configs"));
385
- return;
386
- }
387
- for (const detail of result.details) {
388
- if (detail.action === "skipped") {
389
- writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
390
- continue;
391
- }
392
- if (detail.action === "dry-run" && detail.path !== null) {
393
- writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
394
- continue;
395
- }
396
- if (detail.path !== null) {
397
- writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
198
+ const docsDir = join2(target, "docs");
199
+ if (existsSync2(docsDir)) {
200
+ try {
201
+ const stat = statSync2(docsDir);
202
+ if (stat.isDirectory()) {
203
+ for (const entry of readdirSync(docsDir, { withFileTypes: true })) {
204
+ if (!entry.isFile()) continue;
205
+ if (!/\.(md|mdx|txt)$/iu.test(entry.name)) continue;
206
+ try {
207
+ samples.push(readFileSync(join2(docsDir, entry.name), "utf8"));
208
+ } catch {
398
209
  }
399
210
  }
400
211
  }
401
- })
402
- }
403
- });
404
- async function installMcpClients(target, options = {}) {
405
- const workspaceRoot = resolve3(target);
406
- const fabricConfig = await loadFabricConfig(workspaceRoot);
407
- const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
408
- const serverPath = resolveServerPath(options.localServerPath);
409
- const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
410
- (writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
411
- );
412
- const installed = [];
413
- const skipped = [];
414
- const details = [];
415
- for (const writer of writers) {
416
- const configPath = await writer.detect(workspaceRoot);
417
- if (configPath === null) {
418
- skipped.push(writer.clientKind);
419
- details.push({ client: writer.clientKind, path: null, action: "skipped" });
420
- continue;
212
+ } catch {
421
213
  }
422
- if (options.dryRun) {
423
- skipped.push(writer.clientKind);
424
- details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
425
- continue;
214
+ }
215
+ if (samples.length === 0) {
216
+ return "en";
217
+ }
218
+ let cjkCount = 0;
219
+ let asciiLetterCount = 0;
220
+ for (const sample of samples) {
221
+ for (const ch of sample) {
222
+ const code = ch.codePointAt(0) ?? 0;
223
+ if (code >= 19968 && code <= 40959) {
224
+ cjkCount += 1;
225
+ } else if (code >= 65 && code <= 90 || code >= 97 && code <= 122) {
226
+ asciiLetterCount += 1;
227
+ }
426
228
  }
427
- await writer.write(serverPath, workspaceRoot);
428
- installed.push(writer.clientKind);
429
- details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
430
229
  }
431
- return { installed, skipped, details };
230
+ const denominator = cjkCount + asciiLetterCount;
231
+ if (denominator === 0) {
232
+ return "en";
233
+ }
234
+ const ratio = cjkCount / denominator;
235
+ return ratio > ZH_CN_RATIO_THRESHOLD ? "zh-CN-hybrid" : "en";
432
236
  }
433
237
 
434
238
  // src/scanner/forensic.ts
435
239
  import { execFileSync } from "child_process";
436
- import { existsSync as existsSync5, readdirSync, readFileSync, statSync } from "fs";
240
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
437
241
  import { createRequire } from "module";
438
- import { basename, extname, isAbsolute, join as join4, posix, relative, resolve as resolve4, sep } from "path";
242
+ import { basename, extname, isAbsolute as isAbsolute2, join as join3, posix, relative, resolve as resolve2, sep } from "path";
439
243
  import {
440
244
  forensicReportSchema
441
245
  } from "@fenglimg/fabric-shared";
246
+
247
+ // src/scanner/detector.ts
248
+ import { detectFramework } from "@fenglimg/fabric-shared/node";
249
+
250
+ // src/scanner/forensic.ts
442
251
  var require2 = createRequire(import.meta.url);
443
252
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
444
253
  ".fabric",
@@ -521,7 +330,7 @@ var parserInitPromise = null;
521
330
  var languagePromiseByKind = {};
522
331
  var parserBundlePromiseByKind = {};
523
332
  async function buildForensicReport(targetInput) {
524
- const target = normalizeTarget(targetInput);
333
+ const target = normalizeTarget2(targetInput);
525
334
  const framework = detectFramework(target);
526
335
  const topology = buildTopology(target);
527
336
  const entryPoints = collectEntryPoints(target, topology.files);
@@ -533,7 +342,7 @@ async function buildForensicReport(targetInput) {
533
342
  const report = {
534
343
  version: "1.0",
535
344
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
536
- generated_by: `fab-cli@${getCliVersion()}`,
345
+ generated_by: `fabric-cli@${getCliVersion()}`,
537
346
  target,
538
347
  project_name: readProjectName(target),
539
348
  framework,
@@ -557,11 +366,11 @@ async function buildForensicReport(targetInput) {
557
366
  }
558
367
  return validation.data;
559
368
  }
560
- function normalizeTarget(targetInput) {
561
- return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
369
+ function normalizeTarget2(targetInput) {
370
+ return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
562
371
  }
563
372
  function buildTopology(root) {
564
- assertExistingDirectory(root);
373
+ assertExistingDirectory2(root);
565
374
  const byExt = {};
566
375
  const keyDirs = /* @__PURE__ */ new Set();
567
376
  const files = [];
@@ -573,8 +382,8 @@ function buildTopology(root) {
573
382
  if (current === void 0) {
574
383
  continue;
575
384
  }
576
- for (const entry of readdirSync(current, { withFileTypes: true })) {
577
- const absolutePath = join4(current, entry.name);
385
+ for (const entry of readdirSync2(current, { withFileTypes: true })) {
386
+ const absolutePath = join3(current, entry.name);
578
387
  const relativePath = toPosixPath(relative(root, absolutePath));
579
388
  if (relativePath.length === 0) {
580
389
  continue;
@@ -594,7 +403,7 @@ function buildTopology(root) {
594
403
  if (!entry.isFile()) {
595
404
  continue;
596
405
  }
597
- const stats = statSync(absolutePath);
406
+ const stats = statSync3(absolutePath);
598
407
  const extension = extname(entry.name) || "[none]";
599
408
  byExt[extension] = (byExt[extension] ?? 0) + 1;
600
409
  totalFiles += 1;
@@ -612,8 +421,8 @@ function buildTopology(root) {
612
421
  files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
613
422
  };
614
423
  }
615
- function assertExistingDirectory(target) {
616
- if (!existsSync5(target) || !statSync(target).isDirectory()) {
424
+ function assertExistingDirectory2(target) {
425
+ if (!existsSync3(target) || !statSync3(target).isDirectory()) {
617
426
  throw new Error(`Target must be an existing directory: ${target}`);
618
427
  }
619
428
  }
@@ -662,7 +471,7 @@ function getEntryPointReason(relativePath) {
662
471
  async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
663
472
  const samples = [];
664
473
  for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
665
- const absolutePath = join4(target, ...entryPoint.path.split("/"));
474
+ const absolutePath = join3(target, ...entryPoint.path.split("/"));
666
475
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
667
476
  const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
668
477
  frameworkKind,
@@ -682,7 +491,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
682
491
  }
683
492
  function readFirstLines(path, lineLimit) {
684
493
  try {
685
- const lines = readFileSync(path, "utf8").split(/\r?\n/);
494
+ const lines = readFileSync2(path, "utf8").split(/\r?\n/);
686
495
  if (lines.at(-1) === "") {
687
496
  lines.pop();
688
497
  }
@@ -699,12 +508,12 @@ function readFirstLines(path, lineLimit) {
699
508
  }
700
509
  }
701
510
  function readPackageDependencies(target) {
702
- const packageJsonPath = join4(target, "package.json");
703
- if (!existsSync5(packageJsonPath)) {
511
+ const packageJsonPath = join3(target, "package.json");
512
+ if (!existsSync3(packageJsonPath)) {
704
513
  return /* @__PURE__ */ new Map();
705
514
  }
706
515
  try {
707
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
516
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
708
517
  return new Map([
709
518
  ...Object.entries(packageJson.dependencies ?? {}),
710
519
  ...Object.entries(packageJson.devDependencies ?? {}),
@@ -1040,16 +849,16 @@ function scoreFrameworkConfidence(input) {
1040
849
  return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
1041
850
  }
1042
851
  function readReadmeInfo(target) {
1043
- const readmePath = join4(target, "README.md");
1044
- const hasContributing = existsSync5(join4(target, "CONTRIBUTING.md"));
1045
- if (!existsSync5(readmePath)) {
852
+ const readmePath = join3(target, "README.md");
853
+ const hasContributing = existsSync3(join3(target, "CONTRIBUTING.md"));
854
+ if (!existsSync3(readmePath)) {
1046
855
  return {
1047
856
  quality: "missing",
1048
857
  line_count: 0,
1049
858
  has_contributing: hasContributing
1050
859
  };
1051
860
  }
1052
- const readme = readFileSync(readmePath, "utf8");
861
+ const readme = readFileSync2(readmePath, "utf8");
1053
862
  const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
1054
863
  return {
1055
864
  quality: wordCount >= 200 ? "ok" : "stub",
@@ -1527,10 +1336,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
1527
1336
  return recommendations;
1528
1337
  }
1529
1338
  function readProjectName(target) {
1530
- const packageJsonPath = join4(target, "package.json");
1531
- if (existsSync5(packageJsonPath)) {
1339
+ const packageJsonPath = join3(target, "package.json");
1340
+ if (existsSync3(packageJsonPath)) {
1532
1341
  try {
1533
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1342
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
1534
1343
  if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
1535
1344
  return packageJson.name;
1536
1345
  }
@@ -1541,7 +1350,7 @@ function readProjectName(target) {
1541
1350
  return basename(target);
1542
1351
  }
1543
1352
  function getCliVersion() {
1544
- return true ? "2.0.0" : "unknown";
1353
+ return true ? "2.0.1" : "unknown";
1545
1354
  }
1546
1355
  function sortRecord(record) {
1547
1356
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1550,109 +1359,139 @@ function toPosixPath(path) {
1550
1359
  return path.split(sep).join("/");
1551
1360
  }
1552
1361
 
1553
- // src/commands/init.ts
1554
- var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
1555
-
1556
- This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
1557
-
1558
- Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
1559
- Run \`fabric doctor\` to verify state.
1560
-
1561
- See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
1562
- `;
1563
- var LOCAL_FABRIC_SERVER_PATH = join5("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1362
+ // src/commands/install.ts
1363
+ var LOCAL_FABRIC_SERVER_PATH = join4("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
1564
1364
  var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
1565
1365
  var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
1566
- var initCommand = defineCommand2({
1366
+ var installCommand = defineCommand({
1567
1367
  meta: {
1568
- name: "init",
1569
- description: t("cli.init.description")
1368
+ name: "install",
1369
+ description: t("cli.install.description")
1570
1370
  },
1571
1371
  args: {
1572
- target: {
1573
- type: "string",
1574
- description: t("cli.init.args.target.description")
1575
- },
1576
1372
  debug: {
1577
1373
  type: "boolean",
1578
- description: t("cli.init.args.debug.description"),
1374
+ description: t("cli.install.args.debug.description"),
1579
1375
  default: false
1580
1376
  },
1581
- force: {
1377
+ "dry-run": {
1582
1378
  type: "boolean",
1583
- description: t("cli.init.args.force.description"),
1379
+ description: t("cli.install.args.dry-run.description"),
1584
1380
  default: false
1585
1381
  },
1382
+ target: {
1383
+ type: "string",
1384
+ description: t("cli.install.args.target.description")
1385
+ },
1586
1386
  yes: {
1587
1387
  type: "boolean",
1588
- description: t("cli.init.args.yes.description"),
1388
+ description: t("cli.install.args.yes.description"),
1589
1389
  default: false
1590
1390
  },
1591
- plan: {
1391
+ "force-skills-only": {
1592
1392
  type: "boolean",
1593
- description: t("cli.init.args.plan.description"),
1393
+ description: t("cli.install.args.force-skills-only.description"),
1594
1394
  default: false
1595
1395
  },
1596
- reapply: {
1396
+ "force-hooks-only": {
1597
1397
  type: "boolean",
1598
- description: t("cli.init.args.reapply.description"),
1398
+ description: t("cli.install.args.force-hooks-only.description"),
1599
1399
  default: false
1600
- },
1601
- bootstrap: {
1602
- type: "boolean",
1603
- default: true,
1604
- negativeDescription: t("cli.init.args.no-bootstrap.description")
1605
- },
1606
- mcp: {
1607
- type: "boolean",
1608
- default: true,
1609
- negativeDescription: t("cli.init.args.no-mcp.description")
1610
- },
1611
- hooks: {
1612
- type: "boolean",
1613
- default: true,
1614
- negativeDescription: t("cli.init.args.no-hooks.description")
1615
- },
1616
- interactive: {
1617
- type: "boolean",
1618
- description: t("cli.init.args.interactive.description"),
1619
- default: true
1620
- },
1621
- "mcp-install": {
1622
- type: "string",
1623
- default: "global",
1624
- description: t("cli.init.mcp.install.prompt")
1625
- },
1626
- scope: {
1627
- type: "string",
1628
- description: t("cli.init.mcp.scope.description")
1629
1400
  }
1630
1401
  },
1631
1402
  async run({ args }) {
1632
1403
  await runInitCommand(args);
1633
1404
  }
1634
1405
  });
1635
- var init_default = initCommand;
1406
+ var install_default = installCommand;
1407
+ async function runSkillsOnlyRefresh(targetInput) {
1408
+ const target = normalizeTarget3(targetInput);
1409
+ const metaPath = join4(target, ".fabric", "agents.meta.json");
1410
+ if (!existsSync4(metaPath)) {
1411
+ const message = t("cli.install.force-skills-only.uninitialised.message");
1412
+ const hint = t("cli.install.force-skills-only.uninitialised.hint");
1413
+ process.stderr.write(`${message}
1414
+ ${hint}
1415
+ `);
1416
+ process.exitCode = 1;
1417
+ return;
1418
+ }
1419
+ console.log(formatInitStageHeader(t("cli.install.force-skills-only.banner")));
1420
+ const results = [];
1421
+ results.push(...await cleanupDeprecatedSkills(target));
1422
+ results.push(...await installFabricArchiveSkill(target));
1423
+ results.push(...await installFabricReviewSkill(target));
1424
+ results.push(...await installFabricImportSkill(target));
1425
+ let written = 0;
1426
+ let skipped = 0;
1427
+ let errors = 0;
1428
+ for (const r of results) {
1429
+ if (r.status === "written") written += 1;
1430
+ else if (r.status === "skipped") skipped += 1;
1431
+ else if (r.status === "error") errors += 1;
1432
+ }
1433
+ console.log(
1434
+ t("cli.install.force-skills-only.summary", {
1435
+ written: String(written),
1436
+ skipped: String(skipped),
1437
+ errors: String(errors)
1438
+ })
1439
+ );
1440
+ if (errors > 0) {
1441
+ for (const r of results) {
1442
+ if (r.status === "error") {
1443
+ process.stderr.write(` ${r.step} ${r.path}: ${r.message ?? "error"}
1444
+ `);
1445
+ }
1446
+ }
1447
+ process.exitCode = 1;
1448
+ }
1449
+ }
1450
+ async function runHooksOnlyRefresh(targetInput) {
1451
+ const target = normalizeTarget3(targetInput);
1452
+ const metaPath = join4(target, ".fabric", "agents.meta.json");
1453
+ if (!existsSync4(metaPath)) {
1454
+ const message = t("cli.install.force-hooks-only.uninitialised.message");
1455
+ const hint = t("cli.install.force-hooks-only.uninitialised.hint");
1456
+ process.stderr.write(`${message}
1457
+ ${hint}
1458
+ `);
1459
+ process.exitCode = 1;
1460
+ return;
1461
+ }
1462
+ console.log(formatInitStageHeader(t("cli.install.force-hooks-only.banner")));
1463
+ const result = await installHooks(target);
1464
+ console.log(
1465
+ t("cli.install.force-hooks-only.summary", {
1466
+ written: String(result.installed.length),
1467
+ skipped: String(result.skipped.length),
1468
+ errors: String(result.errors.length)
1469
+ })
1470
+ );
1471
+ if (result.errors.length > 0) {
1472
+ for (const err of result.errors) {
1473
+ process.stderr.write(` ${err}
1474
+ `);
1475
+ }
1476
+ process.exitCode = 1;
1477
+ }
1478
+ }
1636
1479
  async function runInitCommand(args) {
1637
1480
  const logger = createDebugLogger(args.debug);
1638
1481
  const resolution = resolveDevMode(args.target, process.cwd());
1639
- const intent = resolveInitCliIntent(args, resolution.target);
1640
- if (args.reapply === true) {
1641
- checkLockOrThrow(intent.target, { force: args.force });
1482
+ if (args["force-skills-only"] === true) {
1483
+ await runSkillsOnlyRefresh(resolution.target);
1484
+ return;
1485
+ }
1486
+ if (args["force-hooks-only"] === true) {
1487
+ await runHooksOnlyRefresh(resolution.target);
1488
+ return;
1642
1489
  }
1490
+ const intent = resolveInitCliIntent(args, resolution.target);
1643
1491
  logger(`init target source: ${resolution.source}`);
1644
1492
  for (const step of resolution.chain) {
1645
1493
  logger(step);
1646
1494
  }
1647
- if (intent.options.planOnly) {
1648
- writeStderr2(t("cli.init.compat.plan"));
1649
- }
1650
- if (args.interactive === false) {
1651
- writeStderr2(t("cli.init.compat.interactive"));
1652
- }
1653
- if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
1654
- writeStderr2(t("cli.init.compat.legacy-stage-flags"));
1655
- }
1656
1495
  const supports = detectClientSupports(intent.target);
1657
1496
  const basePlan = await buildInitExecutionPlan({
1658
1497
  target: intent.target,
@@ -1662,81 +1501,107 @@ async function runInitCommand(args) {
1662
1501
  interactive: intent.interactiveSummary && !intent.wizardEnabled,
1663
1502
  supports
1664
1503
  });
1665
- const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
1504
+ const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, createDefaultInitWizardAdapter()) : basePlan;
1666
1505
  if (plan === null) {
1667
1506
  process.exitCode = 130;
1668
1507
  return;
1669
1508
  }
1670
1509
  const result = await executeInitExecutionPlan(plan);
1671
- try {
1672
- await maybeWriteImportSentinel({
1673
- target: intent.target,
1674
- planOnly: intent.options.planOnly === true,
1675
- wizardEnabled: intent.wizardEnabled,
1676
- terminalInteractive: intent.interactiveSummary
1677
- });
1678
- } catch {
1679
- }
1680
1510
  if (!intent.options.planOnly) {
1511
+ console.log("");
1512
+ console.log(t("cli.install.next-steps"));
1513
+ console.log("");
1681
1514
  console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
1682
1515
  }
1683
1516
  return result;
1684
1517
  }
1685
- async function maybeWriteImportSentinel(opts) {
1686
- if (opts.planOnly) return;
1687
- if (process.env.FABRIC_NONINTERACTIVE === "1") return;
1688
- if (!opts.wizardEnabled || !opts.terminalInteractive) return;
1689
- if (!Boolean(process.stdin.isTTY)) return;
1690
- const answer = await confirm({
1691
- message: "\u4E0B\u6B21\u5F00 AI \u65F6\u8BA9\u6211\u4ECE git log \u62BD\u66F4\u591A\u77E5\u8BC6\u5417?",
1692
- initialValue: true
1693
- });
1694
- if (isCancel(answer) || answer !== true) return;
1695
- const fabricDir = join5(opts.target, ".fabric");
1696
- const sentinelPath = join5(fabricDir, ".import-requested");
1697
- try {
1698
- if (!existsSync6(fabricDir)) {
1699
- mkdirSync(fabricDir, { recursive: true });
1700
- }
1701
- writeFileSync(sentinelPath, "", "utf8");
1702
- log.success("\u4E0B\u6B21\u5F00 AI \u4F1A\u770B\u5230\u63D0\u793A");
1703
- } catch {
1704
- }
1518
+ function writeDefaultFabricConfig(fabricDir, targetRoot) {
1519
+ const target = join4(fabricDir, "fabric-config.json");
1520
+ if (existsSync4(target)) return;
1521
+ const detectedLanguage = detectExistingLanguage(targetRoot);
1522
+ const FABRIC_CONFIG_DEFAULTS = {
1523
+ // Scan/import language policy. Fixated at init time by probing
1524
+ // README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
1525
+ // can edit `.fabric/fabric-config.json` to override. See
1526
+ // packages/shared/src/schemas/fabric-config.ts for the enum.
1527
+ fabric_language: detectedLanguage,
1528
+ // fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
1529
+ // since last knowledge_proposed event.
1530
+ archive_hint_hours: 24,
1531
+ // fabric-hint Stop hook cooldown after ANY signal fires, in hours.
1532
+ archive_hint_cooldown_hours: 12,
1533
+ // fabric-hint Stop hook Signal B (review): pending-count cutoff.
1534
+ review_hint_pending_count: 10,
1535
+ // fabric-hint Stop hook Signal B (review): pending-age cutoff in days.
1536
+ review_hint_pending_age_days: 7,
1537
+ // fabric-hint Stop hook Signal D (maintenance): days since last doctor.
1538
+ maintenance_hint_days: 14,
1539
+ // fabric-hint Stop hook Signal D (maintenance): cooldown between
1540
+ // reminders, in days.
1541
+ maintenance_hint_cooldown_days: 7,
1542
+ // fabric-hint Stop hook Signal A (archive): edit-count branch threshold;
1543
+ // PreToolUse fires recorded in .fabric/.cache/edit-counter since the
1544
+ // last knowledge_proposed event.
1545
+ archive_edit_threshold: 20,
1546
+ // fabric-hint Stop hook Signal C (import) + doctor lint #22: canonical
1547
+ // knowledge node count below this value flags an underseeded workspace.
1548
+ underseed_node_threshold: 10,
1549
+ // rc.9+ (skill-contract-fix B1): fabric-import first-run git-history
1550
+ // window in months. Default 60 captures the bulk of a mature repo's
1551
+ // signal in one pass; lower to 12-24 for fresh / small repos.
1552
+ import_window_first_run_months: 60,
1553
+ // rc.9+ (skill-contract-fix B1): fabric-import rerun window in months.
1554
+ // Default 2; raise to 6 if the workspace pauses imports for long stretches.
1555
+ import_window_rerun_months: 2,
1556
+ // rc.9+ (skill-contract-fix B1): hard cap on pending entries produced
1557
+ // per fabric-import invocation. Default 10 matches one-sitting triage.
1558
+ import_max_pending_per_run: 10,
1559
+ // rc.9+ (skill-contract-fix B1): hard cap on commits scanned per
1560
+ // fabric-import invocation. Default 500 covers ~2 months of typical churn.
1561
+ import_max_commits_scan: 500,
1562
+ // rc.9+ (skill-contract-fix B1): canonical-node count above which
1563
+ // fabric-import suggests review over importing more. Default 50.
1564
+ import_skip_canonical_threshold: 50,
1565
+ // rc.9+ (skill-contract-fix B1): max candidates per fabric-archive batch.
1566
+ // Default 8 keeps each batch reviewable in one sitting.
1567
+ archive_max_candidates_per_batch: 8,
1568
+ // rc.9+ (skill-contract-fix B1): max recently-touched paths in
1569
+ // fabric-archive's relevance digest. Default 20.
1570
+ archive_max_recent_paths: 20,
1571
+ // rc.9+ (skill-contract-fix B1): max prior fabric-archive sessions
1572
+ // summarised in the digest the skill loads on start. Default 10.
1573
+ archive_digest_max_sessions: 10,
1574
+ // rc.9+ (skill-contract-fix B1): max review results per topic cluster
1575
+ // in fabric-review. Default 8.
1576
+ review_topic_result_cap: 8,
1577
+ // rc.9+ (skill-contract-fix B1): age (days) above which a pending entry
1578
+ // is considered stale by fabric-review. Default 14.
1579
+ review_stale_pending_days: 14
1580
+ };
1581
+ mkdirSync(fabricDir, { recursive: true });
1582
+ writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
1583
+ log.info(
1584
+ `Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
1585
+ );
1705
1586
  }
1706
1587
  function resolveInitCliIntent(args, targetInput) {
1707
- const target = normalizeTarget2(targetInput);
1708
- const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
1709
- const claudeMcpScope = resolveClaudeMcpScope(args.scope);
1588
+ const target = normalizeTarget3(targetInput);
1589
+ const mcpInstallMode = "global";
1590
+ const claudeMcpScope = "project";
1710
1591
  const terminalInteractive = isInteractiveInit();
1711
- const planOnly = args.plan === true;
1712
- const reapply = args.reapply === true;
1592
+ const planOnly = args["dry-run"] === true;
1713
1593
  const options = {
1714
- force: reapply ? true : args.force,
1715
- skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
1716
- skipMcp: args.mcp === false ? true : args.skipMcp,
1717
- skipHooks: args.hooks === false ? true : args.skipHooks,
1718
- planOnly,
1719
- reapply
1594
+ planOnly
1720
1595
  };
1721
1596
  return {
1722
1597
  target,
1723
1598
  options,
1724
1599
  mcpInstallMode,
1725
1600
  claudeMcpScope,
1726
- interactiveSummary: args.interactive !== false && terminalInteractive,
1601
+ interactiveSummary: terminalInteractive,
1727
1602
  wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
1728
1603
  };
1729
1604
  }
1730
- function resolveClaudeMcpScope(raw) {
1731
- if (raw === void 0 || raw === "project") {
1732
- return "project";
1733
- }
1734
- if (raw === "user") {
1735
- return "user";
1736
- }
1737
- writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
1738
- return "project";
1739
- }
1740
1605
  async function buildInitExecutionPlan(input) {
1741
1606
  const options = input.options ?? {};
1742
1607
  const scaffold = await buildInitFabricPlan(input.target, options);
@@ -1773,17 +1638,17 @@ async function buildInitExecutionPlan(input) {
1773
1638
  };
1774
1639
  }
1775
1640
  async function executeInitExecutionPlan(plan) {
1776
- if (plan.options.force) {
1777
- writeStderr2(t("cli.init.force.warning", { path: plan.target }));
1778
- }
1779
- if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
1780
- writeStderr2(formatInitModeBanner(plan.options));
1781
- }
1782
1641
  if (plan.interactive) {
1783
1642
  printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
1784
1643
  }
1644
+ const scaffoldStates = [
1645
+ { path: plan.scaffold.metaPath, state: plan.scaffold.metaState },
1646
+ { path: plan.scaffold.eventsPath, state: plan.scaffold.eventsState },
1647
+ { path: plan.scaffold.forensicPath, state: plan.scaffold.forensicState }
1648
+ ];
1785
1649
  if (plan.options.planOnly) {
1786
1650
  printInitPlanPreview(plan);
1651
+ printInitDiffStateTable(scaffoldStates);
1787
1652
  return {
1788
1653
  plan,
1789
1654
  created: buildPlanOnlyScaffoldResult(plan.scaffold),
@@ -1791,6 +1656,17 @@ async function executeInitExecutionPlan(plan) {
1791
1656
  finalSupports: plan.supports
1792
1657
  };
1793
1658
  }
1659
+ if (existsSync4(plan.scaffold.fabricDir) && !statSync4(plan.scaffold.fabricDir).isDirectory()) {
1660
+ throw new Error(
1661
+ t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
1662
+ );
1663
+ }
1664
+ const drifted = scaffoldStates.find(
1665
+ (entry) => entry.state === "drifted" || entry.state === "user-modified"
1666
+ );
1667
+ if (drifted !== void 0) {
1668
+ throw new Error(t("cli.install.diff.drift-abort", { path: drifted.path }));
1669
+ }
1794
1670
  let created = null;
1795
1671
  const stageResults = [];
1796
1672
  let finalSupports = plan.supports;
@@ -1815,6 +1691,11 @@ async function executeInitExecutionPlan(plan) {
1815
1691
  exhaustiveInitExecutionStep(step);
1816
1692
  }
1817
1693
  }
1694
+ if (scaffoldStates.every((entry) => entry.state === "present-canonical")) {
1695
+ console.log(
1696
+ t("cli.install.diff.canonical", { count: String(scaffoldStates.length) })
1697
+ );
1698
+ }
1818
1699
  return {
1819
1700
  plan,
1820
1701
  created: created ?? unreachableInitScaffold(),
@@ -1824,23 +1705,26 @@ async function executeInitExecutionPlan(plan) {
1824
1705
  }
1825
1706
  var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
1826
1707
  function resolvePersonalFabricRoot() {
1827
- return process.env.FABRIC_HOME ?? homedir4();
1708
+ return process.env.FABRIC_HOME ?? homedir();
1828
1709
  }
1829
1710
  async function buildInitFabricPlan(target, options) {
1830
- assertExistingDirectory2(target);
1831
- const fabricDir = join5(target, ".fabric");
1832
- const agentsMdPath = join5(target, "AGENTS.md");
1833
- const agentsMdAction = existsSync6(agentsMdPath) ? "preserved" : "created";
1834
- const knowledgeDir = join5(fabricDir, "knowledge");
1835
- const personalKnowledgeDir = join5(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1836
- const forensicPath = join5(fabricDir, "forensic.json");
1837
- const eventsPath = join5(fabricDir, "events.jsonl");
1838
- const metaPath = join5(fabricDir, "agents.meta.json");
1711
+ assertExistingDirectory3(target);
1712
+ const fabricDir = join4(target, ".fabric");
1713
+ const agentsMdPath = join4(target, "AGENTS.md");
1714
+ const agentsMdAction = existsSync4(agentsMdPath) ? "preserved" : "created";
1715
+ const knowledgeDir = join4(fabricDir, "knowledge");
1716
+ const personalKnowledgeDir = join4(resolvePersonalFabricRoot(), ".fabric", "knowledge");
1717
+ const forensicPath = join4(fabricDir, "forensic.json");
1718
+ const eventsPath = join4(fabricDir, "events.jsonl");
1719
+ const metaPath = join4(fabricDir, "agents.meta.json");
1839
1720
  const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
1840
- const knowledgeDirAction = existsSync6(knowledgeDir) ? "overwritten" : "created";
1841
- const metaAction = planFreshPath(metaPath, options);
1842
- const eventsAction = planFreshPath(eventsPath, options);
1843
- const forensicAction = planFreshPath(forensicPath, options);
1721
+ const knowledgeDirAction = existsSync4(knowledgeDir) ? "overwritten" : "created";
1722
+ const metaClassification = classifyFreshPath(metaPath, "structural");
1723
+ const eventsClassification = classifyFreshPath(eventsPath, "presence");
1724
+ const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
1725
+ const metaAction = diffStateToWriteAction(metaClassification.state);
1726
+ const eventsAction = diffStateToWriteAction(eventsClassification.state);
1727
+ const forensicAction = diffStateToWriteAction(forensicClassification.state);
1844
1728
  const forensicReport = await buildForensicReport(target);
1845
1729
  const meta = createInitialMeta();
1846
1730
  return {
@@ -1860,60 +1744,63 @@ async function buildInitFabricPlan(target, options) {
1860
1744
  eventsAction,
1861
1745
  forensicPath,
1862
1746
  forensicAction,
1863
- forensicReport
1747
+ forensicReport,
1748
+ metaState: metaClassification.state,
1749
+ eventsState: eventsClassification.state,
1750
+ forensicState: forensicClassification.state
1864
1751
  };
1865
1752
  }
1866
1753
  async function executeInitFabricPlan(plan) {
1867
- const isReapply = plan.options?.reapply === true;
1868
1754
  if (plan.replaceFabricDir) {
1869
1755
  rmSync(plan.fabricDir, { force: true });
1870
1756
  }
1871
1757
  mkdirSync(plan.fabricDir, { recursive: true });
1872
- if (plan.agentsMdAction === "created" && !existsSync6(plan.agentsMdPath)) {
1873
- await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
1874
- }
1758
+ writeDefaultFabricConfig(plan.fabricDir, plan.target);
1875
1759
  mkdirSync(plan.knowledgeDir, { recursive: true });
1876
1760
  for (const sub of KNOWLEDGE_SUBDIRS) {
1877
- const teamSubDir = join5(plan.knowledgeDir, sub);
1761
+ const teamSubDir = join4(plan.knowledgeDir, sub);
1878
1762
  mkdirSync(teamSubDir, { recursive: true });
1879
- const teamGitkeep = join5(teamSubDir, ".gitkeep");
1880
- if (!existsSync6(teamGitkeep)) {
1763
+ const teamGitkeep = join4(teamSubDir, ".gitkeep");
1764
+ if (!existsSync4(teamGitkeep)) {
1881
1765
  writeFileSync(teamGitkeep, "", "utf8");
1882
1766
  }
1883
1767
  }
1884
1768
  try {
1885
1769
  mkdirSync(plan.personalKnowledgeDir, { recursive: true });
1886
1770
  for (const sub of KNOWLEDGE_SUBDIRS) {
1887
- mkdirSync(join5(plan.personalKnowledgeDir, sub), { recursive: true });
1771
+ mkdirSync(join4(plan.personalKnowledgeDir, sub), { recursive: true });
1888
1772
  }
1889
1773
  } catch {
1890
1774
  }
1891
- preparePlannedPath(plan.metaPath, plan.metaAction);
1892
- await atomicWriteJson(plan.metaPath, plan.meta);
1893
- if (isReapply) {
1894
- if (!existsSync6(plan.eventsPath)) {
1895
- mkdirSync(dirname2(plan.eventsPath), { recursive: true });
1896
- writeFileSync(plan.eventsPath, "", "utf8");
1897
- }
1898
- } else {
1775
+ if (plan.metaState === "missing") {
1776
+ preparePlannedPath(plan.metaPath, plan.metaAction);
1777
+ await atomicWriteJson(plan.metaPath, plan.meta);
1778
+ }
1779
+ if (plan.eventsState === "missing") {
1899
1780
  preparePlannedPath(plan.eventsPath, plan.eventsAction);
1781
+ mkdirSync(dirname(plan.eventsPath), { recursive: true });
1900
1782
  writeFileSync(plan.eventsPath, "", "utf8");
1901
1783
  }
1902
1784
  preparePlannedPath(plan.forensicPath, plan.forensicAction);
1903
1785
  await atomicWriteJson(plan.forensicPath, plan.forensicReport);
1904
- if (!plan.options?.reapply) {
1905
- try {
1906
- await runInitScan(plan.target, { source: "init" });
1907
- } catch (error) {
1908
- writeStderr2(
1909
- `[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
1910
- );
1786
+ if (existsSync4(plan.eventsPath)) {
1787
+ const applied = [];
1788
+ const canonical = [];
1789
+ const drifted = [];
1790
+ for (const entry of [
1791
+ { path: plan.metaPath, state: plan.metaState },
1792
+ { path: plan.eventsPath, state: plan.eventsState },
1793
+ { path: plan.forensicPath, state: plan.forensicState }
1794
+ ]) {
1795
+ if (entry.state === "missing") {
1796
+ applied.push(entry.path);
1797
+ } else if (entry.state === "present-canonical") {
1798
+ canonical.push(entry.path);
1799
+ } else {
1800
+ drifted.push(entry.path);
1801
+ }
1911
1802
  }
1912
- }
1913
- if (isReapply) {
1914
- appendReapplyLedgerEvent(plan.eventsPath, {
1915
- preserved_ledger: true
1916
- });
1803
+ appendInstallDiffLedgerEvent(plan.eventsPath, { applied, canonical, drifted });
1917
1804
  }
1918
1805
  return {
1919
1806
  agentsMdPath: plan.agentsMdPath,
@@ -1933,16 +1820,16 @@ async function initFabric(target, options) {
1933
1820
  return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
1934
1821
  }
1935
1822
  function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
1936
- return terminalInteractive && args.interactive !== false && args.yes !== true;
1823
+ return terminalInteractive && args.yes !== true;
1937
1824
  }
1938
- async function resolveInitExecutionPlanWithWizard(basePlan, args, wizardAdapter) {
1825
+ async function resolveInitExecutionPlanWithWizard(basePlan, wizardAdapter) {
1939
1826
  const selection = await wizardAdapter.run({
1940
1827
  target: basePlan.target,
1941
1828
  options: basePlan.options,
1942
1829
  supports: basePlan.supports,
1943
1830
  mcpInstallMode: basePlan.mcpInstallMode,
1944
1831
  claudeMcpScope: basePlan.claudeMcpScope,
1945
- lockedStages: collectLockedWizardStages(args)
1832
+ lockedStages: []
1946
1833
  });
1947
1834
  if (selection === null) {
1948
1835
  return null;
@@ -1980,27 +1867,36 @@ function printInitScaffoldResult(created) {
1980
1867
  function printInitPostSetup(plan, stageResults, finalSupports) {
1981
1868
  if (shouldPrintHooksNextStep(plan.options, stageResults)) {
1982
1869
  console.log(
1983
- t("cli.init.next-step", {
1870
+ t("cli.install.next-step", {
1984
1871
  label: nextLabel(),
1985
- message: paint.muted(t("cli.init.next-step.message"))
1872
+ message: paint.muted(t("cli.install.next-step.message"))
1986
1873
  })
1987
1874
  );
1988
1875
  }
1989
1876
  console.log(
1990
- t("cli.init.reason-message", {
1877
+ t("cli.install.reason-message", {
1991
1878
  label: reasonLabel(),
1992
1879
  message: paint.muted(formatInitReasonMessage(finalSupports))
1993
1880
  })
1994
1881
  );
1995
1882
  printInitStageSummary(stageResults);
1996
1883
  printInitCapabilitySummary(finalSupports, stageResults, plan.options);
1884
+ const fabricLanguage = readFabricLanguagePreference(plan.target);
1885
+ console.log(
1886
+ paint.muted(t("cli.install.language_preference_hint", { value: fabricLanguage }))
1887
+ );
1888
+ }
1889
+ function printInitDiffStateTable(entries) {
1890
+ for (const entry of entries) {
1891
+ console.log(` ${formatDiffFileState(entry.state)} ${entry.path}`);
1892
+ }
1997
1893
  }
1998
1894
  function printInitPlanPreview(plan) {
1999
- console.log(t("cli.init.plan.preview-title"));
1895
+ console.log(t("cli.install.plan.preview-title"));
2000
1896
  printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
2001
1897
  console.log(
2002
- t("cli.init.plan.preview-result", {
2003
- mode: plan.options.reapply ? t("cli.init.mode.reapply") : t("cli.init.mode.default"),
1898
+ t("cli.install.plan.preview-result", {
1899
+ mode: t("cli.install.mode.default"),
2004
1900
  bootstrap: yesNoLabel(!plan.options.skipBootstrap),
2005
1901
  mcp: yesNoLabel(!plan.options.skipMcp),
2006
1902
  hooks: yesNoLabel(!plan.options.skipHooks)
@@ -2030,27 +1926,32 @@ async function executeInitStagePlan(plan, stageName) {
2030
1926
  if (stage.skipped) {
2031
1927
  return { name: stageName, disposition: "skipped" };
2032
1928
  }
2033
- console.log(formatInitStageHeader(t(`cli.init.stages.${stageName}`)));
1929
+ console.log(formatInitStageHeader(t(`cli.install.stages.${stageName}`)));
2034
1930
  try {
2035
1931
  switch (stage.name) {
2036
1932
  case "bootstrap": {
2037
1933
  const installResults = [];
1934
+ installResults.push(...await runBestEffort("skill-deprecated-cleanup", () => cleanupDeprecatedSkills(plan.target)));
2038
1935
  installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
2039
1936
  installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
2040
1937
  installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
2041
1938
  installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
2042
1939
  installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
2043
1940
  installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
1941
+ installResults.push(...await runBestEffort("hook-lib", () => installHookLibs(plan.target)));
2044
1942
  installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
2045
1943
  installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
2046
1944
  installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
2047
- installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
1945
+ installResults.push(await runBestEffortSingle("bootstrap-snapshot", () => writeFabricAgentsSnapshot(plan.target)));
1946
+ installResults.push(await runBestEffortSingle("bootstrap-claude", () => writeClaudeBootstrapThinShell(plan.target)));
1947
+ installResults.push(await runBestEffortSingle("bootstrap-codex", () => writeCodexBootstrapManagedBlock(plan.target)));
1948
+ installResults.push(await runBestEffortSingle("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(plan.target)));
2048
1949
  const installedCount = installResults.filter((r) => r.status === "written").length;
2049
1950
  const skippedCount = installResults.filter((r) => r.status === "skipped").length;
2050
1951
  const errorCount = installResults.filter((r) => r.status === "error").length;
2051
1952
  for (const result of installResults) {
2052
1953
  if (result.status === "error") {
2053
- writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
1954
+ writeStderr(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
2054
1955
  }
2055
1956
  }
2056
1957
  const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
@@ -2060,15 +1961,14 @@ async function executeInitStagePlan(plan, stageName) {
2060
1961
  case "mcp": {
2061
1962
  if (stage.installMode === "local") {
2062
1963
  const manager = stage.packageManager ?? detectPackageManager(plan.target);
2063
- writeStderr2(t("cli.init.mcp.install.local"));
2064
- writeStderr2(t("cli.init.mcp.local.installing", { manager }));
1964
+ writeStderr(t("cli.install.mcp.install.local"));
1965
+ writeStderr(t("cli.install.mcp.local.installing", { manager }));
2065
1966
  installLocalFabricServer(plan.target, manager);
2066
- writeStderr2(t("cli.init.mcp.local.installed"));
1967
+ writeStderr(t("cli.install.mcp.local.installed"));
2067
1968
  } else {
2068
- writeStderr2(t("cli.init.mcp.install.global"));
1969
+ writeStderr(t("cli.install.mcp.install.global"));
2069
1970
  }
2070
1971
  const result = await installMcpClients(plan.target, {
2071
- force: plan.options.force,
2072
1972
  localServerPath: stage.localServerPath,
2073
1973
  claudeMcpScope: stage.claudeMcpScope
2074
1974
  });
@@ -2080,7 +1980,7 @@ async function executeInitStagePlan(plan, stageName) {
2080
1980
  return { name: "mcp", disposition: "ran" };
2081
1981
  }
2082
1982
  case "hooks": {
2083
- const result = await installHooks(plan.target, { force: plan.options.force });
1983
+ const result = await installHooks(plan.target);
2084
1984
  console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
2085
1985
  return { name: "hooks", disposition: "ran" };
2086
1986
  }
@@ -2088,93 +1988,129 @@ async function executeInitStagePlan(plan, stageName) {
2088
1988
  return exhaustiveInitStagePlan(stage);
2089
1989
  }
2090
1990
  } catch (error) {
2091
- writeStderr2(formatInitStageFailure(stageName, error));
1991
+ writeStderr(formatInitStageFailure(stageName, error));
2092
1992
  return { name: stageName, disposition: "failed" };
2093
1993
  }
2094
1994
  }
2095
- function shouldReplaceWritableDirectory(path, options) {
2096
- if (!existsSync6(path)) {
1995
+ function shouldReplaceWritableDirectory(path, _options) {
1996
+ if (!existsSync4(path)) {
2097
1997
  return false;
2098
1998
  }
2099
- if (statSync2(path).isDirectory()) {
1999
+ if (statSync4(path).isDirectory()) {
2100
2000
  return false;
2101
2001
  }
2102
- if (!options?.force) {
2103
- throw new Error(t("cli.init.errors.abort-existing", { path }));
2104
- }
2105
- return true;
2002
+ return false;
2106
2003
  }
2107
- function planFreshPath(path, options) {
2108
- if (!existsSync6(path)) {
2109
- return "created";
2004
+ function classifyFreshPath(path, strategy) {
2005
+ if (!existsSync4(path)) {
2006
+ return { path, state: "missing" };
2007
+ }
2008
+ let stat;
2009
+ try {
2010
+ stat = statSync4(path);
2011
+ } catch (error) {
2012
+ return {
2013
+ path,
2014
+ state: "user-modified",
2015
+ reason: error instanceof Error ? error.message : String(error)
2016
+ };
2017
+ }
2018
+ if (!stat.isFile()) {
2019
+ return { path, state: "user-modified", reason: "expected a file" };
2110
2020
  }
2111
- if (!options?.force) {
2112
- throw new Error(t("cli.init.errors.abort-existing", { path }));
2021
+ if (strategy === "presence" || strategy === "always-rewrite") {
2022
+ return { path, state: "present-canonical" };
2023
+ }
2024
+ try {
2025
+ const raw = readFileSync3(path, "utf8");
2026
+ const parsed = JSON.parse(raw);
2027
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2028
+ return { path, state: "user-modified", reason: "not a JSON object" };
2029
+ }
2030
+ const record = parsed;
2031
+ const hasRevision = typeof record["revision"] === "string";
2032
+ const hasNodes = record["nodes"] !== void 0 && record["nodes"] !== null && typeof record["nodes"] === "object" && !Array.isArray(record["nodes"]);
2033
+ const hasCounters = record["counters"] !== void 0 && record["counters"] !== null && typeof record["counters"] === "object" && !Array.isArray(record["counters"]);
2034
+ if (!hasRevision || !hasNodes || !hasCounters) {
2035
+ return { path, state: "drifted", reason: "missing required AgentsMeta fields" };
2036
+ }
2037
+ return { path, state: "present-canonical" };
2038
+ } catch (error) {
2039
+ return {
2040
+ path,
2041
+ state: "user-modified",
2042
+ reason: error instanceof Error ? error.message : String(error)
2043
+ };
2113
2044
  }
2114
- return "overwritten";
2045
+ }
2046
+ function diffStateToWriteAction(_state) {
2047
+ return "created";
2048
+ }
2049
+ function formatDiffFileState(state) {
2050
+ return t(`cli.install.diff.state.${state}`);
2115
2051
  }
2116
2052
  function preparePlannedPath(path, action) {
2117
- mkdirSync(dirname2(path), { recursive: true });
2118
- if (action === "overwritten" && existsSync6(path)) {
2053
+ mkdirSync(dirname(path), { recursive: true });
2054
+ if (action === "overwritten" && existsSync4(path)) {
2119
2055
  rmSync(path, { recursive: true, force: true });
2120
2056
  }
2121
2057
  }
2122
2058
  function createDefaultInitWizardAdapter() {
2123
2059
  return {
2124
2060
  async run(context) {
2125
- intro(t("cli.init.wizard.intro"));
2061
+ intro(t("cli.install.wizard.intro"));
2126
2062
  note(
2127
- t("cli.init.wizard.overview.body", {
2063
+ t("cli.install.wizard.overview.body", {
2128
2064
  target: context.target,
2129
2065
  mode: formatInitModeBadge(context.options)
2130
2066
  }),
2131
- t("cli.init.wizard.overview.title")
2067
+ t("cli.install.wizard.overview.title")
2132
2068
  );
2133
2069
  printInitPlanSummary(context.target, context.options, context.mcpInstallMode, context.supports);
2134
- log.step(t("cli.init.wizard.step.target"));
2070
+ log.step(t("cli.install.wizard.step.target"));
2135
2071
  const continueWithTarget = await confirm({
2136
- message: t("cli.init.wizard.target.confirm", { target: context.target }),
2072
+ message: t("cli.install.wizard.target.confirm", { target: context.target }),
2137
2073
  initialValue: true
2138
2074
  });
2139
2075
  if (isCancel(continueWithTarget) || !continueWithTarget) {
2140
2076
  emitInitWizardCancellation();
2141
2077
  return null;
2142
2078
  }
2143
- log.step(t("cli.init.wizard.step.plan"));
2079
+ log.step(t("cli.install.wizard.step.plan"));
2144
2080
  let groupedSelection;
2145
2081
  try {
2146
2082
  groupedSelection = await group(
2147
2083
  {
2148
2084
  bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
2149
- message: t("cli.init.wizard.stage.bootstrap", {
2085
+ message: t("cli.install.wizard.stage.bootstrap", {
2150
2086
  defaultValue: formatPromptDefault(!context.options.skipBootstrap)
2151
2087
  }),
2152
2088
  initialValue: !context.options.skipBootstrap
2153
2089
  }),
2154
2090
  mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
2155
- message: t("cli.init.wizard.stage.mcp", {
2091
+ message: t("cli.install.wizard.stage.mcp", {
2156
2092
  defaultValue: formatPromptDefault(!context.options.skipMcp)
2157
2093
  }),
2158
2094
  initialValue: !context.options.skipMcp
2159
2095
  }),
2160
2096
  mcpInstallMode: async ({ results }) => results.mcp ? selectMcpInstallModeInGroup({
2161
- message: t("cli.init.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
2097
+ message: t("cli.install.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
2162
2098
  initialValue: context.mcpInstallMode,
2163
2099
  options: [
2164
- { value: "global", label: "global", hint: t("cli.init.mcp.install.global") },
2165
- { value: "local", label: "local", hint: t("cli.init.mcp.install.local") }
2100
+ { value: "global", label: "global", hint: t("cli.install.mcp.install.global") },
2101
+ { value: "local", label: "local", hint: t("cli.install.mcp.install.local") }
2166
2102
  ]
2167
2103
  }) : context.mcpInstallMode,
2168
2104
  claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
2169
- message: t("cli.init.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
2105
+ message: t("cli.install.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
2170
2106
  initialValue: context.claudeMcpScope,
2171
2107
  options: [
2172
- { value: "project", label: "project", hint: t("cli.init.mcp.scope.project") },
2173
- { value: "user", label: "user", hint: t("cli.init.mcp.scope.user") }
2108
+ { value: "project", label: "project", hint: t("cli.install.mcp.scope.project") },
2109
+ { value: "user", label: "user", hint: t("cli.install.mcp.scope.user") }
2174
2110
  ]
2175
2111
  }) : context.claudeMcpScope,
2176
2112
  hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
2177
- message: t("cli.init.wizard.stage.hooks", {
2113
+ message: t("cli.install.wizard.stage.hooks", {
2178
2114
  defaultValue: formatPromptDefault(!context.options.skipHooks)
2179
2115
  }),
2180
2116
  initialValue: !context.options.skipHooks
@@ -2203,23 +2139,23 @@ function createDefaultInitWizardAdapter() {
2203
2139
  skipMcp: !groupedSelection.mcp,
2204
2140
  skipHooks: !groupedSelection.hooks
2205
2141
  };
2206
- log.step(t("cli.init.wizard.step.review"));
2142
+ log.step(t("cli.install.wizard.step.review"));
2207
2143
  printInitPlanSummary(context.target, previewOptions, groupedSelection.mcpInstallMode, context.supports);
2208
2144
  const confirmed = await confirm({
2209
- message: t("cli.init.wizard.execute.confirm"),
2145
+ message: t("cli.install.wizard.execute.confirm"),
2210
2146
  initialValue: true
2211
2147
  });
2212
2148
  if (isCancel(confirmed) || !confirmed) {
2213
2149
  emitInitWizardCancellation();
2214
2150
  return null;
2215
2151
  }
2216
- outro(t("cli.init.wizard.outro"));
2152
+ outro(t("cli.install.wizard.outro"));
2217
2153
  return groupedSelection;
2218
2154
  }
2219
2155
  };
2220
2156
  }
2221
2157
  function emitInitWizardCancellation() {
2222
- cancel(t("cli.init.wizard.cancelled"));
2158
+ cancel(t("cli.install.wizard.cancelled"));
2223
2159
  }
2224
2160
  async function confirmInGroup(options) {
2225
2161
  const result = await confirm(options);
@@ -2250,74 +2186,42 @@ async function selectClaudeMcpScopeInGroup(options) {
2250
2186
  }
2251
2187
  return result;
2252
2188
  }
2253
- function collectLockedWizardStages(args) {
2254
- const lockedStages = [];
2255
- if (args.bootstrap === false) {
2256
- lockedStages.push("bootstrap");
2257
- }
2258
- if (args.mcp === false) {
2259
- lockedStages.push("mcp");
2260
- }
2261
- if (args.hooks === false) {
2262
- lockedStages.push("hooks");
2263
- }
2264
- return lockedStages;
2265
- }
2266
2189
  function formatPromptDefault(value) {
2267
2190
  return value ? "Y/n" : "y/N";
2268
2191
  }
2269
2192
  function formatInitModeBanner(options) {
2270
- if (options.planOnly && options.reapply) {
2271
- return t("cli.init.plan.mode-banner.plan-reapply");
2272
- }
2273
2193
  if (options.planOnly) {
2274
- return t("cli.init.plan.mode-banner.plan");
2275
- }
2276
- if (options.reapply) {
2277
- return t("cli.init.plan.mode-banner.reapply");
2194
+ return t("cli.install.plan.mode-banner.plan");
2278
2195
  }
2279
- return t("cli.init.plan.mode-banner.default");
2196
+ return t("cli.install.plan.mode-banner.default");
2280
2197
  }
2281
2198
  function formatInitModeBadge(options) {
2282
- if (options.planOnly && options.reapply) {
2283
- return t("cli.init.mode.badge.plan-reapply");
2284
- }
2285
2199
  if (options.planOnly) {
2286
- return t("cli.init.mode.badge.plan");
2287
- }
2288
- if (options.reapply) {
2289
- return t("cli.init.mode.badge.reapply");
2200
+ return t("cli.install.mode.badge.plan");
2290
2201
  }
2291
- return t("cli.init.mode.badge.default");
2202
+ return t("cli.install.mode.badge.default");
2292
2203
  }
2293
- function normalizeTarget2(targetInput) {
2294
- return isAbsolute2(targetInput) ? targetInput : resolve5(process.cwd(), targetInput);
2204
+ function normalizeTarget3(targetInput) {
2205
+ return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
2295
2206
  }
2296
- function assertExistingDirectory2(target) {
2297
- if (!existsSync6(target) || !statSync2(target).isDirectory()) {
2207
+ function assertExistingDirectory3(target) {
2208
+ if (!existsSync4(target) || !statSync4(target).isDirectory()) {
2298
2209
  throw new Error(`Target must be an existing directory: ${target}`);
2299
2210
  }
2300
2211
  }
2301
2212
  function detectPackageManager(cwd) {
2302
- const workspaceRoot = resolve5(cwd);
2303
- if (existsSync6(join5(workspaceRoot, "pnpm-lock.yaml"))) {
2213
+ const workspaceRoot = resolve3(cwd);
2214
+ if (existsSync4(join4(workspaceRoot, "pnpm-lock.yaml"))) {
2304
2215
  return "pnpm";
2305
2216
  }
2306
- if (existsSync6(join5(workspaceRoot, "yarn.lock"))) {
2217
+ if (existsSync4(join4(workspaceRoot, "yarn.lock"))) {
2307
2218
  return "yarn";
2308
2219
  }
2309
- if (existsSync6(join5(workspaceRoot, "package-lock.json"))) {
2220
+ if (existsSync4(join4(workspaceRoot, "package-lock.json"))) {
2310
2221
  return "npm";
2311
2222
  }
2312
2223
  return "npm";
2313
2224
  }
2314
- function resolveMcpInstallMode(rawMode) {
2315
- if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
2316
- return rawMode ?? "global";
2317
- }
2318
- writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
2319
- return "global";
2320
- }
2321
2225
  function installLocalFabricServer(target, manager) {
2322
2226
  const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
2323
2227
  childProcess.execFileSync(manager, installArgs, {
@@ -2333,14 +2237,16 @@ function createInitialMeta() {
2333
2237
  counters: defaultAgentsMetaCounters()
2334
2238
  };
2335
2239
  }
2336
- function appendReapplyLedgerEvent(eventsPath, payload) {
2240
+ function appendInstallDiffLedgerEvent(eventsPath, payload) {
2337
2241
  const event = {
2338
2242
  kind: "fabric-event",
2339
2243
  id: `event:${randomUUID()}`,
2340
2244
  ts: Date.now(),
2341
2245
  schema_version: 1,
2342
- event_type: "reapply_completed",
2343
- preserved_ledger: payload.preserved_ledger
2246
+ event_type: "install_diff_applied",
2247
+ applied: payload.applied,
2248
+ canonical: payload.canonical,
2249
+ drifted: payload.drifted
2344
2250
  };
2345
2251
  const line = `${JSON.stringify(event)}
2346
2252
  `;
@@ -2391,7 +2297,7 @@ function printInitStageSummary(stageResults) {
2391
2297
  console.log(formatInitStageSummaryLine("failed", collectInitStageNames(stageResults, "failed")));
2392
2298
  }
2393
2299
  function formatInitStageSummaryLine(disposition, stages) {
2394
- 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"));
2300
+ 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"));
2395
2301
  return `${label}: ${stages.length > 0 ? stages.join(", ") : t("cli.shared.none")}`;
2396
2302
  }
2397
2303
  function collectInitStageNames(stageResults, disposition) {
@@ -2404,11 +2310,11 @@ function isInteractiveInit() {
2404
2310
  return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
2405
2311
  }
2406
2312
  function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2407
- console.log(t("cli.init.plan.title"));
2313
+ console.log(t("cli.install.plan.title"));
2408
2314
  console.log(formatInitModeBanner(options));
2409
- console.log(t("cli.init.plan.target", { target }));
2315
+ console.log(t("cli.install.plan.target", { target }));
2410
2316
  console.log(
2411
- t("cli.init.plan.actions", {
2317
+ t("cli.install.plan.actions", {
2412
2318
  bootstrap: yesNoLabel(!options.skipBootstrap),
2413
2319
  mcp: yesNoLabel(!options.skipMcp),
2414
2320
  hooks: yesNoLabel(!options.skipHooks),
@@ -2417,31 +2323,32 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
2417
2323
  );
2418
2324
  const detected = supports.filter((support) => support.detected);
2419
2325
  console.log(
2420
- t("cli.init.plan.detected", {
2326
+ t("cli.install.plan.detected", {
2421
2327
  clients: detected.length > 0 ? detected.map((support) => support.label).join(", ") : t("cli.shared.none")
2422
2328
  })
2423
2329
  );
2424
- console.log(t("cli.init.plan.writes"));
2330
+ console.log(t("cli.install.plan.writes"));
2425
2331
  console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
2426
2332
  console.log(` - ${target}/.fabric/agents.meta.json`);
2427
2333
  console.log(` - ${target}/.fabric/events.jsonl`);
2428
2334
  console.log(` - ${target}/.fabric/forensic.json`);
2335
+ console.log(` - ${target}/.fabric/fabric-config.json`);
2429
2336
  }
2430
2337
  function printInitCapabilitySummary(supports, stageResults, options) {
2431
2338
  const detected = supports.filter((support) => support.detected);
2432
2339
  if (detected.length === 0) {
2433
- console.log(t("cli.init.capabilities.none"));
2340
+ console.log(t("cli.install.capabilities.none"));
2434
2341
  return;
2435
2342
  }
2436
- console.log(t("cli.init.capabilities.title"));
2343
+ console.log(t("cli.install.capabilities.title"));
2437
2344
  const rows = detected.map((support) => toCapabilityRow(support, stageResults, options));
2438
2345
  const headers = {
2439
- client: t("cli.init.capabilities.header.client"),
2440
- bootstrap: t("cli.init.capabilities.header.bootstrap"),
2441
- mcp: t("cli.init.capabilities.header.mcp"),
2442
- hook: t("cli.init.capabilities.header.hook"),
2443
- skill: t("cli.init.capabilities.header.skill"),
2444
- followUp: t("cli.init.capabilities.header.follow-up")
2346
+ client: t("cli.install.capabilities.header.client"),
2347
+ bootstrap: t("cli.install.capabilities.header.bootstrap"),
2348
+ mcp: t("cli.install.capabilities.header.mcp"),
2349
+ hook: t("cli.install.capabilities.header.hook"),
2350
+ skill: t("cli.install.capabilities.header.skill"),
2351
+ followUp: t("cli.install.capabilities.header.follow-up")
2445
2352
  };
2446
2353
  const widths = {
2447
2354
  client: Math.max(displayWidth(headers.client), ...rows.map((row) => displayWidth(row.client))),
@@ -2456,11 +2363,13 @@ function printInitCapabilitySummary(supports, stageResults, options) {
2456
2363
  for (const row of rows) {
2457
2364
  console.log(formatCapabilityTableRow(row, widths));
2458
2365
  }
2366
+ console.log("");
2367
+ console.log(t("cli.install.restart-banner"));
2459
2368
  }
2460
2369
  function toCapabilityRow(support, stageResults, options) {
2461
2370
  const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
2462
- const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.init.capabilities.status.na");
2463
- const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.init.capabilities.status.na");
2371
+ const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.install.capabilities.status.na");
2372
+ const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.install.capabilities.status.na");
2464
2373
  const hook = capabilityInstallStatus(support, "hook");
2465
2374
  const skill = capabilityInstallStatus(support, "skill");
2466
2375
  return {
@@ -2469,14 +2378,14 @@ function toCapabilityRow(support, stageResults, options) {
2469
2378
  mcp,
2470
2379
  hook,
2471
2380
  skill,
2472
- 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")
2381
+ 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")
2473
2382
  };
2474
2383
  }
2475
2384
  function capabilityInstallStatus(support, capability) {
2476
2385
  if (!support.capabilities[capability]) {
2477
- return t("cli.init.capabilities.status.na");
2386
+ return t("cli.install.capabilities.status.na");
2478
2387
  }
2479
- return hasInstalledCapability(support, capability) ? t("cli.init.capabilities.status.installed") : t("cli.init.capabilities.status.supported");
2388
+ return hasInstalledCapability(support, capability) ? t("cli.install.capabilities.status.installed") : t("cli.install.capabilities.status.supported");
2480
2389
  }
2481
2390
  function hasInstalledCapability(support, capability) {
2482
2391
  return support.installedCapabilities?.[capability] === true;
@@ -2484,15 +2393,15 @@ function hasInstalledCapability(support, capability) {
2484
2393
  function capabilityStatus(disposition) {
2485
2394
  switch (disposition) {
2486
2395
  case "ran":
2487
- return t("cli.init.capabilities.status.ready");
2396
+ return t("cli.install.capabilities.status.ready");
2488
2397
  case "skipped":
2489
- return t("cli.init.capabilities.status.skipped");
2398
+ return t("cli.install.capabilities.status.skipped");
2490
2399
  case "failed":
2491
- return t("cli.init.capabilities.status.failed");
2400
+ return t("cli.install.capabilities.status.failed");
2492
2401
  case null:
2493
- return t("cli.init.capabilities.status.na");
2402
+ return t("cli.install.capabilities.status.na");
2494
2403
  default:
2495
- return t("cli.init.capabilities.status.ready");
2404
+ return t("cli.install.capabilities.status.ready");
2496
2405
  }
2497
2406
  }
2498
2407
  function formatCapabilityTableRow(row, widths) {
@@ -2518,21 +2427,21 @@ function formatCapabilityDivider(widths) {
2518
2427
  function formatInitReasonMessage(supports) {
2519
2428
  const detected = supports.filter((support) => support.detected);
2520
2429
  if (detected.some((support) => support.capabilities.skill)) {
2521
- return t("cli.init.reason-message.installable-body");
2430
+ return t("cli.install.reason-message.installable-body");
2522
2431
  }
2523
- return t("cli.init.reason-message.manual-body");
2432
+ return t("cli.install.reason-message.manual-body");
2524
2433
  }
2525
2434
  function yesNoLabel(value) {
2526
2435
  return value ? t("cli.shared.yes") : t("cli.shared.no");
2527
2436
  }
2528
2437
  function formatInitPathAction(path, action) {
2529
- return t("cli.init.created-path", { label: labelForInitWriteAction(action), path });
2438
+ return t("cli.install.created-path", { label: labelForInitWriteAction(action), path });
2530
2439
  }
2531
2440
  function formatAgentsMdAction(path, action) {
2532
2441
  if (action === "preserved") {
2533
- return t("cli.init.skipped-existing-path", { label: skippedLabel(), path });
2442
+ return t("cli.install.skipped-existing-path", { label: skippedLabel(), path });
2534
2443
  }
2535
- return t("cli.init.created-path", { label: createdLabel(), path });
2444
+ return t("cli.install.created-path", { label: createdLabel(), path });
2536
2445
  }
2537
2446
  function labelForInitWriteAction(action) {
2538
2447
  return action === "overwritten" ? overwrittenLabel() : createdLabel();
@@ -2550,18 +2459,18 @@ function reasonLabel() {
2550
2459
  return paint.human(t("cli.shared.reason"));
2551
2460
  }
2552
2461
  function overwrittenLabel() {
2553
- return paint.warn(t("cli.init.force.overwritten"));
2462
+ return paint.warn(t("cli.install.label.overwritten"));
2554
2463
  }
2555
2464
  function completedStageLabel() {
2556
- return paint.success(t("cli.init.stages.completed"));
2465
+ return paint.success(t("cli.install.stages.completed"));
2557
2466
  }
2558
2467
  function skippedStageLabel() {
2559
- return paint.muted(t("cli.init.stages.skipped"));
2468
+ return paint.muted(t("cli.install.stages.skipped"));
2560
2469
  }
2561
2470
  function failedStageLabel() {
2562
- return paint.error(t("cli.init.stages.failed"));
2471
+ return paint.error(t("cli.install.stages.failed"));
2563
2472
  }
2564
- function writeStderr2(message) {
2473
+ function writeStderr(message) {
2565
2474
  process.stderr.write(`${message}
2566
2475
  `);
2567
2476
  }
@@ -2569,13 +2478,15 @@ export {
2569
2478
  buildInitExecutionPlan,
2570
2479
  buildInitFabricPlan,
2571
2480
  createDefaultInitWizardAdapter,
2572
- init_default as default,
2481
+ install_default as default,
2573
2482
  detectPackageManager,
2574
2483
  executeInitExecutionPlan,
2575
2484
  executeInitFabricPlan,
2576
- initCommand,
2577
2485
  initFabric,
2486
+ installCommand,
2578
2487
  resolveInitExecutionPlanWithWizard,
2488
+ runHooksOnlyRefresh,
2579
2489
  runInitCommand,
2490
+ runSkillsOnlyRefresh,
2580
2491
  shouldUseInitWizard
2581
2492
  };