@fenglimg/fabric-cli 1.5.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -14
- package/dist/{chunk-QSAEGVKE.js → chunk-NMMUETVK.js} +4 -8
- package/dist/{chunk-AEOYCVBG.js → chunk-QPCRBQ5Y.js} +52 -5
- package/dist/doctor-XP4OQOTX.js +92 -0
- package/dist/index.js +5 -20
- package/dist/{init-FDTUWEZX.js → init-WMB3WLXM.js} +1153 -144
- package/dist/{scan-QH76LC7Z.js → scan-NNBNGIZG.js} +2 -4
- package/dist/{serve-4J2CQY25.js → serve-7AXYKBIT.js} +3 -7
- package/package.json +3 -3
- package/templates/agents-md/AGENTS.md.template +7 -7
- package/templates/agents-md/variants/cocos.md +7 -7
- package/templates/agents-md/variants/next.md +7 -7
- package/templates/agents-md/variants/vite.md +7 -7
- package/templates/bootstrap/CLAUDE.md +3 -1
- package/templates/bootstrap/GEMINI.md +3 -1
- package/templates/bootstrap/codex-AGENTS-header.md +3 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +5 -6
- package/templates/bootstrap/roo-fabric.md +5 -6
- package/templates/bootstrap/windsurf-fabric.md +5 -6
- package/templates/claude-skills/agents-md-init/SKILL.md +1 -1
- package/templates/codex-skills/fabric-init/SKILL.md +1 -1
- package/templates/husky/pre-commit +9 -24
- package/dist/approve-YT4DEABS.js +0 -138
- package/dist/bootstrap-VGL3AR26.js +0 -16
- package/dist/chunk-2YW5CJ32.js +0 -147
- package/dist/chunk-6ICJICVU.js +0 -10
- package/dist/chunk-BEKSXO5N.js +0 -442
- package/dist/chunk-BVTMVW5M.js +0 -159
- package/dist/chunk-KOAEIH72.js +0 -270
- package/dist/chunk-L43IGJ6X.js +0 -106
- package/dist/chunk-T2WJF5I3.js +0 -254
- package/dist/chunk-WWNXR34K.js +0 -49
- package/dist/chunk-YDZJRLHL.js +0 -155
- package/dist/config-EC5L2QNI.js +0 -16
- package/dist/doctor-4BPYHV7V.js +0 -134
- package/dist/hooks-ZSWVH2JD.js +0 -12
- package/dist/human-lint-YSFOZHZ7.js +0 -13
- package/dist/ledger-append-3MDNR3GU.js +0 -10
- package/dist/pre-commit-53ENJDRZ.js +0 -98
- package/dist/sync-meta-IZR2WLIL.js +0 -16
- package/dist/update-M5M5PYKE.js +0 -116
- package/templates/fabric/human-lock.json +0 -12
|
@@ -1,47 +1,988 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
installBootstrap
|
|
5
|
-
} from "./chunk-T2WJF5I3.js";
|
|
6
|
-
import {
|
|
3
|
+
createScanReport,
|
|
7
4
|
detectFramework
|
|
8
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NMMUETVK.js";
|
|
9
6
|
import {
|
|
10
7
|
createDebugLogger,
|
|
11
|
-
resolveDevMode
|
|
12
|
-
} from "./chunk-AEOYCVBG.js";
|
|
13
|
-
import {
|
|
14
8
|
displayWidth,
|
|
15
9
|
padEnd,
|
|
16
|
-
paint
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
installMcpClients
|
|
20
|
-
} from "./chunk-BVTMVW5M.js";
|
|
21
|
-
import {
|
|
22
|
-
detectClientSupports
|
|
23
|
-
} from "./chunk-BEKSXO5N.js";
|
|
24
|
-
import {
|
|
25
|
-
installHooks
|
|
26
|
-
} from "./chunk-YDZJRLHL.js";
|
|
27
|
-
import {
|
|
10
|
+
paint,
|
|
11
|
+
readFabricConfig,
|
|
12
|
+
resolveDevMode,
|
|
28
13
|
t
|
|
29
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-QPCRBQ5Y.js";
|
|
30
15
|
|
|
31
16
|
// src/commands/init.ts
|
|
32
17
|
import { createHash } from "crypto";
|
|
33
18
|
import * as childProcess from "child_process";
|
|
34
|
-
import { chmodSync, copyFileSync, existsSync as
|
|
35
|
-
import { dirname, isAbsolute as
|
|
36
|
-
import { fileURLToPath } from "url";
|
|
19
|
+
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
20
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join8, parse as parse3, resolve as resolve9 } from "path";
|
|
21
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
37
22
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
23
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
24
|
+
|
|
25
|
+
// src/bootstrap-guide.ts
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
27
|
+
import { dirname, isAbsolute, join, parse, resolve } from "path";
|
|
28
|
+
import { fileURLToPath } from "url";
|
|
29
|
+
var AGENTS_TEMPLATE_BY_FRAMEWORK = {
|
|
30
|
+
"cocos-creator": "templates/agents-md/variants/cocos.md",
|
|
31
|
+
vite: "templates/agents-md/variants/vite.md",
|
|
32
|
+
next: "templates/agents-md/variants/next.md"
|
|
33
|
+
};
|
|
34
|
+
var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
|
|
35
|
+
async function buildFabricBootstrapGuide(target) {
|
|
36
|
+
const workspaceRoot = normalizeTarget(target);
|
|
37
|
+
const scanReport = await createScanReport(workspaceRoot);
|
|
38
|
+
const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
|
|
39
|
+
const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
|
|
40
|
+
return ensureTrailingNewline(
|
|
41
|
+
template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
async function ensureFabricBootstrapGuide(workspaceRoot, force) {
|
|
45
|
+
const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
|
|
46
|
+
if (existsSync(guidePath) && !force) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
mkdirSync(dirname(guidePath), { recursive: true });
|
|
50
|
+
writeFileSync(guidePath, await buildFabricBootstrapGuide(workspaceRoot), "utf8");
|
|
51
|
+
}
|
|
52
|
+
function findBootstrapTemplatePath(frameworkKind) {
|
|
53
|
+
const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
|
|
54
|
+
return findTemplatePath(relativePath);
|
|
55
|
+
}
|
|
56
|
+
function readPackageName(target) {
|
|
57
|
+
const packageJsonPath = join(target, "package.json");
|
|
58
|
+
if (!existsSync(packageJsonPath)) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
63
|
+
return packageJson.name;
|
|
64
|
+
} catch {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function findTemplatePath(relativePath) {
|
|
69
|
+
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const candidates = [
|
|
71
|
+
...templateCandidatesFrom(process.cwd(), relativePath),
|
|
72
|
+
...templateCandidatesFrom(currentModuleDir, relativePath)
|
|
73
|
+
];
|
|
74
|
+
for (const candidate of candidates) {
|
|
75
|
+
if (existsSync(candidate)) {
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
80
|
+
}
|
|
81
|
+
function templateCandidatesFrom(start, relativePath) {
|
|
82
|
+
const candidates = [];
|
|
83
|
+
let current = resolve(start);
|
|
84
|
+
while (true) {
|
|
85
|
+
candidates.push(join(current, ...relativePath.split("/")));
|
|
86
|
+
const parent = dirname(current);
|
|
87
|
+
if (parent === current || parse(current).root === current) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
current = parent;
|
|
91
|
+
}
|
|
92
|
+
return candidates.reverse();
|
|
93
|
+
}
|
|
94
|
+
function ensureTrailingNewline(content) {
|
|
95
|
+
return content.endsWith("\n") ? content : `${content}
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
function normalizeTarget(targetInput) {
|
|
99
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/commands/bootstrap.ts
|
|
103
|
+
import { resolve as resolve5 } from "path";
|
|
38
104
|
import { defineCommand } from "citty";
|
|
39
105
|
|
|
106
|
+
// src/config/resolver.ts
|
|
107
|
+
import { existsSync as existsSync5 } from "fs";
|
|
108
|
+
import { join as join5 } from "path";
|
|
109
|
+
import { homedir as homedir4 } from "os";
|
|
110
|
+
|
|
111
|
+
// src/config/claude-code.ts
|
|
112
|
+
import { existsSync as existsSync3 } from "fs";
|
|
113
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
114
|
+
import { homedir as homedir2, platform } from "os";
|
|
115
|
+
|
|
116
|
+
// src/config/json.ts
|
|
117
|
+
import { existsSync as existsSync2 } from "fs";
|
|
118
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
119
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
120
|
+
import { homedir } from "os";
|
|
121
|
+
|
|
122
|
+
// src/config/writer.ts
|
|
123
|
+
function createServerEntry(serverPath) {
|
|
124
|
+
return {
|
|
125
|
+
command: process.execPath,
|
|
126
|
+
args: [serverPath]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/config/json.ts
|
|
131
|
+
function expandHome(filePath) {
|
|
132
|
+
if (filePath === "~") {
|
|
133
|
+
return homedir();
|
|
134
|
+
}
|
|
135
|
+
if (filePath.startsWith("~/")) {
|
|
136
|
+
return join2(homedir(), filePath.slice(2));
|
|
137
|
+
}
|
|
138
|
+
return filePath;
|
|
139
|
+
}
|
|
140
|
+
function normalizeConfigPath(filePath) {
|
|
141
|
+
return resolve2(expandHome(filePath));
|
|
142
|
+
}
|
|
143
|
+
async function readJsonConfig(configPath) {
|
|
144
|
+
try {
|
|
145
|
+
const raw = await readFile(configPath, "utf8");
|
|
146
|
+
if (raw.trim().length === 0) {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
151
|
+
throw new Error(`Expected JSON object in ${configPath}`);
|
|
152
|
+
}
|
|
153
|
+
return parsed;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
162
|
+
const config = await readJsonConfig(configPath);
|
|
163
|
+
const existingServers = config.mcpServers;
|
|
164
|
+
config.mcpServers = existingServers !== null && typeof existingServers === "object" && !Array.isArray(existingServers) ? { ...existingServers, fabric: serverEntry } : { fabric: serverEntry };
|
|
165
|
+
await mkdir(dirname2(configPath), { recursive: true });
|
|
166
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
167
|
+
`, "utf8");
|
|
168
|
+
}
|
|
169
|
+
var JsonClientConfigWriter = class {
|
|
170
|
+
configuredPath;
|
|
171
|
+
constructor(configuredPath) {
|
|
172
|
+
this.configuredPath = configuredPath;
|
|
173
|
+
}
|
|
174
|
+
async detect(workspaceRoot, overridePath) {
|
|
175
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
176
|
+
if (explicitPath !== void 0) {
|
|
177
|
+
return normalizeConfigPath(explicitPath);
|
|
178
|
+
}
|
|
179
|
+
const configPath = this.defaultPath(workspaceRoot);
|
|
180
|
+
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
181
|
+
}
|
|
182
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
183
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
184
|
+
if (configPath === null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
191
|
+
clientKind = "ClaudeCodeCLI";
|
|
192
|
+
constructor(configuredPath) {
|
|
193
|
+
super(configuredPath);
|
|
194
|
+
}
|
|
195
|
+
// Writes to project-level .claude/settings.json so MCP is scoped to the project.
|
|
196
|
+
// Detection in resolver still checks ~/ to confirm Claude Code is installed.
|
|
197
|
+
defaultPath(workspaceRoot) {
|
|
198
|
+
const globalClaudeDir = join2(homedir(), ".claude");
|
|
199
|
+
const projectClaudeDir = join2(workspaceRoot, ".claude");
|
|
200
|
+
if (!existsSync2(globalClaudeDir) && !existsSync2(projectClaudeDir)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return join2(projectClaudeDir, "settings.json");
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var CursorWriter = class extends JsonClientConfigWriter {
|
|
207
|
+
clientKind = "Cursor";
|
|
208
|
+
constructor(configuredPath) {
|
|
209
|
+
super(configuredPath);
|
|
210
|
+
}
|
|
211
|
+
defaultPath(workspaceRoot) {
|
|
212
|
+
const cursorDir = join2(workspaceRoot, ".cursor");
|
|
213
|
+
return existsSync2(cursorDir) ? join2(cursorDir, "mcp.json") : null;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var WindsurfWriter = class extends JsonClientConfigWriter {
|
|
217
|
+
clientKind = "Windsurf";
|
|
218
|
+
constructor(configuredPath) {
|
|
219
|
+
super(configuredPath);
|
|
220
|
+
}
|
|
221
|
+
defaultPath(workspaceRoot) {
|
|
222
|
+
const windsurfDir = join2(workspaceRoot, ".windsurf");
|
|
223
|
+
return existsSync2(windsurfDir) ? join2(windsurfDir, "mcp.json") : null;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var RooCodeWriter = class extends JsonClientConfigWriter {
|
|
227
|
+
clientKind = "RooCode";
|
|
228
|
+
constructor(configuredPath) {
|
|
229
|
+
super(configuredPath);
|
|
230
|
+
}
|
|
231
|
+
defaultPath(workspaceRoot) {
|
|
232
|
+
const rooDir = join2(workspaceRoot, ".roo");
|
|
233
|
+
return existsSync2(rooDir) ? join2(rooDir, "mcp.json") : null;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var GeminiCLIWriter = class extends JsonClientConfigWriter {
|
|
237
|
+
clientKind = "GeminiCLI";
|
|
238
|
+
constructor(configuredPath) {
|
|
239
|
+
super(configuredPath);
|
|
240
|
+
}
|
|
241
|
+
defaultPath(workspaceRoot) {
|
|
242
|
+
const geminiDir = join2(homedir(), ".gemini");
|
|
243
|
+
return existsSync2(geminiDir) || existsSync2(join2(workspaceRoot, "GEMINI.md")) ? join2(geminiDir, "settings.json") : null;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/config/claude-code.ts
|
|
248
|
+
function getClaudeDesktopConfigPath() {
|
|
249
|
+
const os = platform();
|
|
250
|
+
if (os === "darwin") {
|
|
251
|
+
return join3(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
252
|
+
}
|
|
253
|
+
if (os === "win32") {
|
|
254
|
+
return join3(process.env.APPDATA ?? join3(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
255
|
+
}
|
|
256
|
+
return join3(homedir2(), ".config", "Claude", "claude_desktop_config.json");
|
|
257
|
+
}
|
|
258
|
+
var ClaudeCodeDesktopWriter = class {
|
|
259
|
+
clientKind = "ClaudeCodeDesktop";
|
|
260
|
+
configuredPath;
|
|
261
|
+
constructor(configuredPath) {
|
|
262
|
+
this.configuredPath = configuredPath;
|
|
263
|
+
}
|
|
264
|
+
async detect(_workspaceRoot, overridePath) {
|
|
265
|
+
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
266
|
+
return existsSync3(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
267
|
+
}
|
|
268
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
269
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
270
|
+
if (configPath === null) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
await writeJsonClientConfig(configPath, {
|
|
274
|
+
command: process.execPath,
|
|
275
|
+
args: [serverPath]
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/config/toml.ts
|
|
281
|
+
import { existsSync as existsSync4 } from "fs";
|
|
282
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
283
|
+
import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
|
|
284
|
+
import { homedir as homedir3 } from "os";
|
|
285
|
+
function expandHome2(filePath) {
|
|
286
|
+
if (filePath === "~") {
|
|
287
|
+
return homedir3();
|
|
288
|
+
}
|
|
289
|
+
if (filePath.startsWith("~/")) {
|
|
290
|
+
return join4(homedir3(), filePath.slice(2));
|
|
291
|
+
}
|
|
292
|
+
return filePath;
|
|
293
|
+
}
|
|
294
|
+
function escapeTomlString(value) {
|
|
295
|
+
return JSON.stringify(value);
|
|
296
|
+
}
|
|
297
|
+
function serializeTomlStringArray(values) {
|
|
298
|
+
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
299
|
+
}
|
|
300
|
+
function serializeTomlInlineTable(values) {
|
|
301
|
+
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
302
|
+
return `{ ${entries.join(", ")} }`;
|
|
303
|
+
}
|
|
304
|
+
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
305
|
+
const lines = [
|
|
306
|
+
`[mcp_servers.${serverName}]`,
|
|
307
|
+
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
308
|
+
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
309
|
+
];
|
|
310
|
+
if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
|
|
311
|
+
lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
|
|
312
|
+
}
|
|
313
|
+
return `${lines.join("\n")}
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
316
|
+
function trimTrailingBlankLines(value) {
|
|
317
|
+
return value.replace(/\s+$/u, "");
|
|
318
|
+
}
|
|
319
|
+
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
320
|
+
const block = serializeCodexServerBlock(serverName, serverEntry);
|
|
321
|
+
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
322
|
+
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
323
|
+
const currentPattern = new RegExp(
|
|
324
|
+
String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
325
|
+
"g"
|
|
326
|
+
);
|
|
327
|
+
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
328
|
+
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
329
|
+
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
330
|
+
if (trimmed.length === 0) {
|
|
331
|
+
return block;
|
|
332
|
+
}
|
|
333
|
+
return `${trimmed}
|
|
334
|
+
|
|
335
|
+
${block}`;
|
|
336
|
+
}
|
|
337
|
+
async function readTomlConfigText(configPath) {
|
|
338
|
+
try {
|
|
339
|
+
return await readFile2(configPath, "utf8");
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
342
|
+
return "";
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
var CodexTOMLConfigWriter = class {
|
|
348
|
+
clientKind = "CodexCLI";
|
|
349
|
+
configuredPath;
|
|
350
|
+
constructor(configuredPath) {
|
|
351
|
+
this.configuredPath = configuredPath;
|
|
352
|
+
}
|
|
353
|
+
async detect(_workspaceRoot, overridePath) {
|
|
354
|
+
const explicitPath = overridePath ?? this.configuredPath;
|
|
355
|
+
if (explicitPath !== void 0) {
|
|
356
|
+
return resolve4(expandHome2(explicitPath));
|
|
357
|
+
}
|
|
358
|
+
const codexDir = join4(homedir3(), ".codex");
|
|
359
|
+
return existsSync4(codexDir) ? resolve4(join4(codexDir, "config.toml")) : null;
|
|
360
|
+
}
|
|
361
|
+
async write(serverPath, workspaceRoot, overridePath) {
|
|
362
|
+
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
363
|
+
if (configPath === null) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const rawConfig = await readTomlConfigText(configPath);
|
|
367
|
+
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
368
|
+
await mkdir2(dirname3(configPath), { recursive: true });
|
|
369
|
+
await writeFile2(configPath, nextConfig, "utf8");
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/config/resolver.ts
|
|
374
|
+
import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
|
|
375
|
+
function hasExplicitPath(clientPaths, key) {
|
|
376
|
+
return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
|
|
377
|
+
}
|
|
378
|
+
function addIfDetected(writers, detected, createWriter, configuredPath) {
|
|
379
|
+
if (configuredPath !== void 0 || detected) {
|
|
380
|
+
writers.push(createWriter(configuredPath));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function resolveClients(workspaceRoot, fabricConfig = {}) {
|
|
384
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
385
|
+
const writers = [];
|
|
386
|
+
addIfDetected(
|
|
387
|
+
writers,
|
|
388
|
+
existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude")),
|
|
389
|
+
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath),
|
|
390
|
+
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
391
|
+
);
|
|
392
|
+
addIfDetected(
|
|
393
|
+
writers,
|
|
394
|
+
existsSync5(getClaudeDesktopConfigPath()),
|
|
395
|
+
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
396
|
+
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
397
|
+
);
|
|
398
|
+
addIfDetected(
|
|
399
|
+
writers,
|
|
400
|
+
existsSync5(join5(workspaceRoot, ".cursor")),
|
|
401
|
+
(configuredPath) => new CursorWriter(configuredPath),
|
|
402
|
+
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
403
|
+
);
|
|
404
|
+
addIfDetected(
|
|
405
|
+
writers,
|
|
406
|
+
existsSync5(join5(workspaceRoot, ".windsurf")),
|
|
407
|
+
(configuredPath) => new WindsurfWriter(configuredPath),
|
|
408
|
+
hasExplicitPath(clientPaths, "windsurf") ? clientPaths.windsurf : void 0
|
|
409
|
+
);
|
|
410
|
+
addIfDetected(
|
|
411
|
+
writers,
|
|
412
|
+
existsSync5(join5(workspaceRoot, ".roo")),
|
|
413
|
+
(configuredPath) => new RooCodeWriter(configuredPath),
|
|
414
|
+
hasExplicitPath(clientPaths, "rooCode") ? clientPaths.rooCode : void 0
|
|
415
|
+
);
|
|
416
|
+
addIfDetected(
|
|
417
|
+
writers,
|
|
418
|
+
existsSync5(join5(homedir4(), ".gemini")) || existsSync5(join5(workspaceRoot, "GEMINI.md")),
|
|
419
|
+
(configuredPath) => new GeminiCLIWriter(configuredPath),
|
|
420
|
+
hasExplicitPath(clientPaths, "geminiCLI") ? clientPaths.geminiCLI : void 0
|
|
421
|
+
);
|
|
422
|
+
addIfDetected(
|
|
423
|
+
writers,
|
|
424
|
+
existsSync5(join5(homedir4(), ".codex")),
|
|
425
|
+
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
426
|
+
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
427
|
+
);
|
|
428
|
+
return writers;
|
|
429
|
+
}
|
|
430
|
+
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
431
|
+
const clientPaths = fabricConfig.clientPaths;
|
|
432
|
+
const claudeDetected = existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude"));
|
|
433
|
+
const claudeDesktopDetected = existsSync5(getClaudeDesktopConfigPath());
|
|
434
|
+
const cursorDetected = existsSync5(join5(workspaceRoot, ".cursor"));
|
|
435
|
+
const windsurfDetected = existsSync5(join5(workspaceRoot, ".windsurf"));
|
|
436
|
+
const rooDetected = existsSync5(join5(workspaceRoot, ".roo"));
|
|
437
|
+
const geminiDetected = existsSync5(join5(homedir4(), ".gemini")) || existsSync5(join5(workspaceRoot, "GEMINI.md"));
|
|
438
|
+
const codexDetected = existsSync5(join5(homedir4(), ".codex"));
|
|
439
|
+
return [
|
|
440
|
+
{
|
|
441
|
+
clientKind: "ClaudeCodeCLI",
|
|
442
|
+
label: "Claude Code CLI",
|
|
443
|
+
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
444
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
445
|
+
configPath: "project .claude/settings.json",
|
|
446
|
+
capabilities: {
|
|
447
|
+
bootstrap: true,
|
|
448
|
+
mcp: true,
|
|
449
|
+
hook: true,
|
|
450
|
+
skill: true
|
|
451
|
+
},
|
|
452
|
+
installedCapabilities: {
|
|
453
|
+
hook: true,
|
|
454
|
+
skill: true
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
clientKind: "ClaudeCodeDesktop",
|
|
459
|
+
label: "Claude Code Desktop",
|
|
460
|
+
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
461
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
462
|
+
configPath: "desktop Claude config",
|
|
463
|
+
capabilities: {
|
|
464
|
+
bootstrap: true,
|
|
465
|
+
mcp: true,
|
|
466
|
+
hook: false,
|
|
467
|
+
skill: false
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
clientKind: "Cursor",
|
|
472
|
+
label: "Cursor",
|
|
473
|
+
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
474
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
475
|
+
configPath: ".cursor/mcp.json",
|
|
476
|
+
capabilities: {
|
|
477
|
+
bootstrap: true,
|
|
478
|
+
mcp: true,
|
|
479
|
+
hook: false,
|
|
480
|
+
skill: false
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
clientKind: "Windsurf",
|
|
485
|
+
label: "Windsurf",
|
|
486
|
+
detected: windsurfDetected || hasExplicitPath(clientPaths, "windsurf"),
|
|
487
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
488
|
+
configPath: ".windsurf/mcp.json",
|
|
489
|
+
capabilities: {
|
|
490
|
+
bootstrap: true,
|
|
491
|
+
mcp: true,
|
|
492
|
+
hook: false,
|
|
493
|
+
skill: false
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
clientKind: "RooCode",
|
|
498
|
+
label: "Roo Code",
|
|
499
|
+
detected: rooDetected || hasExplicitPath(clientPaths, "rooCode"),
|
|
500
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
501
|
+
configPath: ".roo/mcp.json",
|
|
502
|
+
capabilities: {
|
|
503
|
+
bootstrap: true,
|
|
504
|
+
mcp: true,
|
|
505
|
+
hook: false,
|
|
506
|
+
skill: false
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
clientKind: "GeminiCLI",
|
|
511
|
+
label: "Gemini CLI",
|
|
512
|
+
detected: geminiDetected || hasExplicitPath(clientPaths, "geminiCLI"),
|
|
513
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
514
|
+
configPath: "~/.gemini/settings.json",
|
|
515
|
+
capabilities: {
|
|
516
|
+
bootstrap: true,
|
|
517
|
+
mcp: true,
|
|
518
|
+
hook: false,
|
|
519
|
+
skill: false
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
clientKind: "CodexCLI",
|
|
524
|
+
label: "Codex CLI",
|
|
525
|
+
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
526
|
+
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
527
|
+
configPath: "~/.codex/config.toml",
|
|
528
|
+
capabilities: {
|
|
529
|
+
bootstrap: true,
|
|
530
|
+
mcp: true,
|
|
531
|
+
hook: true,
|
|
532
|
+
skill: true
|
|
533
|
+
},
|
|
534
|
+
installedCapabilities: {
|
|
535
|
+
hook: existsSync5(join5(workspaceRoot, ".codex", "hooks.json")),
|
|
536
|
+
skill: existsSync5(join5(workspaceRoot, ".agents", "skills", "fabric-init", "SKILL.md"))
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/commands/bootstrap.ts
|
|
543
|
+
var CLIENT_ALIASES = {
|
|
544
|
+
claude: "claude",
|
|
545
|
+
"claude-code": "claude",
|
|
546
|
+
claudecode: "claude",
|
|
547
|
+
claudecli: "claude",
|
|
548
|
+
claudecodecli: "claude",
|
|
549
|
+
claudedesktop: "claude",
|
|
550
|
+
claudecodedesktop: "claude",
|
|
551
|
+
cursor: "cursor",
|
|
552
|
+
windsurf: "windsurf",
|
|
553
|
+
roo: "roo",
|
|
554
|
+
"roo-code": "roo",
|
|
555
|
+
roocode: "roo",
|
|
556
|
+
gemini: "gemini",
|
|
557
|
+
"gemini-cli": "gemini",
|
|
558
|
+
geminicli: "gemini",
|
|
559
|
+
codex: "codex",
|
|
560
|
+
"codex-cli": "codex",
|
|
561
|
+
codexcli: "codex"
|
|
562
|
+
};
|
|
563
|
+
var bootstrapCommand = defineCommand({
|
|
564
|
+
meta: {
|
|
565
|
+
name: "bootstrap",
|
|
566
|
+
description: t("cli.bootstrap.description")
|
|
567
|
+
},
|
|
568
|
+
subCommands: {
|
|
569
|
+
install: defineCommand({
|
|
570
|
+
meta: {
|
|
571
|
+
name: "install",
|
|
572
|
+
description: t("cli.bootstrap.install.description")
|
|
573
|
+
},
|
|
574
|
+
args: {
|
|
575
|
+
clients: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: t("cli.bootstrap.install.args.clients.description")
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
async run({ args }) {
|
|
581
|
+
const workspaceRoot = process.cwd();
|
|
582
|
+
const selectedClients = parseClientFilter(args.clients);
|
|
583
|
+
const result = await installBootstrap(workspaceRoot, {
|
|
584
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
|
|
585
|
+
});
|
|
586
|
+
if (result.details.length === 0) {
|
|
587
|
+
process.stderr.write(
|
|
588
|
+
`${t("cli.bootstrap.install.no-targets")}
|
|
589
|
+
`
|
|
590
|
+
);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
for (const detail of result.details) {
|
|
594
|
+
if (detail.action === "skipped") {
|
|
595
|
+
process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
|
|
596
|
+
`);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (detail.action === "prepended") {
|
|
600
|
+
process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
|
|
601
|
+
`);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
|
|
605
|
+
`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
})
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
async function installBootstrap(target, options = {}) {
|
|
612
|
+
const workspaceRoot = resolve5(target);
|
|
613
|
+
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
614
|
+
const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
|
|
615
|
+
const installed = [];
|
|
616
|
+
const skipped = [];
|
|
617
|
+
const details = [];
|
|
618
|
+
await ensureFabricBootstrapGuide(workspaceRoot, options.force);
|
|
619
|
+
for (const bootstrapTarget of targets) {
|
|
620
|
+
details.push({
|
|
621
|
+
client: bootstrapTarget.client,
|
|
622
|
+
path: resolve5(workspaceRoot, FABRIC_GUIDE_PATH),
|
|
623
|
+
action: "skipped"
|
|
624
|
+
});
|
|
625
|
+
skipped.push(bootstrapTarget.client);
|
|
626
|
+
}
|
|
627
|
+
return { installed, skipped, details };
|
|
628
|
+
}
|
|
629
|
+
function parseClientFilter(value) {
|
|
630
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
const clients = /* @__PURE__ */ new Set();
|
|
634
|
+
for (const rawClient of value.split(",")) {
|
|
635
|
+
const alias = rawClient.trim().toLowerCase();
|
|
636
|
+
const client = CLIENT_ALIASES[alias];
|
|
637
|
+
if (client === void 0) {
|
|
638
|
+
throw new Error(t("cli.bootstrap.errors.unknown-client", { client: rawClient }));
|
|
639
|
+
}
|
|
640
|
+
clients.add(client);
|
|
641
|
+
}
|
|
642
|
+
return clients;
|
|
643
|
+
}
|
|
644
|
+
function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
|
|
645
|
+
const targets = [];
|
|
646
|
+
const seenClients = /* @__PURE__ */ new Set();
|
|
647
|
+
const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
|
|
648
|
+
for (const clientKind of clientKinds) {
|
|
649
|
+
const bootstrapClient = mapClientKind(clientKind);
|
|
650
|
+
if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
seenClients.add(bootstrapClient);
|
|
654
|
+
targets.push({ client: clientKind, bootstrapClient });
|
|
655
|
+
}
|
|
656
|
+
return targets;
|
|
657
|
+
}
|
|
658
|
+
function mapClientKind(clientKind) {
|
|
659
|
+
switch (clientKind) {
|
|
660
|
+
case "ClaudeCodeCLI":
|
|
661
|
+
case "ClaudeCodeDesktop":
|
|
662
|
+
return "claude";
|
|
663
|
+
case "Cursor":
|
|
664
|
+
return "cursor";
|
|
665
|
+
case "Windsurf":
|
|
666
|
+
return "windsurf";
|
|
667
|
+
case "RooCode":
|
|
668
|
+
return "roo";
|
|
669
|
+
case "GeminiCLI":
|
|
670
|
+
return "gemini";
|
|
671
|
+
case "CodexCLI":
|
|
672
|
+
return "codex";
|
|
673
|
+
default:
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
function mapBootstrapClientToClientKind(client) {
|
|
678
|
+
switch (client) {
|
|
679
|
+
case "claude":
|
|
680
|
+
return "ClaudeCodeCLI";
|
|
681
|
+
case "cursor":
|
|
682
|
+
return "Cursor";
|
|
683
|
+
case "windsurf":
|
|
684
|
+
return "Windsurf";
|
|
685
|
+
case "roo":
|
|
686
|
+
return "RooCode";
|
|
687
|
+
case "gemini":
|
|
688
|
+
return "GeminiCLI";
|
|
689
|
+
case "codex":
|
|
690
|
+
return "CodexCLI";
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/commands/config.ts
|
|
695
|
+
import { existsSync as existsSync7 } from "fs";
|
|
696
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
697
|
+
import { resolve as resolve7 } from "path";
|
|
698
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
699
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
700
|
+
|
|
701
|
+
// src/commands/hooks.ts
|
|
702
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
703
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, parse as parse2, resolve as resolve6 } from "path";
|
|
704
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
705
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
706
|
+
var hooksCommand = defineCommand2({
|
|
707
|
+
meta: {
|
|
708
|
+
name: "hooks",
|
|
709
|
+
description: t("cli.hooks.description")
|
|
710
|
+
},
|
|
711
|
+
subCommands: {
|
|
712
|
+
install: defineCommand2({
|
|
713
|
+
meta: {
|
|
714
|
+
name: "install",
|
|
715
|
+
description: t("cli.hooks.install.description")
|
|
716
|
+
},
|
|
717
|
+
args: {
|
|
718
|
+
target: {
|
|
719
|
+
type: "string",
|
|
720
|
+
description: t("cli.hooks.install.args.target.description"),
|
|
721
|
+
default: process.cwd()
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
async run({ args }) {
|
|
725
|
+
const result = await installHooks(args.target);
|
|
726
|
+
if (result.hookAction === "skipped") {
|
|
727
|
+
writeStderr(t("cli.hooks.install.hook-skipped", { path: result.hookPath }));
|
|
728
|
+
} else if (result.hookAction === "appended") {
|
|
729
|
+
writeStderr(t("cli.hooks.install.hook-appended", { path: result.hookPath }));
|
|
730
|
+
} else {
|
|
731
|
+
writeStderr(t("cli.hooks.install.hook-created", { path: result.hookPath }));
|
|
732
|
+
}
|
|
733
|
+
if (result.prepareAction === "left") {
|
|
734
|
+
writeStderr(t("cli.hooks.install.prepare-left", { path: result.packageJsonPath }));
|
|
735
|
+
} else {
|
|
736
|
+
writeStderr(t("cli.hooks.install.prepare-added", { path: result.packageJsonPath }));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
})
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
async function installHooks(target, options = {}) {
|
|
743
|
+
const normalizedTarget = normalizeTarget2(target);
|
|
744
|
+
assertExistingDirectory(normalizedTarget);
|
|
745
|
+
const huskyDir = join6(normalizedTarget, ".husky");
|
|
746
|
+
const hookPath = join6(huskyDir, "pre-commit");
|
|
747
|
+
const packageJsonPath = join6(normalizedTarget, "package.json");
|
|
748
|
+
if (!existsSync6(packageJsonPath)) {
|
|
749
|
+
throw new Error(t("cli.hooks.errors.package-json-required", { path: packageJsonPath }));
|
|
750
|
+
}
|
|
751
|
+
mkdirSync2(huskyDir, { recursive: true });
|
|
752
|
+
const templateContent = readFileSync2(findTemplatePath2("templates/husky/pre-commit"), "utf8");
|
|
753
|
+
const hookAction = installHookFile(hookPath, templateContent, options.force);
|
|
754
|
+
chmodSync(hookPath, 493);
|
|
755
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
756
|
+
const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
|
|
757
|
+
const hadPrepare = typeof scripts.prepare === "string" && scripts.prepare.trim().length > 0;
|
|
758
|
+
let prepareAction = "left";
|
|
759
|
+
if (!hadPrepare) {
|
|
760
|
+
scripts.prepare = "husky install";
|
|
761
|
+
packageJson.scripts = scripts;
|
|
762
|
+
writeFileSync2(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
763
|
+
`, "utf8");
|
|
764
|
+
prepareAction = "added";
|
|
765
|
+
}
|
|
766
|
+
const installed = [];
|
|
767
|
+
const skipped = [];
|
|
768
|
+
if (hookAction === "skipped") {
|
|
769
|
+
skipped.push(hookPath);
|
|
770
|
+
} else {
|
|
771
|
+
installed.push(hookPath);
|
|
772
|
+
}
|
|
773
|
+
if (prepareAction === "left") {
|
|
774
|
+
skipped.push(packageJsonPath);
|
|
775
|
+
} else {
|
|
776
|
+
installed.push(packageJsonPath);
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
installed,
|
|
780
|
+
skipped,
|
|
781
|
+
hookPath,
|
|
782
|
+
packageJsonPath,
|
|
783
|
+
hookAction,
|
|
784
|
+
prepareAction
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function normalizeTarget2(targetInput) {
|
|
788
|
+
return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
|
|
789
|
+
}
|
|
790
|
+
function assertExistingDirectory(target) {
|
|
791
|
+
if (!existsSync6(target) || !statSync(target).isDirectory()) {
|
|
792
|
+
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function installHookFile(hookPath, templateContent, force) {
|
|
796
|
+
if (existsSync6(hookPath)) {
|
|
797
|
+
if (force) {
|
|
798
|
+
writeFileSync2(hookPath, templateContent, "utf8");
|
|
799
|
+
return "overwritten";
|
|
800
|
+
}
|
|
801
|
+
const existing = readFileSync2(hookPath, "utf8");
|
|
802
|
+
if (existing.includes("FAB_BIN=")) {
|
|
803
|
+
return "skipped";
|
|
804
|
+
}
|
|
805
|
+
const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
|
|
806
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
807
|
+
writeFileSync2(hookPath, `${existing}${separator}# --- Fabric ---
|
|
808
|
+
${fabricBlock}`, "utf8");
|
|
809
|
+
return "appended";
|
|
810
|
+
}
|
|
811
|
+
writeFileSync2(hookPath, templateContent, "utf8");
|
|
812
|
+
return "created";
|
|
813
|
+
}
|
|
814
|
+
function findTemplatePath2(relativePath) {
|
|
815
|
+
const currentModuleDir = dirname4(fileURLToPath2(import.meta.url));
|
|
816
|
+
const candidates = [
|
|
817
|
+
...templateCandidatesFrom2(process.cwd(), relativePath),
|
|
818
|
+
...templateCandidatesFrom2(currentModuleDir, relativePath)
|
|
819
|
+
];
|
|
820
|
+
for (const candidate of candidates) {
|
|
821
|
+
if (existsSync6(candidate)) {
|
|
822
|
+
return candidate;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
826
|
+
}
|
|
827
|
+
function templateCandidatesFrom2(start, relativePath) {
|
|
828
|
+
const candidates = [];
|
|
829
|
+
let current = resolve6(start);
|
|
830
|
+
while (true) {
|
|
831
|
+
candidates.push(join6(current, ...relativePath.split("/")));
|
|
832
|
+
const parent = dirname4(current);
|
|
833
|
+
if (parent === current || parse2(current).root === current) {
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
current = parent;
|
|
837
|
+
}
|
|
838
|
+
return candidates.reverse();
|
|
839
|
+
}
|
|
840
|
+
function writeStderr(message) {
|
|
841
|
+
process.stderr.write(`${message}
|
|
842
|
+
`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/commands/config.ts
|
|
846
|
+
var CLIENT_ALIASES2 = {
|
|
847
|
+
claude: "ClaudeCodeCLI",
|
|
848
|
+
claudecodecli: "ClaudeCodeCLI",
|
|
849
|
+
"claude-code-cli": "ClaudeCodeCLI",
|
|
850
|
+
claudecli: "ClaudeCodeCLI",
|
|
851
|
+
claudecodedesktop: "ClaudeCodeDesktop",
|
|
852
|
+
"claude-code-desktop": "ClaudeCodeDesktop",
|
|
853
|
+
claudedesktop: "ClaudeCodeDesktop",
|
|
854
|
+
cursor: "Cursor",
|
|
855
|
+
windsurf: "Windsurf",
|
|
856
|
+
roocode: "RooCode",
|
|
857
|
+
"roo-code": "RooCode",
|
|
858
|
+
roo: "RooCode",
|
|
859
|
+
geminicli: "GeminiCLI",
|
|
860
|
+
"gemini-cli": "GeminiCLI",
|
|
861
|
+
gemini: "GeminiCLI",
|
|
862
|
+
codexcli: "CodexCLI",
|
|
863
|
+
"codex-cli": "CodexCLI",
|
|
864
|
+
codex: "CodexCLI"
|
|
865
|
+
};
|
|
866
|
+
function parseClientFilter2(value) {
|
|
867
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
const clients = /* @__PURE__ */ new Set();
|
|
871
|
+
for (const rawClient of value.split(",")) {
|
|
872
|
+
const alias = rawClient.trim().toLowerCase();
|
|
873
|
+
const clientKind = CLIENT_ALIASES2[alias];
|
|
874
|
+
if (clientKind === void 0) {
|
|
875
|
+
throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
|
|
876
|
+
}
|
|
877
|
+
clients.add(clientKind);
|
|
878
|
+
}
|
|
879
|
+
return clients;
|
|
880
|
+
}
|
|
881
|
+
async function loadFabricConfig(workspaceRoot) {
|
|
882
|
+
const configPath = resolve7(workspaceRoot, "fabric.config.json");
|
|
883
|
+
if (!existsSync7(configPath)) {
|
|
884
|
+
return {};
|
|
885
|
+
}
|
|
886
|
+
const parsed = JSON.parse(await readFile3(configPath, "utf8"));
|
|
887
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
888
|
+
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
889
|
+
}
|
|
890
|
+
return parsed;
|
|
891
|
+
}
|
|
892
|
+
function resolveServerPath(override) {
|
|
893
|
+
if (override) return override;
|
|
894
|
+
if (process.env.FAB_SERVER_PATH) return resolve7(process.env.FAB_SERVER_PATH);
|
|
895
|
+
return fileURLToPath3(import.meta.resolve("@fenglimg/fabric-server"));
|
|
896
|
+
}
|
|
897
|
+
function writeStderr2(message) {
|
|
898
|
+
process.stderr.write(`${message}
|
|
899
|
+
`);
|
|
900
|
+
}
|
|
901
|
+
var configCmd = defineCommand3({
|
|
902
|
+
meta: {
|
|
903
|
+
name: "config",
|
|
904
|
+
description: t("cli.config.description")
|
|
905
|
+
},
|
|
906
|
+
subCommands: {
|
|
907
|
+
hooks: hooksCommand,
|
|
908
|
+
install: defineCommand3({
|
|
909
|
+
meta: {
|
|
910
|
+
name: "install",
|
|
911
|
+
description: t("cli.config.install.description")
|
|
912
|
+
},
|
|
913
|
+
args: {
|
|
914
|
+
clients: {
|
|
915
|
+
type: "string",
|
|
916
|
+
description: t("cli.config.install.args.clients.description")
|
|
917
|
+
},
|
|
918
|
+
"dry-run": {
|
|
919
|
+
type: "boolean",
|
|
920
|
+
description: t("cli.config.install.args.dry-run.description"),
|
|
921
|
+
default: false
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
async run({ args }) {
|
|
925
|
+
const selectedClients = parseClientFilter2(args.clients);
|
|
926
|
+
const result = await installMcpClients(process.cwd(), {
|
|
927
|
+
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
928
|
+
dryRun: args["dry-run"]
|
|
929
|
+
});
|
|
930
|
+
if (result.details.length === 0) {
|
|
931
|
+
writeStderr2(t("cli.config.install.no-configs"));
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
for (const detail of result.details) {
|
|
935
|
+
if (detail.action === "skipped") {
|
|
936
|
+
writeStderr2(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (detail.action === "dry-run" && detail.path !== null) {
|
|
940
|
+
writeStderr2(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (detail.path !== null) {
|
|
944
|
+
writeStderr2(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
})
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
async function installMcpClients(target, options = {}) {
|
|
952
|
+
const workspaceRoot = resolve7(target);
|
|
953
|
+
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
954
|
+
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
955
|
+
const serverPath = resolveServerPath(options.localServerPath);
|
|
956
|
+
const writers = resolveClients(workspaceRoot, fabricConfig).filter(
|
|
957
|
+
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
958
|
+
);
|
|
959
|
+
const installed = [];
|
|
960
|
+
const skipped = [];
|
|
961
|
+
const details = [];
|
|
962
|
+
for (const writer of writers) {
|
|
963
|
+
const configPath = await writer.detect(workspaceRoot);
|
|
964
|
+
if (configPath === null) {
|
|
965
|
+
skipped.push(writer.clientKind);
|
|
966
|
+
details.push({ client: writer.clientKind, path: null, action: "skipped" });
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (options.dryRun) {
|
|
970
|
+
skipped.push(writer.clientKind);
|
|
971
|
+
details.push({ client: writer.clientKind, path: configPath, action: "dry-run" });
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
await writer.write(serverPath, workspaceRoot);
|
|
975
|
+
installed.push(writer.clientKind);
|
|
976
|
+
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
977
|
+
}
|
|
978
|
+
return { installed, skipped, details };
|
|
979
|
+
}
|
|
980
|
+
|
|
40
981
|
// src/scanner/forensic.ts
|
|
41
982
|
import { execFileSync } from "child_process";
|
|
42
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
983
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
43
984
|
import { createRequire } from "module";
|
|
44
|
-
import { basename, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
|
|
985
|
+
import { basename, extname, isAbsolute as isAbsolute3, join as join7, posix, relative, resolve as resolve8, sep } from "path";
|
|
45
986
|
import {
|
|
46
987
|
forensicReportSchema
|
|
47
988
|
} from "@fenglimg/fabric-shared";
|
|
@@ -127,7 +1068,7 @@ var parserInitPromise = null;
|
|
|
127
1068
|
var languagePromiseByKind = {};
|
|
128
1069
|
var parserBundlePromiseByKind = {};
|
|
129
1070
|
async function buildForensicReport(targetInput) {
|
|
130
|
-
const target =
|
|
1071
|
+
const target = normalizeTarget3(targetInput);
|
|
131
1072
|
const framework = detectFramework(target);
|
|
132
1073
|
const topology = buildTopology(target);
|
|
133
1074
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -163,11 +1104,11 @@ async function buildForensicReport(targetInput) {
|
|
|
163
1104
|
}
|
|
164
1105
|
return validation.data;
|
|
165
1106
|
}
|
|
166
|
-
function
|
|
167
|
-
return
|
|
1107
|
+
function normalizeTarget3(targetInput) {
|
|
1108
|
+
return isAbsolute3(targetInput) ? targetInput : resolve8(process.cwd(), targetInput);
|
|
168
1109
|
}
|
|
169
1110
|
function buildTopology(root) {
|
|
170
|
-
|
|
1111
|
+
assertExistingDirectory2(root);
|
|
171
1112
|
const byExt = {};
|
|
172
1113
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
173
1114
|
const files = [];
|
|
@@ -180,7 +1121,7 @@ function buildTopology(root) {
|
|
|
180
1121
|
continue;
|
|
181
1122
|
}
|
|
182
1123
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
183
|
-
const absolutePath =
|
|
1124
|
+
const absolutePath = join7(current, entry.name);
|
|
184
1125
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
185
1126
|
if (relativePath.length === 0) {
|
|
186
1127
|
continue;
|
|
@@ -200,7 +1141,7 @@ function buildTopology(root) {
|
|
|
200
1141
|
if (!entry.isFile()) {
|
|
201
1142
|
continue;
|
|
202
1143
|
}
|
|
203
|
-
const stats =
|
|
1144
|
+
const stats = statSync2(absolutePath);
|
|
204
1145
|
const extension = extname(entry.name) || "[none]";
|
|
205
1146
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
206
1147
|
totalFiles += 1;
|
|
@@ -218,8 +1159,8 @@ function buildTopology(root) {
|
|
|
218
1159
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
219
1160
|
};
|
|
220
1161
|
}
|
|
221
|
-
function
|
|
222
|
-
if (!
|
|
1162
|
+
function assertExistingDirectory2(target) {
|
|
1163
|
+
if (!existsSync8(target) || !statSync2(target).isDirectory()) {
|
|
223
1164
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
224
1165
|
}
|
|
225
1166
|
}
|
|
@@ -268,7 +1209,7 @@ function getEntryPointReason(relativePath) {
|
|
|
268
1209
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
269
1210
|
const samples = [];
|
|
270
1211
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
271
|
-
const absolutePath =
|
|
1212
|
+
const absolutePath = join7(target, ...entryPoint.path.split("/"));
|
|
272
1213
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
273
1214
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
274
1215
|
frameworkKind,
|
|
@@ -288,7 +1229,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
288
1229
|
}
|
|
289
1230
|
function readFirstLines(path, lineLimit) {
|
|
290
1231
|
try {
|
|
291
|
-
const lines =
|
|
1232
|
+
const lines = readFileSync3(path, "utf8").split(/\r?\n/);
|
|
292
1233
|
if (lines.at(-1) === "") {
|
|
293
1234
|
lines.pop();
|
|
294
1235
|
}
|
|
@@ -305,12 +1246,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
305
1246
|
}
|
|
306
1247
|
}
|
|
307
1248
|
function readPackageDependencies(target) {
|
|
308
|
-
const packageJsonPath =
|
|
309
|
-
if (!
|
|
1249
|
+
const packageJsonPath = join7(target, "package.json");
|
|
1250
|
+
if (!existsSync8(packageJsonPath)) {
|
|
310
1251
|
return /* @__PURE__ */ new Map();
|
|
311
1252
|
}
|
|
312
1253
|
try {
|
|
313
|
-
const packageJson = JSON.parse(
|
|
1254
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
314
1255
|
return new Map([
|
|
315
1256
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
316
1257
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -646,16 +1587,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
646
1587
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
647
1588
|
}
|
|
648
1589
|
function readReadmeInfo(target) {
|
|
649
|
-
const readmePath =
|
|
650
|
-
const hasContributing =
|
|
651
|
-
if (!
|
|
1590
|
+
const readmePath = join7(target, "README.md");
|
|
1591
|
+
const hasContributing = existsSync8(join7(target, "CONTRIBUTING.md"));
|
|
1592
|
+
if (!existsSync8(readmePath)) {
|
|
652
1593
|
return {
|
|
653
1594
|
quality: "missing",
|
|
654
1595
|
line_count: 0,
|
|
655
1596
|
has_contributing: hasContributing
|
|
656
1597
|
};
|
|
657
1598
|
}
|
|
658
|
-
const readme =
|
|
1599
|
+
const readme = readFileSync3(readmePath, "utf8");
|
|
659
1600
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
660
1601
|
return {
|
|
661
1602
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -888,7 +1829,7 @@ function buildDomainAssertion(codeSamples) {
|
|
|
888
1829
|
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
889
1830
|
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
890
1831
|
]),
|
|
891
|
-
proposedRule: "Preserve domain-specific module names when mirroring structure into
|
|
1832
|
+
proposedRule: "Preserve domain-specific module names when mirroring structure into .fabric/rules/."
|
|
892
1833
|
});
|
|
893
1834
|
}
|
|
894
1835
|
function createAssertion(input) {
|
|
@@ -1133,10 +2074,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1133
2074
|
return recommendations;
|
|
1134
2075
|
}
|
|
1135
2076
|
function readProjectName(target) {
|
|
1136
|
-
const packageJsonPath =
|
|
1137
|
-
if (
|
|
2077
|
+
const packageJsonPath = join7(target, "package.json");
|
|
2078
|
+
if (existsSync8(packageJsonPath)) {
|
|
1138
2079
|
try {
|
|
1139
|
-
const packageJson = JSON.parse(
|
|
2080
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
1140
2081
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1141
2082
|
return packageJson.name;
|
|
1142
2083
|
}
|
|
@@ -1147,7 +2088,7 @@ function readProjectName(target) {
|
|
|
1147
2088
|
return basename(target);
|
|
1148
2089
|
}
|
|
1149
2090
|
function getCliVersion() {
|
|
1150
|
-
return true ? "1.
|
|
2091
|
+
return true ? "1.7.0" : "unknown";
|
|
1151
2092
|
}
|
|
1152
2093
|
function sortRecord(record) {
|
|
1153
2094
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1165,10 +2106,10 @@ var CODEX_SESSION_START_HOOK_TEMPLATE = "templates/codex-hooks/fabric-session-st
|
|
|
1165
2106
|
var CODEX_STOP_HOOK_TEMPLATE = "templates/codex-hooks/fabric-stop-reminder.cjs";
|
|
1166
2107
|
var CODEX_SESSION_START_COMMAND = ".codex/hooks/fabric-session-start.cjs";
|
|
1167
2108
|
var CODEX_STOP_COMMAND = ".codex/hooks/fabric-stop-reminder.cjs";
|
|
1168
|
-
var LOCAL_FABRIC_SERVER_PATH =
|
|
2109
|
+
var LOCAL_FABRIC_SERVER_PATH = join8("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1169
2110
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1170
2111
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1171
|
-
var initCommand =
|
|
2112
|
+
var initCommand = defineCommand4({
|
|
1172
2113
|
meta: {
|
|
1173
2114
|
name: "init",
|
|
1174
2115
|
description: t("cli.init.description")
|
|
@@ -1243,13 +2184,13 @@ async function runInitCommand(args) {
|
|
|
1243
2184
|
logger(step);
|
|
1244
2185
|
}
|
|
1245
2186
|
if (intent.options.planOnly) {
|
|
1246
|
-
|
|
2187
|
+
writeStderr3(t("cli.init.compat.plan"));
|
|
1247
2188
|
}
|
|
1248
2189
|
if (args.interactive === false) {
|
|
1249
|
-
|
|
2190
|
+
writeStderr3(t("cli.init.compat.interactive"));
|
|
1250
2191
|
}
|
|
1251
2192
|
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1252
|
-
|
|
2193
|
+
writeStderr3(t("cli.init.compat.legacy-stage-flags"));
|
|
1253
2194
|
}
|
|
1254
2195
|
const supports = detectClientSupports(intent.target);
|
|
1255
2196
|
const basePlan = await buildInitExecutionPlan({
|
|
@@ -1261,13 +2202,13 @@ async function runInitCommand(args) {
|
|
|
1261
2202
|
});
|
|
1262
2203
|
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, args, createDefaultInitWizardAdapter()) : basePlan;
|
|
1263
2204
|
if (plan === null) {
|
|
1264
|
-
|
|
2205
|
+
writeStderr3(t("cli.init.wizard.cancelled"));
|
|
1265
2206
|
throw new Error(t("cli.init.wizard.cancelled"));
|
|
1266
2207
|
}
|
|
1267
2208
|
return executeInitExecutionPlan(plan);
|
|
1268
2209
|
}
|
|
1269
2210
|
function resolveInitCliIntent(args, targetInput) {
|
|
1270
|
-
const target =
|
|
2211
|
+
const target = normalizeTarget4(targetInput);
|
|
1271
2212
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
1272
2213
|
const terminalInteractive = isInteractiveInit();
|
|
1273
2214
|
const planOnly = args.plan === true;
|
|
@@ -1322,10 +2263,10 @@ async function buildInitExecutionPlan(input) {
|
|
|
1322
2263
|
}
|
|
1323
2264
|
async function executeInitExecutionPlan(plan) {
|
|
1324
2265
|
if (plan.options.force) {
|
|
1325
|
-
|
|
2266
|
+
writeStderr3(t("cli.init.force.warning", { path: plan.target }));
|
|
1326
2267
|
}
|
|
1327
2268
|
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1328
|
-
|
|
2269
|
+
writeStderr3(formatInitModeBanner(plan.options));
|
|
1329
2270
|
}
|
|
1330
2271
|
if (plan.interactive) {
|
|
1331
2272
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
@@ -1371,27 +2312,30 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1371
2312
|
};
|
|
1372
2313
|
}
|
|
1373
2314
|
async function buildInitFabricPlan(target, options) {
|
|
1374
|
-
|
|
1375
|
-
const fabricDir =
|
|
1376
|
-
const bootstrapPath =
|
|
1377
|
-
const forensicPath =
|
|
1378
|
-
const
|
|
1379
|
-
const
|
|
1380
|
-
const
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1383
|
-
const
|
|
1384
|
-
const
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
2315
|
+
assertExistingDirectory3(target);
|
|
2316
|
+
const fabricDir = join8(target, ".fabric");
|
|
2317
|
+
const bootstrapPath = join8(fabricDir, "bootstrap", "README.md");
|
|
2318
|
+
const forensicPath = join8(fabricDir, "forensic.json");
|
|
2319
|
+
const taxonomyPath = join8(fabricDir, "INITIAL_TAXONOMY.md");
|
|
2320
|
+
const rulesDir = join8(fabricDir, "rules");
|
|
2321
|
+
const eventsPath = join8(fabricDir, "events.jsonl");
|
|
2322
|
+
const claudeSkillPath = join8(target, ".claude", "skills", "agents-md-init", "SKILL.md");
|
|
2323
|
+
const codexSkillPath = join8(target, ".agents", "skills", "fabric-init", "SKILL.md");
|
|
2324
|
+
const codexSessionStartHookPath = join8(target, ".codex", "hooks", "fabric-session-start.cjs");
|
|
2325
|
+
const codexStopHookPath = join8(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
|
|
2326
|
+
const codexHooksConfigPath = join8(target, ".codex", "hooks.json");
|
|
2327
|
+
const claudeHookPath = join8(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
|
|
2328
|
+
const claudeSettingsPath = join8(target, ".claude", "settings.json");
|
|
2329
|
+
const metaPath = join8(fabricDir, "agents.meta.json");
|
|
1387
2330
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1388
2331
|
const bootstrapAction = planFreshPath(bootstrapPath, options);
|
|
1389
2332
|
const metaAction = planFreshPath(metaPath, options);
|
|
1390
|
-
const
|
|
2333
|
+
const taxonomyAction = planFreshPath(taxonomyPath, options);
|
|
2334
|
+
const eventsAction = planFreshPath(eventsPath, options);
|
|
1391
2335
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
1392
2336
|
const forensicReport = await buildForensicReport(target);
|
|
1393
|
-
const humanLockTemplate = readFileSync2(findTemplatePath("templates/fabric/human-lock.json"), "utf8");
|
|
1394
2337
|
const bootstrapContent = await buildFabricBootstrapGuide(target);
|
|
2338
|
+
const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
|
|
1395
2339
|
const bootstrapHash = sha256(bootstrapContent);
|
|
1396
2340
|
const meta = createInitialMeta(bootstrapHash);
|
|
1397
2341
|
return {
|
|
@@ -1405,31 +2349,33 @@ async function buildInitFabricPlan(target, options) {
|
|
|
1405
2349
|
metaPath,
|
|
1406
2350
|
metaAction,
|
|
1407
2351
|
meta,
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
2352
|
+
taxonomyPath,
|
|
2353
|
+
taxonomyAction,
|
|
2354
|
+
taxonomyContent,
|
|
2355
|
+
rulesDir,
|
|
2356
|
+
eventsPath,
|
|
2357
|
+
eventsAction,
|
|
1412
2358
|
forensicPath,
|
|
1413
2359
|
forensicAction,
|
|
1414
2360
|
forensicReport,
|
|
1415
|
-
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath,
|
|
1416
|
-
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath,
|
|
2361
|
+
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath3(CLAUDE_INIT_SKILL_TEMPLATE), options),
|
|
2362
|
+
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath3(CODEX_INIT_SKILL_TEMPLATE), options),
|
|
1417
2363
|
codexSessionStartHook: buildOptionalTemplateWritePlan(
|
|
1418
2364
|
codexSessionStartHookPath,
|
|
1419
|
-
|
|
2365
|
+
findTemplatePath3(CODEX_SESSION_START_HOOK_TEMPLATE),
|
|
1420
2366
|
options,
|
|
1421
2367
|
true
|
|
1422
2368
|
),
|
|
1423
2369
|
codexStopHook: buildOptionalTemplateWritePlan(
|
|
1424
2370
|
codexStopHookPath,
|
|
1425
|
-
|
|
2371
|
+
findTemplatePath3(CODEX_STOP_HOOK_TEMPLATE),
|
|
1426
2372
|
options,
|
|
1427
2373
|
true
|
|
1428
2374
|
),
|
|
1429
2375
|
codexHooksConfig: buildCodexHooksConfigPlan(codexHooksConfigPath, options),
|
|
1430
2376
|
claudeHook: buildOptionalTemplateWritePlan(
|
|
1431
2377
|
claudeHookPath,
|
|
1432
|
-
|
|
2378
|
+
findTemplatePath3(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
1433
2379
|
options,
|
|
1434
2380
|
true
|
|
1435
2381
|
),
|
|
@@ -1440,17 +2386,20 @@ function executeInitFabricPlan(plan) {
|
|
|
1440
2386
|
if (plan.replaceFabricDir) {
|
|
1441
2387
|
rmSync(plan.fabricDir, { force: true });
|
|
1442
2388
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
2389
|
+
mkdirSync3(plan.fabricDir, { recursive: true });
|
|
2390
|
+
mkdirSync3(dirname5(plan.bootstrapPath), { recursive: true });
|
|
1445
2391
|
preparePlannedPath(plan.bootstrapPath, plan.bootstrapAction);
|
|
1446
|
-
|
|
2392
|
+
writeFileSync3(plan.bootstrapPath, plan.bootstrapContent, "utf8");
|
|
1447
2393
|
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1448
|
-
|
|
2394
|
+
writeFileSync3(plan.metaPath, `${JSON.stringify(plan.meta, null, 2)}
|
|
1449
2395
|
`, "utf8");
|
|
1450
|
-
preparePlannedPath(plan.
|
|
1451
|
-
|
|
2396
|
+
preparePlannedPath(plan.taxonomyPath, plan.taxonomyAction);
|
|
2397
|
+
writeFileSync3(plan.taxonomyPath, ensureTrailingNewline2(plan.taxonomyContent), "utf8");
|
|
2398
|
+
mkdirSync3(plan.rulesDir, { recursive: true });
|
|
2399
|
+
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
2400
|
+
writeFileSync3(plan.eventsPath, "", "utf8");
|
|
1452
2401
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1453
|
-
|
|
2402
|
+
writeFileSync3(plan.forensicPath, `${JSON.stringify(plan.forensicReport, null, 2)}
|
|
1454
2403
|
`, "utf8");
|
|
1455
2404
|
applyOptionalTemplateWritePlan(plan.claudeSkill);
|
|
1456
2405
|
applyOptionalTemplateWritePlan(plan.codexSkill);
|
|
@@ -1464,8 +2413,10 @@ function executeInitFabricPlan(plan) {
|
|
|
1464
2413
|
bootstrapAction: plan.bootstrapAction,
|
|
1465
2414
|
metaPath: plan.metaPath,
|
|
1466
2415
|
metaAction: plan.metaAction,
|
|
1467
|
-
|
|
1468
|
-
|
|
2416
|
+
taxonomyPath: plan.taxonomyPath,
|
|
2417
|
+
taxonomyAction: plan.taxonomyAction,
|
|
2418
|
+
eventsPath: plan.eventsPath,
|
|
2419
|
+
eventsAction: plan.eventsAction,
|
|
1469
2420
|
forensicPath: plan.forensicPath,
|
|
1470
2421
|
forensicAction: plan.forensicAction,
|
|
1471
2422
|
claudeSkillPath: plan.claudeSkill.path,
|
|
@@ -1526,19 +2477,20 @@ function exhaustiveInitStagePlan(value) {
|
|
|
1526
2477
|
function printInitScaffoldResult(created) {
|
|
1527
2478
|
console.log(formatInitPathAction(created.bootstrapPath, created.bootstrapAction));
|
|
1528
2479
|
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
1529
|
-
console.log(formatInitPathAction(created.
|
|
2480
|
+
console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
|
|
2481
|
+
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
1530
2482
|
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
2483
|
+
writeStderr3(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
|
|
2484
|
+
writeStderr3(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
|
|
2485
|
+
writeStderr3(
|
|
1534
2486
|
formatOptionalInitPathAction(created.codexSessionStartHookPath, created.codexSessionStartHookAction)
|
|
1535
2487
|
);
|
|
1536
|
-
|
|
2488
|
+
writeStderr3(
|
|
1537
2489
|
formatOptionalInitPathAction(created.codexStopHookPath, created.codexStopHookAction)
|
|
1538
2490
|
);
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2491
|
+
writeStderr3(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
|
|
2492
|
+
writeStderr3(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
|
|
2493
|
+
writeStderr3(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
1542
2494
|
}
|
|
1543
2495
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
1544
2496
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
@@ -1576,8 +2528,10 @@ function buildPlanOnlyScaffoldResult(plan) {
|
|
|
1576
2528
|
bootstrapAction: plan.bootstrapAction,
|
|
1577
2529
|
metaPath: plan.metaPath,
|
|
1578
2530
|
metaAction: plan.metaAction,
|
|
1579
|
-
|
|
1580
|
-
|
|
2531
|
+
taxonomyPath: plan.taxonomyPath,
|
|
2532
|
+
taxonomyAction: plan.taxonomyAction,
|
|
2533
|
+
eventsPath: plan.eventsPath,
|
|
2534
|
+
eventsAction: plan.eventsAction,
|
|
1581
2535
|
forensicPath: plan.forensicPath,
|
|
1582
2536
|
forensicAction: plan.forensicAction,
|
|
1583
2537
|
claudeSkillPath: plan.claudeSkill.path,
|
|
@@ -1621,12 +2575,12 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1621
2575
|
case "mcp": {
|
|
1622
2576
|
if (stage.installMode === "local") {
|
|
1623
2577
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
1624
|
-
|
|
1625
|
-
|
|
2578
|
+
writeStderr3(t("cli.init.mcp.install.local"));
|
|
2579
|
+
writeStderr3(t("cli.init.mcp.local.installing", { manager }));
|
|
1626
2580
|
installLocalFabricServer(plan.target, manager);
|
|
1627
|
-
|
|
2581
|
+
writeStderr3(t("cli.init.mcp.local.installed"));
|
|
1628
2582
|
} else {
|
|
1629
|
-
|
|
2583
|
+
writeStderr3(t("cli.init.mcp.install.global"));
|
|
1630
2584
|
}
|
|
1631
2585
|
const result = await installMcpClients(plan.target, {
|
|
1632
2586
|
force: plan.options.force,
|
|
@@ -1648,15 +2602,15 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1648
2602
|
return exhaustiveInitStagePlan(stage);
|
|
1649
2603
|
}
|
|
1650
2604
|
} catch (error) {
|
|
1651
|
-
|
|
2605
|
+
writeStderr3(formatInitStageFailure(stageName, error));
|
|
1652
2606
|
return { name: stageName, disposition: "failed" };
|
|
1653
2607
|
}
|
|
1654
2608
|
}
|
|
1655
2609
|
function shouldReplaceWritableDirectory(path, options) {
|
|
1656
|
-
if (!
|
|
2610
|
+
if (!existsSync9(path)) {
|
|
1657
2611
|
return false;
|
|
1658
2612
|
}
|
|
1659
|
-
if (
|
|
2613
|
+
if (statSync3(path).isDirectory()) {
|
|
1660
2614
|
return false;
|
|
1661
2615
|
}
|
|
1662
2616
|
if (!options?.force) {
|
|
@@ -1665,7 +2619,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
1665
2619
|
return true;
|
|
1666
2620
|
}
|
|
1667
2621
|
function planFreshPath(path, options) {
|
|
1668
|
-
if (!
|
|
2622
|
+
if (!existsSync9(path)) {
|
|
1669
2623
|
return "created";
|
|
1670
2624
|
}
|
|
1671
2625
|
if (!options?.force) {
|
|
@@ -1674,13 +2628,13 @@ function planFreshPath(path, options) {
|
|
|
1674
2628
|
return "overwritten";
|
|
1675
2629
|
}
|
|
1676
2630
|
function preparePlannedPath(path, action) {
|
|
1677
|
-
|
|
1678
|
-
if (action === "overwritten" &&
|
|
2631
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
2632
|
+
if (action === "overwritten" && existsSync9(path)) {
|
|
1679
2633
|
rmSync(path, { recursive: true, force: true });
|
|
1680
2634
|
}
|
|
1681
2635
|
}
|
|
1682
2636
|
function buildOptionalTemplateWritePlan(path, templatePath, options, executable = false) {
|
|
1683
|
-
const existed =
|
|
2637
|
+
const existed = existsSync9(path);
|
|
1684
2638
|
if (existed && !options?.force) {
|
|
1685
2639
|
return { path, action: "skipped", templatePath, executable };
|
|
1686
2640
|
}
|
|
@@ -1695,10 +2649,10 @@ function applyOptionalTemplateWritePlan(plan) {
|
|
|
1695
2649
|
if (plan.action === "skipped") {
|
|
1696
2650
|
return;
|
|
1697
2651
|
}
|
|
1698
|
-
|
|
2652
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1699
2653
|
copyFileSync(plan.templatePath, plan.path);
|
|
1700
2654
|
if (plan.executable) {
|
|
1701
|
-
|
|
2655
|
+
chmodSync2(plan.path, 493);
|
|
1702
2656
|
}
|
|
1703
2657
|
}
|
|
1704
2658
|
function buildCodexHooksConfigValue() {
|
|
@@ -1720,7 +2674,7 @@ function buildCodexHooksConfigValue() {
|
|
|
1720
2674
|
};
|
|
1721
2675
|
}
|
|
1722
2676
|
function buildCodexHooksConfigPlan(configPath, options) {
|
|
1723
|
-
const action = !
|
|
2677
|
+
const action = !existsSync9(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
|
|
1724
2678
|
return {
|
|
1725
2679
|
path: configPath,
|
|
1726
2680
|
action,
|
|
@@ -1731,37 +2685,37 @@ function applyJsonWritePlan(plan) {
|
|
|
1731
2685
|
if (plan.action === "skipped") {
|
|
1732
2686
|
return;
|
|
1733
2687
|
}
|
|
1734
|
-
|
|
2688
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1735
2689
|
writeJsonAtomically(plan.path, plan.value);
|
|
1736
2690
|
}
|
|
1737
2691
|
function buildClaudeSettingsWritePlan(settingsPath, options) {
|
|
1738
2692
|
let settings;
|
|
1739
2693
|
let action = "updated";
|
|
1740
|
-
if (!
|
|
2694
|
+
if (!existsSync9(settingsPath)) {
|
|
1741
2695
|
settings = {};
|
|
1742
2696
|
action = "created";
|
|
1743
2697
|
} else {
|
|
1744
2698
|
try {
|
|
1745
|
-
const parsed = JSON.parse(
|
|
2699
|
+
const parsed = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
1746
2700
|
if (!isRecord(parsed)) {
|
|
1747
|
-
|
|
2701
|
+
writeStderr3(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
|
|
1748
2702
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1749
2703
|
}
|
|
1750
2704
|
settings = parsed;
|
|
1751
2705
|
} catch (error) {
|
|
1752
2706
|
const reason = error instanceof Error ? error.message : "unknown parse error";
|
|
1753
|
-
|
|
2707
|
+
writeStderr3(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
|
|
1754
2708
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1755
2709
|
}
|
|
1756
2710
|
}
|
|
1757
2711
|
if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
|
|
1758
|
-
|
|
2712
|
+
writeStderr3(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
|
|
1759
2713
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1760
2714
|
}
|
|
1761
2715
|
const hooks = settings.hooks ?? {};
|
|
1762
2716
|
const stopHooksValue = hooks.Stop;
|
|
1763
2717
|
if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
|
|
1764
|
-
|
|
2718
|
+
writeStderr3(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
|
|
1765
2719
|
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
1766
2720
|
}
|
|
1767
2721
|
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
@@ -1796,7 +2750,7 @@ function applyClaudeSettingsWritePlan(plan) {
|
|
|
1796
2750
|
if (plan.value === null) {
|
|
1797
2751
|
return;
|
|
1798
2752
|
}
|
|
1799
|
-
|
|
2753
|
+
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
1800
2754
|
writeJsonAtomically(plan.path, plan.value);
|
|
1801
2755
|
}
|
|
1802
2756
|
function createDefaultInitWizardAdapter() {
|
|
@@ -1951,23 +2905,23 @@ function formatInitModeBadge(options) {
|
|
|
1951
2905
|
}
|
|
1952
2906
|
return t("cli.init.mode.badge.default");
|
|
1953
2907
|
}
|
|
1954
|
-
function
|
|
1955
|
-
return
|
|
2908
|
+
function normalizeTarget4(targetInput) {
|
|
2909
|
+
return isAbsolute4(targetInput) ? targetInput : resolve9(process.cwd(), targetInput);
|
|
1956
2910
|
}
|
|
1957
|
-
function
|
|
1958
|
-
if (!
|
|
2911
|
+
function assertExistingDirectory3(target) {
|
|
2912
|
+
if (!existsSync9(target) || !statSync3(target).isDirectory()) {
|
|
1959
2913
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
1960
2914
|
}
|
|
1961
2915
|
}
|
|
1962
2916
|
function detectPackageManager(cwd) {
|
|
1963
|
-
const workspaceRoot =
|
|
1964
|
-
if (
|
|
2917
|
+
const workspaceRoot = resolve9(cwd);
|
|
2918
|
+
if (existsSync9(join8(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
1965
2919
|
return "pnpm";
|
|
1966
2920
|
}
|
|
1967
|
-
if (
|
|
2921
|
+
if (existsSync9(join8(workspaceRoot, "yarn.lock"))) {
|
|
1968
2922
|
return "yarn";
|
|
1969
2923
|
}
|
|
1970
|
-
if (
|
|
2924
|
+
if (existsSync9(join8(workspaceRoot, "package-lock.json"))) {
|
|
1971
2925
|
return "npm";
|
|
1972
2926
|
}
|
|
1973
2927
|
return "npm";
|
|
@@ -1976,7 +2930,7 @@ function resolveMcpInstallMode(rawMode) {
|
|
|
1976
2930
|
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
1977
2931
|
return rawMode ?? "global";
|
|
1978
2932
|
}
|
|
1979
|
-
|
|
2933
|
+
writeStderr3(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
1980
2934
|
return "global";
|
|
1981
2935
|
}
|
|
1982
2936
|
function installLocalFabricServer(target, manager) {
|
|
@@ -2003,26 +2957,80 @@ function createInitialMeta(agentsHash) {
|
|
|
2003
2957
|
}
|
|
2004
2958
|
};
|
|
2005
2959
|
}
|
|
2006
|
-
function
|
|
2007
|
-
const
|
|
2960
|
+
function buildInitialTaxonomyMarkdown(forensicReport) {
|
|
2961
|
+
const frameworkInfo = forensicReport.framework;
|
|
2962
|
+
const framework = [frameworkInfo?.kind ?? "unknown", frameworkInfo?.subkind ?? ""].filter((value) => value.trim() !== "").join(" / ") || "unknown";
|
|
2963
|
+
const keyDirs = forensicReport.topology?.key_dirs?.slice(0, 8) ?? [];
|
|
2964
|
+
const candidateFiles = forensicReport.candidate_files?.slice(0, 8) ?? [];
|
|
2965
|
+
const generatedAt = forensicReport.generated_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2966
|
+
return `# Fabric Initial Taxonomy
|
|
2967
|
+
|
|
2968
|
+
**Date**: ${generatedAt}
|
|
2969
|
+
**Base Architecture**: L0/L1/L2 Tiered System
|
|
2970
|
+
**Detected Framework**: ${framework}
|
|
2971
|
+
|
|
2972
|
+
## Origin Logic
|
|
2973
|
+
|
|
2974
|
+
- **L0 \u5224\u5B9A**: \u5168\u5C40\u534F\u4F5C\u7A33\u5B9A\u6027\u89C4\u5219\u3002\u5178\u578B\u6765\u6E90\u5305\u62EC\u4ED3\u5E93\u6839\u914D\u7F6E\u3001package metadata\u3001Fabric \u5185\u90E8\u534F\u8BAE\u548C\u4E0D\u53EF\u968F\u5C40\u90E8\u4E1A\u52A1\u6F02\u79FB\u7684\u7EA6\u675F\u3002
|
|
2975
|
+
- **L1 \u5224\u5B9A**: \u9886\u57DF/\u6A21\u5757\u7EA7\u89C4\u5219\u3002\u4F9D\u636E\u6280\u672F\u6808\u3001\u76EE\u5F55\u804C\u8D23\u3001\u6846\u67B6\u7279\u5F81\u548C\u529F\u80FD\u6A21\u5757\u5212\u5206\uFF0C\u800C\u4E0D\u662F\u8DEF\u5F84\u6DF1\u5EA6\u3002
|
|
2976
|
+
- **L2 \u5224\u5B9A**: \u5177\u4F53\u811A\u672C\u3001\u8D44\u6E90\u6216\u5C40\u90E8\u4E1A\u52A1\u72B6\u6001\u89C4\u5219\u3002\u7528\u4E8E\u627F\u8F7D\u7279\u5B9A\u6587\u4EF6\u3001\u8D44\u6E90\u3001\u5386\u53F2\u8865\u4E01\u548C\u5C40\u90E8\u5904\u7406\u7EC6\u5219\u3002
|
|
2977
|
+
|
|
2978
|
+
## Initial L1 Buckets
|
|
2979
|
+
|
|
2980
|
+
${formatInitialL1Buckets(keyDirs)}
|
|
2981
|
+
|
|
2982
|
+
## L2 Candidate Signals
|
|
2983
|
+
|
|
2984
|
+
${formatInitialL2Signals(candidateFiles)}
|
|
2985
|
+
|
|
2986
|
+
## Evolution Guide
|
|
2987
|
+
|
|
2988
|
+
- \u6D89\u53CA\u5168\u4ED3\u534F\u4F5C\u7A33\u5B9A\u6027\u7684\u89C4\u5219\u8FDB\u5165 L0\u3002
|
|
2989
|
+
- \u6D89\u53CA\u6280\u672F\u9886\u57DF\u3001\u6846\u67B6\u6A21\u5757\u6216\u529F\u80FD\u6A21\u5757\u7684\u89C4\u5219\u8FDB\u5165 L1\u3002
|
|
2990
|
+
- \u6D89\u53CA\u5177\u4F53\u6587\u4EF6\u3001\u5177\u4F53\u8D44\u6E90\u6216\u5C40\u90E8\u4E1A\u52A1\u72B6\u6001\u7684\u89C4\u5219\u8FDB\u5165 L2\u3002
|
|
2991
|
+
- \u51B2\u7A81\u65F6\u6267\u884C\u89E3\u91CA\u56FA\u5B9A\u4E3A L2 > L1 > L0\uFF1B\u540C\u5C42\u5185\u624D\u4F7F\u7528 priority \u6392\u5E8F\u3002
|
|
2992
|
+
`;
|
|
2993
|
+
}
|
|
2994
|
+
function formatInitialL1Buckets(keyDirs) {
|
|
2995
|
+
if (keyDirs.length === 0) {
|
|
2996
|
+
return "- **L1-General**: \u521D\u59CB\u5316\u65F6\u672A\u68C0\u6D4B\u5230\u7A33\u5B9A\u76EE\u5F55\u8F74\u7EBF\uFF0C\u540E\u7EED\u4F9D\u636E\u6280\u672F\u6808\u548C\u6A21\u5757\u804C\u8D23\u6F14\u8FDB\u3002";
|
|
2997
|
+
}
|
|
2998
|
+
return keyDirs.map((dir) => `- **L1-${sanitizeTaxonomyLabel(dir)}**: \u6302\u8F7D\u4F9D\u636E\u2014\u2014forensic topology detected \`${dir}\`.`).join("\n");
|
|
2999
|
+
}
|
|
3000
|
+
function formatInitialL2Signals(candidateFiles) {
|
|
3001
|
+
if (candidateFiles.length === 0) {
|
|
3002
|
+
return "- \u6682\u672A\u8BC6\u522B\u660E\u786E L2 \u5019\u9009\u6587\u4EF6\u3002";
|
|
3003
|
+
}
|
|
3004
|
+
return candidateFiles.map((entry) => `- \`${entry.path}\`: ${entry.family} \u2014 ${entry.rationale}`).join("\n");
|
|
3005
|
+
}
|
|
3006
|
+
function sanitizeTaxonomyLabel(value) {
|
|
3007
|
+
const sanitized = value.replaceAll("\\", "/").split("/").filter(Boolean).join("-").replace(/[^A-Za-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
3008
|
+
return sanitized === "" ? "General" : sanitized;
|
|
3009
|
+
}
|
|
3010
|
+
function ensureTrailingNewline2(value) {
|
|
3011
|
+
return value.endsWith("\n") ? value : `${value}
|
|
3012
|
+
`;
|
|
3013
|
+
}
|
|
3014
|
+
function findTemplatePath3(relativePath) {
|
|
3015
|
+
const currentModuleDir = dirname5(fileURLToPath4(import.meta.url));
|
|
2008
3016
|
const candidates = [
|
|
2009
|
-
...
|
|
2010
|
-
...
|
|
3017
|
+
...templateCandidatesFrom3(process.cwd(), relativePath),
|
|
3018
|
+
...templateCandidatesFrom3(currentModuleDir, relativePath)
|
|
2011
3019
|
];
|
|
2012
3020
|
for (const candidate of candidates) {
|
|
2013
|
-
if (
|
|
3021
|
+
if (existsSync9(candidate)) {
|
|
2014
3022
|
return candidate;
|
|
2015
3023
|
}
|
|
2016
3024
|
}
|
|
2017
3025
|
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
2018
3026
|
}
|
|
2019
|
-
function
|
|
3027
|
+
function templateCandidatesFrom3(start, relativePath) {
|
|
2020
3028
|
const candidates = [];
|
|
2021
|
-
let current =
|
|
3029
|
+
let current = resolve9(start);
|
|
2022
3030
|
while (true) {
|
|
2023
|
-
candidates.push(
|
|
2024
|
-
const parent =
|
|
2025
|
-
if (parent === current ||
|
|
3031
|
+
candidates.push(join8(current, ...relativePath.split("/")));
|
|
3032
|
+
const parent = dirname5(current);
|
|
3033
|
+
if (parent === current || parse3(current).root === current) {
|
|
2026
3034
|
break;
|
|
2027
3035
|
}
|
|
2028
3036
|
current = parent;
|
|
@@ -2045,7 +3053,7 @@ function isClaudeInitReminderStopEntry(entry) {
|
|
|
2045
3053
|
}
|
|
2046
3054
|
function writeJsonAtomically(path, value) {
|
|
2047
3055
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
2048
|
-
|
|
3056
|
+
writeFileSync3(tempPath, `${JSON.stringify(value, null, 2)}
|
|
2049
3057
|
`, "utf8");
|
|
2050
3058
|
renameSync(tempPath, path);
|
|
2051
3059
|
}
|
|
@@ -2120,7 +3128,8 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
2120
3128
|
console.log(t("cli.init.plan.writes"));
|
|
2121
3129
|
console.log(` - ${target}/.fabric/bootstrap/README.md`);
|
|
2122
3130
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
2123
|
-
console.log(` - ${target}/.fabric/
|
|
3131
|
+
console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
|
|
3132
|
+
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
2124
3133
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
2125
3134
|
}
|
|
2126
3135
|
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
@@ -2284,7 +3293,7 @@ function skippedStageLabel() {
|
|
|
2284
3293
|
function failedStageLabel() {
|
|
2285
3294
|
return paint.error(t("cli.init.stages.failed"));
|
|
2286
3295
|
}
|
|
2287
|
-
function
|
|
3296
|
+
function writeStderr3(message) {
|
|
2288
3297
|
process.stderr.write(`${message}
|
|
2289
3298
|
`);
|
|
2290
3299
|
}
|