@fenglimg/fabric-cli 1.8.0-rc.2 → 2.0.0-rc.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.
- package/dist/{chunk-QPCRBQ5Y.js → chunk-5LOYBXWD.js} +0 -1
- package/dist/chunk-UHNP7T7W.js +740 -0
- package/dist/{doctor-F52XWWZC.js → doctor-DUHWLAYD.js} +1 -1
- package/dist/index.js +5 -5
- package/dist/{init-CAZN4S4T.js → init-DRHUYHYA.js} +224 -875
- package/dist/scan-HU2EGITF.js +20 -0
- package/dist/{serve-466QXQ5Q.js → serve-3LXXSBFR.js} +1 -1
- package/package.json +3 -3
- package/templates/agents-md/AGENTS.md.template +55 -17
- package/dist/chunk-NMMUETVK.js +0 -216
- package/dist/scan-NNBNGIZG.js +0 -12
- package/templates/agents-md/variants/cocos.md +0 -20
- package/templates/agents-md/variants/next.md +0 -20
- package/templates/agents-md/variants/vite.md +0 -20
- package/templates/bootstrap/GEMINI.md +0 -8
- package/templates/bootstrap/roo-fabric.md +0 -5
- package/templates/bootstrap/windsurf-fabric.md +0 -5
- package/templates/claude-hooks/fabric-init-reminder.cjs +0 -18
- package/templates/claude-skills/fabric-init/SKILL.md +0 -163
- package/templates/codex-hooks/fabric-session-start.cjs +0 -19
- package/templates/codex-hooks/fabric-stop-reminder.cjs +0 -18
- package/templates/codex-skills/fabric-init/SKILL.md +0 -162
- package/templates/husky/pre-commit +0 -9
- package/templates/skill-source/fabric-init/SOURCE.md +0 -157
- package/templates/skill-source/fabric-init/clients.json +0 -17
|
@@ -1,125 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-
|
|
3
|
+
detectFramework,
|
|
4
|
+
runInitScan
|
|
5
|
+
} from "./chunk-UHNP7T7W.js";
|
|
6
6
|
import {
|
|
7
7
|
createDebugLogger,
|
|
8
8
|
displayWidth,
|
|
9
9
|
padEnd,
|
|
10
10
|
paint,
|
|
11
|
-
readFabricConfig,
|
|
12
11
|
resolveDevMode,
|
|
13
12
|
t
|
|
14
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5LOYBXWD.js";
|
|
15
14
|
|
|
16
15
|
// src/commands/init.ts
|
|
17
|
-
import {
|
|
16
|
+
import { randomUUID } from "crypto";
|
|
17
|
+
import { homedir as homedir5 } from "os";
|
|
18
18
|
import * as childProcess from "child_process";
|
|
19
|
-
import { appendFileSync,
|
|
20
|
-
import { dirname as
|
|
21
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
19
|
+
import { appendFileSync, existsSync as existsSync8, mkdirSync, rmSync, statSync as statSync3, writeFileSync } from "fs";
|
|
20
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join6, resolve as resolve7 } from "path";
|
|
22
21
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
22
|
+
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
23
|
+
import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
24
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
25
25
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
26
26
|
|
|
27
|
-
// src/
|
|
28
|
-
import { existsSync
|
|
29
|
-
import {
|
|
30
|
-
import { fileURLToPath } from "url";
|
|
31
|
-
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
32
|
-
var AGENTS_TEMPLATE_BY_FRAMEWORK = {
|
|
33
|
-
"cocos-creator": "templates/agents-md/variants/cocos.md",
|
|
34
|
-
vite: "templates/agents-md/variants/vite.md",
|
|
35
|
-
next: "templates/agents-md/variants/next.md"
|
|
36
|
-
};
|
|
37
|
-
var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
|
|
38
|
-
async function buildFabricBootstrapGuide(target) {
|
|
39
|
-
const workspaceRoot = normalizeTarget(target);
|
|
40
|
-
const scanReport = await createScanReport(workspaceRoot);
|
|
41
|
-
const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
|
|
42
|
-
const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
|
|
43
|
-
return ensureTrailingNewline(
|
|
44
|
-
template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
async function ensureFabricBootstrapGuide(workspaceRoot, force) {
|
|
48
|
-
const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
|
|
49
|
-
if (existsSync(guidePath) && !force) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
mkdirSync(dirname(guidePath), { recursive: true });
|
|
53
|
-
await atomicWriteText(guidePath, await buildFabricBootstrapGuide(workspaceRoot));
|
|
54
|
-
}
|
|
55
|
-
function findBootstrapTemplatePath(frameworkKind) {
|
|
56
|
-
const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
|
|
57
|
-
return findTemplatePath(relativePath);
|
|
58
|
-
}
|
|
59
|
-
function readPackageName(target) {
|
|
60
|
-
const packageJsonPath = join(target, "package.json");
|
|
61
|
-
if (!existsSync(packageJsonPath)) {
|
|
62
|
-
return void 0;
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
66
|
-
return packageJson.name;
|
|
67
|
-
} catch {
|
|
68
|
-
return void 0;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function findTemplatePath(relativePath) {
|
|
72
|
-
const currentModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
73
|
-
const candidates = [
|
|
74
|
-
...templateCandidatesFrom(process.cwd(), relativePath),
|
|
75
|
-
...templateCandidatesFrom(currentModuleDir, relativePath)
|
|
76
|
-
];
|
|
77
|
-
for (const candidate of candidates) {
|
|
78
|
-
if (existsSync(candidate)) {
|
|
79
|
-
return candidate;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
83
|
-
}
|
|
84
|
-
function templateCandidatesFrom(start, relativePath) {
|
|
85
|
-
const candidates = [];
|
|
86
|
-
let current = resolve(start);
|
|
87
|
-
while (true) {
|
|
88
|
-
candidates.push(join(current, ...relativePath.split("/")));
|
|
89
|
-
const parent = dirname(current);
|
|
90
|
-
if (parent === current || parse(current).root === current) {
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
current = parent;
|
|
94
|
-
}
|
|
95
|
-
return candidates.reverse();
|
|
96
|
-
}
|
|
97
|
-
function ensureTrailingNewline(content) {
|
|
98
|
-
return content.endsWith("\n") ? content : `${content}
|
|
99
|
-
`;
|
|
100
|
-
}
|
|
101
|
-
function normalizeTarget(targetInput) {
|
|
102
|
-
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// src/commands/bootstrap.ts
|
|
27
|
+
// src/commands/config.ts
|
|
28
|
+
import { existsSync as existsSync6 } from "fs";
|
|
29
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
106
30
|
import { resolve as resolve5 } from "path";
|
|
107
|
-
import {
|
|
31
|
+
import { fileURLToPath } from "url";
|
|
32
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
108
33
|
|
|
109
34
|
// src/config/resolver.ts
|
|
110
|
-
import { existsSync as
|
|
111
|
-
import { join as
|
|
35
|
+
import { existsSync as existsSync4 } from "fs";
|
|
36
|
+
import { join as join4 } from "path";
|
|
112
37
|
import { homedir as homedir4 } from "os";
|
|
113
38
|
|
|
114
39
|
// src/config/claude-code.ts
|
|
115
|
-
import { existsSync as
|
|
116
|
-
import { join as
|
|
40
|
+
import { existsSync as existsSync2 } from "fs";
|
|
41
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
117
42
|
import { homedir as homedir2, platform } from "os";
|
|
118
43
|
|
|
119
44
|
// src/config/json.ts
|
|
120
|
-
import { existsSync
|
|
45
|
+
import { existsSync } from "fs";
|
|
121
46
|
import { mkdir, readFile } from "fs/promises";
|
|
122
|
-
import { dirname
|
|
47
|
+
import { dirname, join, resolve } from "path";
|
|
123
48
|
import { homedir } from "os";
|
|
124
49
|
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
125
50
|
|
|
@@ -150,12 +75,12 @@ function expandHome(filePath) {
|
|
|
150
75
|
return homedir();
|
|
151
76
|
}
|
|
152
77
|
if (filePath.startsWith("~/")) {
|
|
153
|
-
return
|
|
78
|
+
return join(homedir(), filePath.slice(2));
|
|
154
79
|
}
|
|
155
80
|
return filePath;
|
|
156
81
|
}
|
|
157
82
|
function normalizeConfigPath(filePath) {
|
|
158
|
-
return
|
|
83
|
+
return resolve(expandHome(filePath));
|
|
159
84
|
}
|
|
160
85
|
async function readJsonConfig(configPath) {
|
|
161
86
|
try {
|
|
@@ -178,7 +103,7 @@ async function readJsonConfig(configPath) {
|
|
|
178
103
|
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
179
104
|
const existing = await readJsonConfig(configPath);
|
|
180
105
|
const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
|
|
181
|
-
await mkdir(
|
|
106
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
182
107
|
await atomicWriteJson(configPath, merged, { indent: 2 });
|
|
183
108
|
}
|
|
184
109
|
var JsonClientConfigWriter = class {
|
|
@@ -213,12 +138,12 @@ var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
|
213
138
|
// or ~/.claude.json for user scope.
|
|
214
139
|
// Detection still checks ~/.claude to confirm Claude Code is installed.
|
|
215
140
|
defaultPath(workspaceRoot) {
|
|
216
|
-
const globalClaudeDir =
|
|
217
|
-
const projectClaudeDir =
|
|
218
|
-
if (!
|
|
141
|
+
const globalClaudeDir = join(homedir(), ".claude");
|
|
142
|
+
const projectClaudeDir = join(workspaceRoot, ".claude");
|
|
143
|
+
if (!existsSync(globalClaudeDir) && !existsSync(projectClaudeDir)) {
|
|
219
144
|
return null;
|
|
220
145
|
}
|
|
221
|
-
return this.scope === "user" ?
|
|
146
|
+
return this.scope === "user" ? join(homedir(), ".claude.json") : join(workspaceRoot, ".mcp.json");
|
|
222
147
|
}
|
|
223
148
|
};
|
|
224
149
|
var CursorWriter = class extends JsonClientConfigWriter {
|
|
@@ -227,8 +152,8 @@ var CursorWriter = class extends JsonClientConfigWriter {
|
|
|
227
152
|
super(configuredPath);
|
|
228
153
|
}
|
|
229
154
|
defaultPath(workspaceRoot) {
|
|
230
|
-
const cursorDir =
|
|
231
|
-
return
|
|
155
|
+
const cursorDir = join(workspaceRoot, ".cursor");
|
|
156
|
+
return existsSync(cursorDir) ? join(cursorDir, "mcp.json") : null;
|
|
232
157
|
}
|
|
233
158
|
};
|
|
234
159
|
|
|
@@ -236,12 +161,12 @@ var CursorWriter = class extends JsonClientConfigWriter {
|
|
|
236
161
|
function getClaudeDesktopConfigPath() {
|
|
237
162
|
const os = platform();
|
|
238
163
|
if (os === "darwin") {
|
|
239
|
-
return
|
|
164
|
+
return join2(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
240
165
|
}
|
|
241
166
|
if (os === "win32") {
|
|
242
|
-
return
|
|
167
|
+
return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
243
168
|
}
|
|
244
|
-
return
|
|
169
|
+
return join2(homedir2(), ".config", "Claude", "claude_desktop_config.json");
|
|
245
170
|
}
|
|
246
171
|
var ClaudeCodeDesktopWriter = class {
|
|
247
172
|
clientKind = "ClaudeCodeDesktop";
|
|
@@ -251,7 +176,7 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
251
176
|
}
|
|
252
177
|
async detect(_workspaceRoot, overridePath) {
|
|
253
178
|
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
254
|
-
return
|
|
179
|
+
return existsSync2(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
255
180
|
}
|
|
256
181
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
257
182
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -266,17 +191,17 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
266
191
|
};
|
|
267
192
|
|
|
268
193
|
// src/config/toml.ts
|
|
269
|
-
import { existsSync as
|
|
194
|
+
import { existsSync as existsSync3 } from "fs";
|
|
270
195
|
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
271
|
-
import { dirname as
|
|
196
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
272
197
|
import { homedir as homedir3 } from "os";
|
|
273
|
-
import { atomicWriteText
|
|
198
|
+
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
274
199
|
function expandHome2(filePath) {
|
|
275
200
|
if (filePath === "~") {
|
|
276
201
|
return homedir3();
|
|
277
202
|
}
|
|
278
203
|
if (filePath.startsWith("~/")) {
|
|
279
|
-
return
|
|
204
|
+
return join3(homedir3(), filePath.slice(2));
|
|
280
205
|
}
|
|
281
206
|
return filePath;
|
|
282
207
|
}
|
|
@@ -342,10 +267,10 @@ var CodexTOMLConfigWriter = class {
|
|
|
342
267
|
async detect(_workspaceRoot, overridePath) {
|
|
343
268
|
const explicitPath = overridePath ?? this.configuredPath;
|
|
344
269
|
if (explicitPath !== void 0) {
|
|
345
|
-
return
|
|
270
|
+
return resolve3(expandHome2(explicitPath));
|
|
346
271
|
}
|
|
347
|
-
const codexDir =
|
|
348
|
-
return
|
|
272
|
+
const codexDir = join3(homedir3(), ".codex");
|
|
273
|
+
return existsSync3(codexDir) ? resolve3(join3(codexDir, "config.toml")) : null;
|
|
349
274
|
}
|
|
350
275
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
351
276
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -354,8 +279,8 @@ var CodexTOMLConfigWriter = class {
|
|
|
354
279
|
}
|
|
355
280
|
const rawConfig = await readTomlConfigText(configPath);
|
|
356
281
|
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
357
|
-
await mkdir2(
|
|
358
|
-
await
|
|
282
|
+
await mkdir2(dirname2(configPath), { recursive: true });
|
|
283
|
+
await atomicWriteText(configPath, nextConfig);
|
|
359
284
|
}
|
|
360
285
|
};
|
|
361
286
|
|
|
@@ -375,25 +300,25 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
375
300
|
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
376
301
|
addIfDetected(
|
|
377
302
|
writers,
|
|
378
|
-
|
|
303
|
+
existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude")),
|
|
379
304
|
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
380
305
|
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
381
306
|
);
|
|
382
307
|
addIfDetected(
|
|
383
308
|
writers,
|
|
384
|
-
|
|
309
|
+
existsSync4(getClaudeDesktopConfigPath()),
|
|
385
310
|
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
386
311
|
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
387
312
|
);
|
|
388
313
|
addIfDetected(
|
|
389
314
|
writers,
|
|
390
|
-
|
|
315
|
+
existsSync4(join4(workspaceRoot, ".cursor")),
|
|
391
316
|
(configuredPath) => new CursorWriter(configuredPath),
|
|
392
317
|
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
393
318
|
);
|
|
394
319
|
addIfDetected(
|
|
395
320
|
writers,
|
|
396
|
-
|
|
321
|
+
existsSync4(join4(homedir4(), ".codex")),
|
|
397
322
|
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
398
323
|
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
399
324
|
);
|
|
@@ -401,10 +326,10 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
401
326
|
}
|
|
402
327
|
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
403
328
|
const clientPaths = fabricConfig.clientPaths;
|
|
404
|
-
const claudeDetected =
|
|
405
|
-
const claudeDesktopDetected =
|
|
406
|
-
const cursorDetected =
|
|
407
|
-
const codexDetected =
|
|
329
|
+
const claudeDetected = existsSync4(join4(homedir4(), ".claude")) || existsSync4(join4(workspaceRoot, ".claude"));
|
|
330
|
+
const claudeDesktopDetected = existsSync4(getClaudeDesktopConfigPath());
|
|
331
|
+
const cursorDetected = existsSync4(join4(workspaceRoot, ".cursor"));
|
|
332
|
+
const codexDetected = existsSync4(join4(homedir4(), ".codex"));
|
|
408
333
|
return [
|
|
409
334
|
{
|
|
410
335
|
clientKind: "ClaudeCodeCLI",
|
|
@@ -462,166 +387,27 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
462
387
|
skill: true
|
|
463
388
|
},
|
|
464
389
|
installedCapabilities: {
|
|
465
|
-
hook:
|
|
466
|
-
|
|
390
|
+
hook: existsSync4(join4(workspaceRoot, ".codex", "hooks.json")),
|
|
391
|
+
// v2/rc.2: v1 client-side init skill removed; skill-installation probes
|
|
392
|
+
// will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
|
|
393
|
+
// fabric-review, fabric-import). Until then there is nothing to probe.
|
|
394
|
+
skill: false
|
|
467
395
|
}
|
|
468
396
|
}
|
|
469
397
|
];
|
|
470
398
|
}
|
|
471
399
|
|
|
472
|
-
// src/commands/bootstrap.ts
|
|
473
|
-
var CLIENT_ALIASES = {
|
|
474
|
-
claude: "claude",
|
|
475
|
-
"claude-code": "claude",
|
|
476
|
-
claudecode: "claude",
|
|
477
|
-
claudecli: "claude",
|
|
478
|
-
claudecodecli: "claude",
|
|
479
|
-
claudedesktop: "claude",
|
|
480
|
-
claudecodedesktop: "claude",
|
|
481
|
-
cursor: "cursor",
|
|
482
|
-
codex: "codex",
|
|
483
|
-
"codex-cli": "codex",
|
|
484
|
-
codexcli: "codex"
|
|
485
|
-
};
|
|
486
|
-
var bootstrapCommand = defineCommand({
|
|
487
|
-
meta: {
|
|
488
|
-
name: "bootstrap",
|
|
489
|
-
description: t("cli.bootstrap.description")
|
|
490
|
-
},
|
|
491
|
-
subCommands: {
|
|
492
|
-
install: defineCommand({
|
|
493
|
-
meta: {
|
|
494
|
-
name: "install",
|
|
495
|
-
description: t("cli.bootstrap.install.description")
|
|
496
|
-
},
|
|
497
|
-
args: {
|
|
498
|
-
clients: {
|
|
499
|
-
type: "string",
|
|
500
|
-
description: t("cli.bootstrap.install.args.clients.description")
|
|
501
|
-
}
|
|
502
|
-
},
|
|
503
|
-
async run({ args }) {
|
|
504
|
-
const workspaceRoot = process.cwd();
|
|
505
|
-
const selectedClients = parseClientFilter(args.clients);
|
|
506
|
-
const result = await installBootstrap(workspaceRoot, {
|
|
507
|
-
clients: selectedClients === null ? void 0 : Array.from(selectedClients, mapBootstrapClientToClientKind)
|
|
508
|
-
});
|
|
509
|
-
if (result.details.length === 0) {
|
|
510
|
-
process.stderr.write(
|
|
511
|
-
`${t("cli.bootstrap.install.no-targets")}
|
|
512
|
-
`
|
|
513
|
-
);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
for (const detail of result.details) {
|
|
517
|
-
if (detail.action === "skipped") {
|
|
518
|
-
process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: detail.path })}
|
|
519
|
-
`);
|
|
520
|
-
continue;
|
|
521
|
-
}
|
|
522
|
-
if (detail.action === "prepended") {
|
|
523
|
-
process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: detail.path })}
|
|
524
|
-
`);
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
process.stderr.write(`${t("cli.bootstrap.install.installed", { path: detail.path })}
|
|
528
|
-
`);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
})
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
async function installBootstrap(target, options = {}) {
|
|
535
|
-
const workspaceRoot = resolve5(target);
|
|
536
|
-
const fabricConfig = readFabricConfig(workspaceRoot);
|
|
537
|
-
const targets = resolveBootstrapTargets(workspaceRoot, fabricConfig, options.clients);
|
|
538
|
-
const installed = [];
|
|
539
|
-
const skipped = [];
|
|
540
|
-
const details = [];
|
|
541
|
-
await ensureFabricBootstrapGuide(workspaceRoot, options.force);
|
|
542
|
-
for (const bootstrapTarget of targets) {
|
|
543
|
-
details.push({
|
|
544
|
-
client: bootstrapTarget.client,
|
|
545
|
-
path: resolve5(workspaceRoot, FABRIC_GUIDE_PATH),
|
|
546
|
-
action: "skipped"
|
|
547
|
-
});
|
|
548
|
-
skipped.push(bootstrapTarget.client);
|
|
549
|
-
}
|
|
550
|
-
return { installed, skipped, details };
|
|
551
|
-
}
|
|
552
|
-
function parseClientFilter(value) {
|
|
553
|
-
if (value === void 0 || value.trim().length === 0) {
|
|
554
|
-
return null;
|
|
555
|
-
}
|
|
556
|
-
const clients = /* @__PURE__ */ new Set();
|
|
557
|
-
for (const rawClient of value.split(",")) {
|
|
558
|
-
const alias = rawClient.trim().toLowerCase();
|
|
559
|
-
const client = CLIENT_ALIASES[alias];
|
|
560
|
-
if (client === void 0) {
|
|
561
|
-
throw new Error(t("cli.bootstrap.errors.unknown-client", { client: rawClient }));
|
|
562
|
-
}
|
|
563
|
-
clients.add(client);
|
|
564
|
-
}
|
|
565
|
-
return clients;
|
|
566
|
-
}
|
|
567
|
-
function resolveBootstrapTargets(workspaceRoot, fabricConfig, selectedClients) {
|
|
568
|
-
const targets = [];
|
|
569
|
-
const seenClients = /* @__PURE__ */ new Set();
|
|
570
|
-
const clientKinds = selectedClients ?? resolveClients(workspaceRoot, fabricConfig).map((writer) => writer.clientKind);
|
|
571
|
-
for (const clientKind of clientKinds) {
|
|
572
|
-
const bootstrapClient = mapClientKind(clientKind);
|
|
573
|
-
if (bootstrapClient === null || seenClients.has(bootstrapClient)) {
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
seenClients.add(bootstrapClient);
|
|
577
|
-
targets.push({ client: clientKind, bootstrapClient });
|
|
578
|
-
}
|
|
579
|
-
return targets;
|
|
580
|
-
}
|
|
581
|
-
function mapClientKind(clientKind) {
|
|
582
|
-
switch (clientKind) {
|
|
583
|
-
case "ClaudeCodeCLI":
|
|
584
|
-
case "ClaudeCodeDesktop":
|
|
585
|
-
return "claude";
|
|
586
|
-
case "Cursor":
|
|
587
|
-
return "cursor";
|
|
588
|
-
case "CodexCLI":
|
|
589
|
-
return "codex";
|
|
590
|
-
default:
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
function mapBootstrapClientToClientKind(client) {
|
|
595
|
-
switch (client) {
|
|
596
|
-
case "claude":
|
|
597
|
-
return "ClaudeCodeCLI";
|
|
598
|
-
case "cursor":
|
|
599
|
-
return "Cursor";
|
|
600
|
-
case "codex":
|
|
601
|
-
return "CodexCLI";
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// src/commands/config.ts
|
|
606
|
-
import { existsSync as existsSync7 } from "fs";
|
|
607
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
608
|
-
import { resolve as resolve7 } from "path";
|
|
609
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
610
|
-
import { defineCommand as defineCommand3 } from "citty";
|
|
611
|
-
|
|
612
400
|
// src/commands/hooks.ts
|
|
613
|
-
import {
|
|
614
|
-
import {
|
|
615
|
-
import {
|
|
616
|
-
|
|
617
|
-
import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
618
|
-
var hooksCommand = defineCommand2({
|
|
401
|
+
import { existsSync as existsSync5, statSync } from "fs";
|
|
402
|
+
import { isAbsolute, resolve as resolve4 } from "path";
|
|
403
|
+
import { defineCommand } from "citty";
|
|
404
|
+
var hooksCommand = defineCommand({
|
|
619
405
|
meta: {
|
|
620
406
|
name: "hooks",
|
|
621
407
|
description: t("cli.hooks.description")
|
|
622
408
|
},
|
|
623
409
|
subCommands: {
|
|
624
|
-
install:
|
|
410
|
+
install: defineCommand({
|
|
625
411
|
meta: {
|
|
626
412
|
name: "install",
|
|
627
413
|
description: t("cli.hooks.install.description")
|
|
@@ -634,127 +420,29 @@ var hooksCommand = defineCommand2({
|
|
|
634
420
|
}
|
|
635
421
|
},
|
|
636
422
|
async run({ args }) {
|
|
637
|
-
|
|
638
|
-
if (result.hookAction === "skipped") {
|
|
639
|
-
writeStderr(t("cli.hooks.install.hook-skipped", { path: result.hookPath }));
|
|
640
|
-
} else if (result.hookAction === "appended") {
|
|
641
|
-
writeStderr(t("cli.hooks.install.hook-appended", { path: result.hookPath }));
|
|
642
|
-
} else {
|
|
643
|
-
writeStderr(t("cli.hooks.install.hook-created", { path: result.hookPath }));
|
|
644
|
-
}
|
|
645
|
-
if (result.prepareAction === "left") {
|
|
646
|
-
writeStderr(t("cli.hooks.install.prepare-left", { path: result.packageJsonPath }));
|
|
647
|
-
} else {
|
|
648
|
-
writeStderr(t("cli.hooks.install.prepare-added", { path: result.packageJsonPath }));
|
|
649
|
-
}
|
|
423
|
+
await installHooks(args.target);
|
|
650
424
|
}
|
|
651
425
|
})
|
|
652
426
|
}
|
|
653
427
|
});
|
|
654
|
-
async function installHooks(target,
|
|
655
|
-
const normalizedTarget =
|
|
428
|
+
async function installHooks(target, _options = {}) {
|
|
429
|
+
const normalizedTarget = normalizeTarget(target);
|
|
656
430
|
assertExistingDirectory(normalizedTarget);
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (!existsSync6(packageJsonPath)) {
|
|
661
|
-
throw new Error(t("cli.hooks.errors.package-json-required", { path: packageJsonPath }));
|
|
662
|
-
}
|
|
663
|
-
mkdirSync2(huskyDir, { recursive: true });
|
|
664
|
-
const templateContent = readFileSync2(findTemplatePath2("templates/husky/pre-commit"), "utf8");
|
|
665
|
-
const hookAction = await installHookFile(hookPath, templateContent, options.force);
|
|
666
|
-
chmodSync(hookPath, 493);
|
|
667
|
-
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
668
|
-
const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts) ? packageJson.scripts : {};
|
|
669
|
-
const hadPrepare = typeof scripts.prepare === "string" && scripts.prepare.trim().length > 0;
|
|
670
|
-
let prepareAction = "left";
|
|
671
|
-
if (!hadPrepare) {
|
|
672
|
-
scripts.prepare = "husky install";
|
|
673
|
-
packageJson.scripts = scripts;
|
|
674
|
-
await atomicWriteJson2(packageJsonPath, packageJson);
|
|
675
|
-
prepareAction = "added";
|
|
676
|
-
}
|
|
677
|
-
const installed = [];
|
|
678
|
-
const skipped = [];
|
|
679
|
-
if (hookAction === "skipped") {
|
|
680
|
-
skipped.push(hookPath);
|
|
681
|
-
} else {
|
|
682
|
-
installed.push(hookPath);
|
|
683
|
-
}
|
|
684
|
-
if (prepareAction === "left") {
|
|
685
|
-
skipped.push(packageJsonPath);
|
|
686
|
-
} else {
|
|
687
|
-
installed.push(packageJsonPath);
|
|
688
|
-
}
|
|
689
|
-
return {
|
|
690
|
-
installed,
|
|
691
|
-
skipped,
|
|
692
|
-
hookPath,
|
|
693
|
-
packageJsonPath,
|
|
694
|
-
hookAction,
|
|
695
|
-
prepareAction
|
|
696
|
-
};
|
|
431
|
+
throw new Error(
|
|
432
|
+
`fab hooks install is not available in v2 (husky pre-commit template removed). Use per-client hooks under .claude/ and .codex/ instead. Target: ${normalizedTarget}`
|
|
433
|
+
);
|
|
697
434
|
}
|
|
698
|
-
function
|
|
699
|
-
return
|
|
435
|
+
function normalizeTarget(targetInput) {
|
|
436
|
+
return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
|
|
700
437
|
}
|
|
701
438
|
function assertExistingDirectory(target) {
|
|
702
|
-
if (!
|
|
439
|
+
if (!existsSync5(target) || !statSync(target).isDirectory()) {
|
|
703
440
|
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
704
441
|
}
|
|
705
442
|
}
|
|
706
|
-
async function installHookFile(hookPath, templateContent, force) {
|
|
707
|
-
if (existsSync6(hookPath)) {
|
|
708
|
-
if (force) {
|
|
709
|
-
await atomicWriteText3(hookPath, templateContent);
|
|
710
|
-
return "overwritten";
|
|
711
|
-
}
|
|
712
|
-
const existing = readFileSync2(hookPath, "utf8");
|
|
713
|
-
if (existing.includes("FAB_BIN=")) {
|
|
714
|
-
return "skipped";
|
|
715
|
-
}
|
|
716
|
-
const fabricBlock = templateContent.replace(/^#!\/bin\/sh\n/, "");
|
|
717
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
718
|
-
await atomicWriteText3(hookPath, `${existing}${separator}# --- Fabric ---
|
|
719
|
-
${fabricBlock}`);
|
|
720
|
-
return "appended";
|
|
721
|
-
}
|
|
722
|
-
await atomicWriteText3(hookPath, templateContent);
|
|
723
|
-
return "created";
|
|
724
|
-
}
|
|
725
|
-
function findTemplatePath2(relativePath) {
|
|
726
|
-
const currentModuleDir = dirname4(fileURLToPath2(import.meta.url));
|
|
727
|
-
const candidates = [
|
|
728
|
-
...templateCandidatesFrom2(process.cwd(), relativePath),
|
|
729
|
-
...templateCandidatesFrom2(currentModuleDir, relativePath)
|
|
730
|
-
];
|
|
731
|
-
for (const candidate of candidates) {
|
|
732
|
-
if (existsSync6(candidate)) {
|
|
733
|
-
return candidate;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
737
|
-
}
|
|
738
|
-
function templateCandidatesFrom2(start, relativePath) {
|
|
739
|
-
const candidates = [];
|
|
740
|
-
let current = resolve6(start);
|
|
741
|
-
while (true) {
|
|
742
|
-
candidates.push(join6(current, ...relativePath.split("/")));
|
|
743
|
-
const parent = dirname4(current);
|
|
744
|
-
if (parent === current || parse2(current).root === current) {
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
747
|
-
current = parent;
|
|
748
|
-
}
|
|
749
|
-
return candidates.reverse();
|
|
750
|
-
}
|
|
751
|
-
function writeStderr(message) {
|
|
752
|
-
process.stderr.write(`${message}
|
|
753
|
-
`);
|
|
754
|
-
}
|
|
755
443
|
|
|
756
444
|
// src/commands/config.ts
|
|
757
|
-
var
|
|
445
|
+
var CLIENT_ALIASES = {
|
|
758
446
|
claude: "ClaudeCodeCLI",
|
|
759
447
|
claudecodecli: "ClaudeCodeCLI",
|
|
760
448
|
"claude-code-cli": "ClaudeCodeCLI",
|
|
@@ -767,14 +455,14 @@ var CLIENT_ALIASES2 = {
|
|
|
767
455
|
"codex-cli": "CodexCLI",
|
|
768
456
|
codex: "CodexCLI"
|
|
769
457
|
};
|
|
770
|
-
function
|
|
458
|
+
function parseClientFilter(value) {
|
|
771
459
|
if (value === void 0 || value.trim().length === 0) {
|
|
772
460
|
return null;
|
|
773
461
|
}
|
|
774
462
|
const clients = /* @__PURE__ */ new Set();
|
|
775
463
|
for (const rawClient of value.split(",")) {
|
|
776
464
|
const alias = rawClient.trim().toLowerCase();
|
|
777
|
-
const clientKind =
|
|
465
|
+
const clientKind = CLIENT_ALIASES[alias];
|
|
778
466
|
if (clientKind === void 0) {
|
|
779
467
|
throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
|
|
780
468
|
}
|
|
@@ -783,8 +471,8 @@ function parseClientFilter2(value) {
|
|
|
783
471
|
return clients;
|
|
784
472
|
}
|
|
785
473
|
async function loadFabricConfig(workspaceRoot) {
|
|
786
|
-
const configPath =
|
|
787
|
-
if (!
|
|
474
|
+
const configPath = resolve5(workspaceRoot, "fabric.config.json");
|
|
475
|
+
if (!existsSync6(configPath)) {
|
|
788
476
|
return {};
|
|
789
477
|
}
|
|
790
478
|
const parsed = JSON.parse(await readFile3(configPath, "utf8"));
|
|
@@ -795,21 +483,21 @@ async function loadFabricConfig(workspaceRoot) {
|
|
|
795
483
|
}
|
|
796
484
|
function resolveServerPath(override) {
|
|
797
485
|
if (override) return override;
|
|
798
|
-
if (process.env.FAB_SERVER_PATH) return
|
|
799
|
-
return
|
|
486
|
+
if (process.env.FAB_SERVER_PATH) return resolve5(process.env.FAB_SERVER_PATH);
|
|
487
|
+
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
800
488
|
}
|
|
801
|
-
function
|
|
489
|
+
function writeStderr(message) {
|
|
802
490
|
process.stderr.write(`${message}
|
|
803
491
|
`);
|
|
804
492
|
}
|
|
805
|
-
var configCmd =
|
|
493
|
+
var configCmd = defineCommand2({
|
|
806
494
|
meta: {
|
|
807
495
|
name: "config",
|
|
808
496
|
description: t("cli.config.description")
|
|
809
497
|
},
|
|
810
498
|
subCommands: {
|
|
811
499
|
hooks: hooksCommand,
|
|
812
|
-
install:
|
|
500
|
+
install: defineCommand2({
|
|
813
501
|
meta: {
|
|
814
502
|
name: "install",
|
|
815
503
|
description: t("cli.config.install.description")
|
|
@@ -826,26 +514,26 @@ var configCmd = defineCommand3({
|
|
|
826
514
|
}
|
|
827
515
|
},
|
|
828
516
|
async run({ args }) {
|
|
829
|
-
const selectedClients =
|
|
517
|
+
const selectedClients = parseClientFilter(args.clients);
|
|
830
518
|
const result = await installMcpClients(process.cwd(), {
|
|
831
519
|
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
832
520
|
dryRun: args["dry-run"]
|
|
833
521
|
});
|
|
834
522
|
if (result.details.length === 0) {
|
|
835
|
-
|
|
523
|
+
writeStderr(t("cli.config.install.no-configs"));
|
|
836
524
|
return;
|
|
837
525
|
}
|
|
838
526
|
for (const detail of result.details) {
|
|
839
527
|
if (detail.action === "skipped") {
|
|
840
|
-
|
|
528
|
+
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
841
529
|
continue;
|
|
842
530
|
}
|
|
843
531
|
if (detail.action === "dry-run" && detail.path !== null) {
|
|
844
|
-
|
|
532
|
+
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
845
533
|
continue;
|
|
846
534
|
}
|
|
847
535
|
if (detail.path !== null) {
|
|
848
|
-
|
|
536
|
+
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
849
537
|
}
|
|
850
538
|
}
|
|
851
539
|
}
|
|
@@ -853,7 +541,7 @@ var configCmd = defineCommand3({
|
|
|
853
541
|
}
|
|
854
542
|
});
|
|
855
543
|
async function installMcpClients(target, options = {}) {
|
|
856
|
-
const workspaceRoot =
|
|
544
|
+
const workspaceRoot = resolve5(target);
|
|
857
545
|
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
858
546
|
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
859
547
|
const serverPath = resolveServerPath(options.localServerPath);
|
|
@@ -884,9 +572,9 @@ async function installMcpClients(target, options = {}) {
|
|
|
884
572
|
|
|
885
573
|
// src/scanner/forensic.ts
|
|
886
574
|
import { execFileSync } from "child_process";
|
|
887
|
-
import { existsSync as
|
|
575
|
+
import { existsSync as existsSync7, readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
888
576
|
import { createRequire } from "module";
|
|
889
|
-
import { basename, extname, isAbsolute as
|
|
577
|
+
import { basename, extname, isAbsolute as isAbsolute2, join as join5, posix, relative, resolve as resolve6, sep } from "path";
|
|
890
578
|
import {
|
|
891
579
|
forensicReportSchema
|
|
892
580
|
} from "@fenglimg/fabric-shared";
|
|
@@ -972,7 +660,7 @@ var parserInitPromise = null;
|
|
|
972
660
|
var languagePromiseByKind = {};
|
|
973
661
|
var parserBundlePromiseByKind = {};
|
|
974
662
|
async function buildForensicReport(targetInput) {
|
|
975
|
-
const target =
|
|
663
|
+
const target = normalizeTarget2(targetInput);
|
|
976
664
|
const framework = detectFramework(target);
|
|
977
665
|
const topology = buildTopology(target);
|
|
978
666
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -1008,8 +696,8 @@ async function buildForensicReport(targetInput) {
|
|
|
1008
696
|
}
|
|
1009
697
|
return validation.data;
|
|
1010
698
|
}
|
|
1011
|
-
function
|
|
1012
|
-
return
|
|
699
|
+
function normalizeTarget2(targetInput) {
|
|
700
|
+
return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
|
|
1013
701
|
}
|
|
1014
702
|
function buildTopology(root) {
|
|
1015
703
|
assertExistingDirectory2(root);
|
|
@@ -1025,7 +713,7 @@ function buildTopology(root) {
|
|
|
1025
713
|
continue;
|
|
1026
714
|
}
|
|
1027
715
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1028
|
-
const absolutePath =
|
|
716
|
+
const absolutePath = join5(current, entry.name);
|
|
1029
717
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
1030
718
|
if (relativePath.length === 0) {
|
|
1031
719
|
continue;
|
|
@@ -1064,7 +752,7 @@ function buildTopology(root) {
|
|
|
1064
752
|
};
|
|
1065
753
|
}
|
|
1066
754
|
function assertExistingDirectory2(target) {
|
|
1067
|
-
if (!
|
|
755
|
+
if (!existsSync7(target) || !statSync2(target).isDirectory()) {
|
|
1068
756
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
1069
757
|
}
|
|
1070
758
|
}
|
|
@@ -1113,7 +801,7 @@ function getEntryPointReason(relativePath) {
|
|
|
1113
801
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
1114
802
|
const samples = [];
|
|
1115
803
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
1116
|
-
const absolutePath =
|
|
804
|
+
const absolutePath = join5(target, ...entryPoint.path.split("/"));
|
|
1117
805
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
1118
806
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
1119
807
|
frameworkKind,
|
|
@@ -1133,7 +821,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
1133
821
|
}
|
|
1134
822
|
function readFirstLines(path, lineLimit) {
|
|
1135
823
|
try {
|
|
1136
|
-
const lines =
|
|
824
|
+
const lines = readFileSync(path, "utf8").split(/\r?\n/);
|
|
1137
825
|
if (lines.at(-1) === "") {
|
|
1138
826
|
lines.pop();
|
|
1139
827
|
}
|
|
@@ -1150,12 +838,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
1150
838
|
}
|
|
1151
839
|
}
|
|
1152
840
|
function readPackageDependencies(target) {
|
|
1153
|
-
const packageJsonPath =
|
|
1154
|
-
if (!
|
|
841
|
+
const packageJsonPath = join5(target, "package.json");
|
|
842
|
+
if (!existsSync7(packageJsonPath)) {
|
|
1155
843
|
return /* @__PURE__ */ new Map();
|
|
1156
844
|
}
|
|
1157
845
|
try {
|
|
1158
|
-
const packageJson = JSON.parse(
|
|
846
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1159
847
|
return new Map([
|
|
1160
848
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
1161
849
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -1491,16 +1179,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
1491
1179
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1492
1180
|
}
|
|
1493
1181
|
function readReadmeInfo(target) {
|
|
1494
|
-
const readmePath =
|
|
1495
|
-
const hasContributing =
|
|
1496
|
-
if (!
|
|
1182
|
+
const readmePath = join5(target, "README.md");
|
|
1183
|
+
const hasContributing = existsSync7(join5(target, "CONTRIBUTING.md"));
|
|
1184
|
+
if (!existsSync7(readmePath)) {
|
|
1497
1185
|
return {
|
|
1498
1186
|
quality: "missing",
|
|
1499
1187
|
line_count: 0,
|
|
1500
1188
|
has_contributing: hasContributing
|
|
1501
1189
|
};
|
|
1502
1190
|
}
|
|
1503
|
-
const readme =
|
|
1191
|
+
const readme = readFileSync(readmePath, "utf8");
|
|
1504
1192
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
1505
1193
|
return {
|
|
1506
1194
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -1733,7 +1421,7 @@ function buildDomainAssertion(codeSamples) {
|
|
|
1733
1421
|
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
1734
1422
|
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
1735
1423
|
]),
|
|
1736
|
-
proposedRule: "Preserve domain-specific module names when
|
|
1424
|
+
proposedRule: "Preserve domain-specific module names when authoring knowledge entries that reference these modules."
|
|
1737
1425
|
});
|
|
1738
1426
|
}
|
|
1739
1427
|
function createAssertion(input) {
|
|
@@ -1978,10 +1666,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1978
1666
|
return recommendations;
|
|
1979
1667
|
}
|
|
1980
1668
|
function readProjectName(target) {
|
|
1981
|
-
const packageJsonPath =
|
|
1982
|
-
if (
|
|
1669
|
+
const packageJsonPath = join5(target, "package.json");
|
|
1670
|
+
if (existsSync7(packageJsonPath)) {
|
|
1983
1671
|
try {
|
|
1984
|
-
const packageJson = JSON.parse(
|
|
1672
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1985
1673
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1986
1674
|
return packageJson.name;
|
|
1987
1675
|
}
|
|
@@ -1992,7 +1680,7 @@ function readProjectName(target) {
|
|
|
1992
1680
|
return basename(target);
|
|
1993
1681
|
}
|
|
1994
1682
|
function getCliVersion() {
|
|
1995
|
-
return true ? "
|
|
1683
|
+
return true ? "2.0.0-rc.1" : "unknown";
|
|
1996
1684
|
}
|
|
1997
1685
|
function sortRecord(record) {
|
|
1998
1686
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -2002,18 +1690,19 @@ function toPosixPath(path) {
|
|
|
2002
1690
|
}
|
|
2003
1691
|
|
|
2004
1692
|
// src/commands/init.ts
|
|
2005
|
-
var
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1693
|
+
var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
|
|
1694
|
+
|
|
1695
|
+
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
1696
|
+
|
|
1697
|
+
Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
|
|
1698
|
+
Run \`fabric doctor\` to verify state.
|
|
1699
|
+
|
|
1700
|
+
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1701
|
+
`;
|
|
1702
|
+
var LOCAL_FABRIC_SERVER_PATH = join6("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
2014
1703
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
2015
1704
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
2016
|
-
var initCommand =
|
|
1705
|
+
var initCommand = defineCommand3({
|
|
2017
1706
|
meta: {
|
|
2018
1707
|
name: "init",
|
|
2019
1708
|
description: t("cli.init.description")
|
|
@@ -2095,13 +1784,13 @@ async function runInitCommand(args) {
|
|
|
2095
1784
|
logger(step);
|
|
2096
1785
|
}
|
|
2097
1786
|
if (intent.options.planOnly) {
|
|
2098
|
-
|
|
1787
|
+
writeStderr2(t("cli.init.compat.plan"));
|
|
2099
1788
|
}
|
|
2100
1789
|
if (args.interactive === false) {
|
|
2101
|
-
|
|
1790
|
+
writeStderr2(t("cli.init.compat.interactive"));
|
|
2102
1791
|
}
|
|
2103
1792
|
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
2104
|
-
|
|
1793
|
+
writeStderr2(t("cli.init.compat.legacy-stage-flags"));
|
|
2105
1794
|
}
|
|
2106
1795
|
const supports = detectClientSupports(intent.target);
|
|
2107
1796
|
const basePlan = await buildInitExecutionPlan({
|
|
@@ -2120,7 +1809,7 @@ async function runInitCommand(args) {
|
|
|
2120
1809
|
return executeInitExecutionPlan(plan);
|
|
2121
1810
|
}
|
|
2122
1811
|
function resolveInitCliIntent(args, targetInput) {
|
|
2123
|
-
const target =
|
|
1812
|
+
const target = normalizeTarget3(targetInput);
|
|
2124
1813
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
2125
1814
|
const claudeMcpScope = resolveClaudeMcpScope(args.scope);
|
|
2126
1815
|
const terminalInteractive = isInteractiveInit();
|
|
@@ -2150,7 +1839,7 @@ function resolveClaudeMcpScope(raw) {
|
|
|
2150
1839
|
if (raw === "user") {
|
|
2151
1840
|
return "user";
|
|
2152
1841
|
}
|
|
2153
|
-
|
|
1842
|
+
writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
|
|
2154
1843
|
return "project";
|
|
2155
1844
|
}
|
|
2156
1845
|
async function buildInitExecutionPlan(input) {
|
|
@@ -2190,10 +1879,10 @@ async function buildInitExecutionPlan(input) {
|
|
|
2190
1879
|
}
|
|
2191
1880
|
async function executeInitExecutionPlan(plan) {
|
|
2192
1881
|
if (plan.options.force) {
|
|
2193
|
-
|
|
1882
|
+
writeStderr2(t("cli.init.force.warning", { path: plan.target }));
|
|
2194
1883
|
}
|
|
2195
1884
|
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
2196
|
-
|
|
1885
|
+
writeStderr2(formatInitModeBanner(plan.options));
|
|
2197
1886
|
}
|
|
2198
1887
|
if (plan.interactive) {
|
|
2199
1888
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
@@ -2238,98 +1927,77 @@ async function executeInitExecutionPlan(plan) {
|
|
|
2238
1927
|
finalSupports
|
|
2239
1928
|
};
|
|
2240
1929
|
}
|
|
1930
|
+
var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
|
|
1931
|
+
function resolvePersonalFabricRoot() {
|
|
1932
|
+
return process.env.FABRIC_HOME ?? homedir5();
|
|
1933
|
+
}
|
|
2241
1934
|
async function buildInitFabricPlan(target, options) {
|
|
2242
1935
|
assertExistingDirectory3(target);
|
|
2243
|
-
const fabricDir =
|
|
2244
|
-
const
|
|
2245
|
-
const
|
|
2246
|
-
const
|
|
2247
|
-
const
|
|
2248
|
-
const
|
|
2249
|
-
const
|
|
2250
|
-
const
|
|
2251
|
-
const codexSessionStartHookPath = join8(target, ".codex", "hooks", "fabric-session-start.cjs");
|
|
2252
|
-
const codexStopHookPath = join8(target, ".codex", "hooks", "fabric-stop-reminder.cjs");
|
|
2253
|
-
const codexHooksConfigPath = join8(target, ".codex", "hooks.json");
|
|
2254
|
-
const claudeHookPath = join8(target, ".claude", "hooks", "fabric-init-reminder.cjs");
|
|
2255
|
-
const claudeSettingsPath = join8(target, ".claude", "settings.json");
|
|
2256
|
-
const metaPath = join8(fabricDir, "agents.meta.json");
|
|
1936
|
+
const fabricDir = join6(target, ".fabric");
|
|
1937
|
+
const agentsMdPath = join6(target, "AGENTS.md");
|
|
1938
|
+
const agentsMdAction = existsSync8(agentsMdPath) ? "preserved" : "created";
|
|
1939
|
+
const knowledgeDir = join6(fabricDir, "knowledge");
|
|
1940
|
+
const personalKnowledgeDir = join6(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1941
|
+
const forensicPath = join6(fabricDir, "forensic.json");
|
|
1942
|
+
const eventsPath = join6(fabricDir, "events.jsonl");
|
|
1943
|
+
const metaPath = join6(fabricDir, "agents.meta.json");
|
|
2257
1944
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
2258
|
-
const
|
|
1945
|
+
const knowledgeDirAction = existsSync8(knowledgeDir) ? "overwritten" : "created";
|
|
2259
1946
|
const metaAction = planFreshPath(metaPath, options);
|
|
2260
|
-
const taxonomyAction = planFreshPath(taxonomyPath, options);
|
|
2261
1947
|
const eventsAction = planFreshPath(eventsPath, options);
|
|
2262
1948
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
2263
1949
|
const forensicReport = await buildForensicReport(target);
|
|
2264
|
-
const
|
|
2265
|
-
const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
|
|
2266
|
-
const bootstrapHash = sha256(bootstrapContent);
|
|
2267
|
-
const meta = createInitialMeta(bootstrapHash);
|
|
1950
|
+
const meta = createInitialMeta();
|
|
2268
1951
|
return {
|
|
2269
1952
|
target,
|
|
2270
1953
|
options,
|
|
2271
1954
|
fabricDir,
|
|
2272
1955
|
replaceFabricDir,
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
1956
|
+
agentsMdPath,
|
|
1957
|
+
agentsMdAction,
|
|
1958
|
+
knowledgeDir,
|
|
1959
|
+
knowledgeDirAction,
|
|
1960
|
+
personalKnowledgeDir,
|
|
2276
1961
|
metaPath,
|
|
2277
1962
|
metaAction,
|
|
2278
1963
|
meta,
|
|
2279
|
-
taxonomyPath,
|
|
2280
|
-
taxonomyAction,
|
|
2281
|
-
taxonomyContent,
|
|
2282
|
-
rulesDir,
|
|
2283
1964
|
eventsPath,
|
|
2284
1965
|
eventsAction,
|
|
2285
1966
|
forensicPath,
|
|
2286
1967
|
forensicAction,
|
|
2287
|
-
forensicReport
|
|
2288
|
-
claudeSkill: buildOptionalTemplateWritePlan(claudeSkillPath, findTemplatePath3(CLAUDE_INIT_SKILL_TEMPLATE), options),
|
|
2289
|
-
codexSkill: buildOptionalTemplateWritePlan(codexSkillPath, findTemplatePath3(CODEX_INIT_SKILL_TEMPLATE), options),
|
|
2290
|
-
codexSessionStartHook: buildOptionalTemplateWritePlan(
|
|
2291
|
-
codexSessionStartHookPath,
|
|
2292
|
-
findTemplatePath3(CODEX_SESSION_START_HOOK_TEMPLATE),
|
|
2293
|
-
options,
|
|
2294
|
-
true
|
|
2295
|
-
),
|
|
2296
|
-
codexStopHook: buildOptionalTemplateWritePlan(
|
|
2297
|
-
codexStopHookPath,
|
|
2298
|
-
findTemplatePath3(CODEX_STOP_HOOK_TEMPLATE),
|
|
2299
|
-
options,
|
|
2300
|
-
true
|
|
2301
|
-
),
|
|
2302
|
-
codexHooksConfig: buildCodexHooksConfigPlan(codexHooksConfigPath, options),
|
|
2303
|
-
claudeHook: buildOptionalTemplateWritePlan(
|
|
2304
|
-
claudeHookPath,
|
|
2305
|
-
findTemplatePath3(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
2306
|
-
options,
|
|
2307
|
-
true
|
|
2308
|
-
),
|
|
2309
|
-
claudeSettings: buildClaudeSettingsWritePlan(claudeSettingsPath, options)
|
|
1968
|
+
forensicReport
|
|
2310
1969
|
};
|
|
2311
1970
|
}
|
|
2312
1971
|
async function executeInitFabricPlan(plan) {
|
|
2313
1972
|
const isReapply = plan.options?.reapply === true;
|
|
2314
|
-
const existingRules = isReapply && existsSync9(plan.rulesDir) ? readdirSync2(plan.rulesDir).filter((f) => f.endsWith(".md")) : [];
|
|
2315
|
-
const preserveMeta = isReapply && existingRules.length > 0;
|
|
2316
1973
|
if (plan.replaceFabricDir) {
|
|
2317
1974
|
rmSync(plan.fabricDir, { force: true });
|
|
2318
1975
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
1976
|
+
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1977
|
+
if (plan.agentsMdAction === "created" && !existsSync8(plan.agentsMdPath)) {
|
|
1978
|
+
await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1979
|
+
}
|
|
1980
|
+
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1981
|
+
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1982
|
+
const teamSubDir = join6(plan.knowledgeDir, sub);
|
|
1983
|
+
mkdirSync(teamSubDir, { recursive: true });
|
|
1984
|
+
const teamGitkeep = join6(teamSubDir, ".gitkeep");
|
|
1985
|
+
if (!existsSync8(teamGitkeep)) {
|
|
1986
|
+
writeFileSync(teamGitkeep, "", "utf8");
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
try {
|
|
1990
|
+
mkdirSync(plan.personalKnowledgeDir, { recursive: true });
|
|
1991
|
+
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1992
|
+
mkdirSync(join6(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1993
|
+
}
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1997
|
+
await atomicWriteJson2(plan.metaPath, plan.meta);
|
|
2330
1998
|
if (isReapply) {
|
|
2331
|
-
if (!
|
|
2332
|
-
|
|
1999
|
+
if (!existsSync8(plan.eventsPath)) {
|
|
2000
|
+
mkdirSync(dirname3(plan.eventsPath), { recursive: true });
|
|
2333
2001
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2334
2002
|
}
|
|
2335
2003
|
} else {
|
|
@@ -2337,46 +2005,33 @@ async function executeInitFabricPlan(plan) {
|
|
|
2337
2005
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2338
2006
|
}
|
|
2339
2007
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
2340
|
-
await
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2008
|
+
await atomicWriteJson2(plan.forensicPath, plan.forensicReport);
|
|
2009
|
+
if (!plan.options?.reapply) {
|
|
2010
|
+
try {
|
|
2011
|
+
await runInitScan(plan.target, { source: "init" });
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
writeStderr2(
|
|
2014
|
+
`[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2348
2018
|
if (isReapply) {
|
|
2349
2019
|
appendReapplyLedgerEvent(plan.eventsPath, {
|
|
2350
|
-
preserved_ledger: true
|
|
2351
|
-
preserved_meta: preserveMeta,
|
|
2352
|
-
rules_count: existingRules.length
|
|
2020
|
+
preserved_ledger: true
|
|
2353
2021
|
});
|
|
2354
2022
|
}
|
|
2355
2023
|
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
2024
|
+
agentsMdPath: plan.agentsMdPath,
|
|
2025
|
+
agentsMdAction: plan.agentsMdAction,
|
|
2026
|
+
knowledgeDir: plan.knowledgeDir,
|
|
2027
|
+
knowledgeDirAction: plan.knowledgeDirAction,
|
|
2028
|
+
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2358
2029
|
metaPath: plan.metaPath,
|
|
2359
2030
|
metaAction: plan.metaAction,
|
|
2360
|
-
taxonomyPath: plan.taxonomyPath,
|
|
2361
|
-
taxonomyAction: plan.taxonomyAction,
|
|
2362
2031
|
eventsPath: plan.eventsPath,
|
|
2363
2032
|
eventsAction: plan.eventsAction,
|
|
2364
2033
|
forensicPath: plan.forensicPath,
|
|
2365
|
-
forensicAction: plan.forensicAction
|
|
2366
|
-
claudeSkillPath: plan.claudeSkill.path,
|
|
2367
|
-
claudeSkillAction: plan.claudeSkill.action,
|
|
2368
|
-
codexSkillPath: plan.codexSkill.path,
|
|
2369
|
-
codexSkillAction: plan.codexSkill.action,
|
|
2370
|
-
codexSessionStartHookPath: plan.codexSessionStartHook.path,
|
|
2371
|
-
codexSessionStartHookAction: plan.codexSessionStartHook.action,
|
|
2372
|
-
codexStopHookPath: plan.codexStopHook.path,
|
|
2373
|
-
codexStopHookAction: plan.codexStopHook.action,
|
|
2374
|
-
codexHooksConfigPath: plan.codexHooksConfig.path,
|
|
2375
|
-
codexHooksConfigAction: plan.codexHooksConfig.action,
|
|
2376
|
-
claudeHookPath: plan.claudeHook.path,
|
|
2377
|
-
claudeHookAction: plan.claudeHook.action,
|
|
2378
|
-
claudeSettingsPath: plan.claudeSettings.path,
|
|
2379
|
-
claudeSettingsAction: plan.claudeSettings.action
|
|
2034
|
+
forensicAction: plan.forensicAction
|
|
2380
2035
|
};
|
|
2381
2036
|
}
|
|
2382
2037
|
async function initFabric(target, options) {
|
|
@@ -2421,22 +2076,11 @@ function exhaustiveInitStagePlan(value) {
|
|
|
2421
2076
|
throw new Error(`Unsupported init stage plan: ${JSON.stringify(value)}`);
|
|
2422
2077
|
}
|
|
2423
2078
|
function printInitScaffoldResult(created) {
|
|
2424
|
-
console.log(
|
|
2079
|
+
console.log(formatAgentsMdAction(created.agentsMdPath, created.agentsMdAction));
|
|
2080
|
+
console.log(formatInitPathAction(created.knowledgeDir, created.knowledgeDirAction));
|
|
2425
2081
|
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
2426
|
-
console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
|
|
2427
2082
|
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
2428
2083
|
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
2429
|
-
writeStderr3(formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction));
|
|
2430
|
-
writeStderr3(formatOptionalInitPathAction(created.codexSkillPath, created.codexSkillAction));
|
|
2431
|
-
writeStderr3(
|
|
2432
|
-
formatOptionalInitPathAction(created.codexSessionStartHookPath, created.codexSessionStartHookAction)
|
|
2433
|
-
);
|
|
2434
|
-
writeStderr3(
|
|
2435
|
-
formatOptionalInitPathAction(created.codexStopHookPath, created.codexStopHookAction)
|
|
2436
|
-
);
|
|
2437
|
-
writeStderr3(formatCodexHooksAction(created.codexHooksConfigPath, created.codexHooksConfigAction));
|
|
2438
|
-
writeStderr3(formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction));
|
|
2439
|
-
writeStderr3(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
2440
2084
|
}
|
|
2441
2085
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
2442
2086
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
@@ -2470,30 +2114,17 @@ function printInitPlanPreview(plan) {
|
|
|
2470
2114
|
}
|
|
2471
2115
|
function buildPlanOnlyScaffoldResult(plan) {
|
|
2472
2116
|
return {
|
|
2473
|
-
|
|
2474
|
-
|
|
2117
|
+
agentsMdPath: plan.agentsMdPath,
|
|
2118
|
+
agentsMdAction: plan.agentsMdAction,
|
|
2119
|
+
knowledgeDir: plan.knowledgeDir,
|
|
2120
|
+
knowledgeDirAction: plan.knowledgeDirAction,
|
|
2121
|
+
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2475
2122
|
metaPath: plan.metaPath,
|
|
2476
2123
|
metaAction: plan.metaAction,
|
|
2477
|
-
taxonomyPath: plan.taxonomyPath,
|
|
2478
|
-
taxonomyAction: plan.taxonomyAction,
|
|
2479
2124
|
eventsPath: plan.eventsPath,
|
|
2480
2125
|
eventsAction: plan.eventsAction,
|
|
2481
2126
|
forensicPath: plan.forensicPath,
|
|
2482
|
-
forensicAction: plan.forensicAction
|
|
2483
|
-
claudeSkillPath: plan.claudeSkill.path,
|
|
2484
|
-
claudeSkillAction: plan.claudeSkill.action,
|
|
2485
|
-
codexSkillPath: plan.codexSkill.path,
|
|
2486
|
-
codexSkillAction: plan.codexSkill.action,
|
|
2487
|
-
codexSessionStartHookPath: plan.codexSessionStartHook.path,
|
|
2488
|
-
codexSessionStartHookAction: plan.codexSessionStartHook.action,
|
|
2489
|
-
codexStopHookPath: plan.codexStopHook.path,
|
|
2490
|
-
codexStopHookAction: plan.codexStopHook.action,
|
|
2491
|
-
codexHooksConfigPath: plan.codexHooksConfig.path,
|
|
2492
|
-
codexHooksConfigAction: plan.codexHooksConfig.action,
|
|
2493
|
-
claudeHookPath: plan.claudeHook.path,
|
|
2494
|
-
claudeHookAction: plan.claudeHook.action,
|
|
2495
|
-
claudeSettingsPath: plan.claudeSettings.path,
|
|
2496
|
-
claudeSettingsAction: plan.claudeSettings.action
|
|
2127
|
+
forensicAction: plan.forensicAction
|
|
2497
2128
|
};
|
|
2498
2129
|
}
|
|
2499
2130
|
async function executeInitStagePlan(plan, stageName) {
|
|
@@ -2508,25 +2139,18 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2508
2139
|
try {
|
|
2509
2140
|
switch (stage.name) {
|
|
2510
2141
|
case "bootstrap": {
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
|
|
2514
|
-
return { name: "bootstrap", disposition: "skipped" };
|
|
2515
|
-
}
|
|
2516
|
-
console.log(
|
|
2517
|
-
formatInitStageResult("bootstrap", "completed", result.installed.length, result.skipped.length)
|
|
2518
|
-
);
|
|
2519
|
-
return { name: "bootstrap", disposition: "ran" };
|
|
2142
|
+
console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
|
|
2143
|
+
return { name: "bootstrap", disposition: "skipped" };
|
|
2520
2144
|
}
|
|
2521
2145
|
case "mcp": {
|
|
2522
2146
|
if (stage.installMode === "local") {
|
|
2523
2147
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
2524
|
-
|
|
2525
|
-
|
|
2148
|
+
writeStderr2(t("cli.init.mcp.install.local"));
|
|
2149
|
+
writeStderr2(t("cli.init.mcp.local.installing", { manager }));
|
|
2526
2150
|
installLocalFabricServer(plan.target, manager);
|
|
2527
|
-
|
|
2151
|
+
writeStderr2(t("cli.init.mcp.local.installed"));
|
|
2528
2152
|
} else {
|
|
2529
|
-
|
|
2153
|
+
writeStderr2(t("cli.init.mcp.install.global"));
|
|
2530
2154
|
}
|
|
2531
2155
|
const result = await installMcpClients(plan.target, {
|
|
2532
2156
|
force: plan.options.force,
|
|
@@ -2549,12 +2173,12 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2549
2173
|
return exhaustiveInitStagePlan(stage);
|
|
2550
2174
|
}
|
|
2551
2175
|
} catch (error) {
|
|
2552
|
-
|
|
2176
|
+
writeStderr2(formatInitStageFailure(stageName, error));
|
|
2553
2177
|
return { name: stageName, disposition: "failed" };
|
|
2554
2178
|
}
|
|
2555
2179
|
}
|
|
2556
2180
|
function shouldReplaceWritableDirectory(path, options) {
|
|
2557
|
-
if (!
|
|
2181
|
+
if (!existsSync8(path)) {
|
|
2558
2182
|
return false;
|
|
2559
2183
|
}
|
|
2560
2184
|
if (statSync3(path).isDirectory()) {
|
|
@@ -2566,7 +2190,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
2566
2190
|
return true;
|
|
2567
2191
|
}
|
|
2568
2192
|
function planFreshPath(path, options) {
|
|
2569
|
-
if (!
|
|
2193
|
+
if (!existsSync8(path)) {
|
|
2570
2194
|
return "created";
|
|
2571
2195
|
}
|
|
2572
2196
|
if (!options?.force) {
|
|
@@ -2575,131 +2199,11 @@ function planFreshPath(path, options) {
|
|
|
2575
2199
|
return "overwritten";
|
|
2576
2200
|
}
|
|
2577
2201
|
function preparePlannedPath(path, action) {
|
|
2578
|
-
|
|
2579
|
-
if (action === "overwritten" &&
|
|
2202
|
+
mkdirSync(dirname3(path), { recursive: true });
|
|
2203
|
+
if (action === "overwritten" && existsSync8(path)) {
|
|
2580
2204
|
rmSync(path, { recursive: true, force: true });
|
|
2581
2205
|
}
|
|
2582
2206
|
}
|
|
2583
|
-
function buildOptionalTemplateWritePlan(path, templatePath, options, executable = false) {
|
|
2584
|
-
const existed = existsSync9(path);
|
|
2585
|
-
if (existed && !options?.force) {
|
|
2586
|
-
return { path, action: "skipped", templatePath, executable };
|
|
2587
|
-
}
|
|
2588
|
-
return {
|
|
2589
|
-
path,
|
|
2590
|
-
action: existed ? "overwritten" : "created",
|
|
2591
|
-
templatePath,
|
|
2592
|
-
executable
|
|
2593
|
-
};
|
|
2594
|
-
}
|
|
2595
|
-
function applyOptionalTemplateWritePlan(plan) {
|
|
2596
|
-
if (plan.action === "skipped") {
|
|
2597
|
-
return;
|
|
2598
|
-
}
|
|
2599
|
-
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
2600
|
-
copyFileSync(plan.templatePath, plan.path);
|
|
2601
|
-
if (plan.executable) {
|
|
2602
|
-
chmodSync2(plan.path, 493);
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
function buildCodexHooksConfigValue() {
|
|
2606
|
-
return {
|
|
2607
|
-
hooks: {
|
|
2608
|
-
SessionStart: [
|
|
2609
|
-
{
|
|
2610
|
-
matcher: "*",
|
|
2611
|
-
hooks: [{ type: "command", command: CODEX_SESSION_START_COMMAND }]
|
|
2612
|
-
}
|
|
2613
|
-
],
|
|
2614
|
-
Stop: [
|
|
2615
|
-
{
|
|
2616
|
-
matcher: "*",
|
|
2617
|
-
hooks: [{ type: "command", command: CODEX_STOP_COMMAND }]
|
|
2618
|
-
}
|
|
2619
|
-
]
|
|
2620
|
-
}
|
|
2621
|
-
};
|
|
2622
|
-
}
|
|
2623
|
-
function buildCodexHooksConfigPlan(configPath, options) {
|
|
2624
|
-
const action = !existsSync9(configPath) ? "created" : options?.force ? "overwritten" : "skipped";
|
|
2625
|
-
return {
|
|
2626
|
-
path: configPath,
|
|
2627
|
-
action,
|
|
2628
|
-
value: buildCodexHooksConfigValue()
|
|
2629
|
-
};
|
|
2630
|
-
}
|
|
2631
|
-
async function applyJsonWritePlan(plan) {
|
|
2632
|
-
if (plan.action === "skipped") {
|
|
2633
|
-
return;
|
|
2634
|
-
}
|
|
2635
|
-
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
2636
|
-
await atomicWriteJson3(plan.path, plan.value);
|
|
2637
|
-
}
|
|
2638
|
-
function buildClaudeSettingsWritePlan(settingsPath, options) {
|
|
2639
|
-
let settings;
|
|
2640
|
-
let action = "updated";
|
|
2641
|
-
if (!existsSync9(settingsPath)) {
|
|
2642
|
-
settings = {};
|
|
2643
|
-
action = "created";
|
|
2644
|
-
} else {
|
|
2645
|
-
try {
|
|
2646
|
-
const parsed = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
2647
|
-
if (!isRecord(parsed)) {
|
|
2648
|
-
writeStderr3(t("cli.init.claude-settings.invalid-object", { label: skippedLabel(), path: settingsPath }));
|
|
2649
|
-
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
2650
|
-
}
|
|
2651
|
-
settings = parsed;
|
|
2652
|
-
} catch (error) {
|
|
2653
|
-
const reason = error instanceof Error ? error.message : "unknown parse error";
|
|
2654
|
-
writeStderr3(t("cli.init.claude-settings.invalid-json", { label: skippedLabel(), path: settingsPath, reason }));
|
|
2655
|
-
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
if (settings.hooks !== void 0 && !isRecord(settings.hooks)) {
|
|
2659
|
-
writeStderr3(t("cli.init.claude-settings.invalid-hooks", { label: skippedLabel(), path: settingsPath }));
|
|
2660
|
-
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
2661
|
-
}
|
|
2662
|
-
const hooks = settings.hooks ?? {};
|
|
2663
|
-
const stopHooksValue = hooks.Stop;
|
|
2664
|
-
if (stopHooksValue !== void 0 && !Array.isArray(stopHooksValue)) {
|
|
2665
|
-
writeStderr3(t("cli.init.claude-settings.invalid-stop-array", { label: skippedLabel(), path: settingsPath }));
|
|
2666
|
-
return { path: settingsPath, action: "skipped-invalid", value: null };
|
|
2667
|
-
}
|
|
2668
|
-
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
2669
|
-
const hasExistingFabricHook = hasClaudeInitReminderHook(stopHooks);
|
|
2670
|
-
if (hasExistingFabricHook && !options?.force) {
|
|
2671
|
-
return { path: settingsPath, action: "skipped", value: null };
|
|
2672
|
-
}
|
|
2673
|
-
const nextStopHooks = hasExistingFabricHook && options?.force ? removeClaudeInitReminderHook(stopHooks) : [...stopHooks];
|
|
2674
|
-
nextStopHooks.push({
|
|
2675
|
-
matcher: "*",
|
|
2676
|
-
hooks: [
|
|
2677
|
-
{
|
|
2678
|
-
type: "command",
|
|
2679
|
-
command: CLAUDE_INIT_REMINDER_COMMAND
|
|
2680
|
-
}
|
|
2681
|
-
]
|
|
2682
|
-
});
|
|
2683
|
-
const nextSettings = {
|
|
2684
|
-
...settings,
|
|
2685
|
-
hooks: {
|
|
2686
|
-
...hooks,
|
|
2687
|
-
Stop: nextStopHooks
|
|
2688
|
-
}
|
|
2689
|
-
};
|
|
2690
|
-
return {
|
|
2691
|
-
path: settingsPath,
|
|
2692
|
-
action: hasExistingFabricHook && options?.force ? "overwritten" : action,
|
|
2693
|
-
value: nextSettings
|
|
2694
|
-
};
|
|
2695
|
-
}
|
|
2696
|
-
async function applyClaudeSettingsWritePlan(plan) {
|
|
2697
|
-
if (plan.value === null) {
|
|
2698
|
-
return;
|
|
2699
|
-
}
|
|
2700
|
-
mkdirSync3(dirname5(plan.path), { recursive: true });
|
|
2701
|
-
await atomicWriteJson3(plan.path, plan.value);
|
|
2702
|
-
}
|
|
2703
2207
|
function createDefaultInitWizardAdapter() {
|
|
2704
2208
|
return {
|
|
2705
2209
|
async run(context) {
|
|
@@ -2871,23 +2375,23 @@ function formatInitModeBadge(options) {
|
|
|
2871
2375
|
}
|
|
2872
2376
|
return t("cli.init.mode.badge.default");
|
|
2873
2377
|
}
|
|
2874
|
-
function
|
|
2875
|
-
return
|
|
2378
|
+
function normalizeTarget3(targetInput) {
|
|
2379
|
+
return isAbsolute3(targetInput) ? targetInput : resolve7(process.cwd(), targetInput);
|
|
2876
2380
|
}
|
|
2877
2381
|
function assertExistingDirectory3(target) {
|
|
2878
|
-
if (!
|
|
2382
|
+
if (!existsSync8(target) || !statSync3(target).isDirectory()) {
|
|
2879
2383
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2880
2384
|
}
|
|
2881
2385
|
}
|
|
2882
2386
|
function detectPackageManager(cwd) {
|
|
2883
|
-
const workspaceRoot =
|
|
2884
|
-
if (
|
|
2387
|
+
const workspaceRoot = resolve7(cwd);
|
|
2388
|
+
if (existsSync8(join6(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2885
2389
|
return "pnpm";
|
|
2886
2390
|
}
|
|
2887
|
-
if (
|
|
2391
|
+
if (existsSync8(join6(workspaceRoot, "yarn.lock"))) {
|
|
2888
2392
|
return "yarn";
|
|
2889
2393
|
}
|
|
2890
|
-
if (
|
|
2394
|
+
if (existsSync8(join6(workspaceRoot, "package-lock.json"))) {
|
|
2891
2395
|
return "npm";
|
|
2892
2396
|
}
|
|
2893
2397
|
return "npm";
|
|
@@ -2896,7 +2400,7 @@ function resolveMcpInstallMode(rawMode) {
|
|
|
2896
2400
|
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
2897
2401
|
return rawMode ?? "global";
|
|
2898
2402
|
}
|
|
2899
|
-
|
|
2403
|
+
writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
2900
2404
|
return "global";
|
|
2901
2405
|
}
|
|
2902
2406
|
function installLocalFabricServer(target, manager) {
|
|
@@ -2907,20 +2411,11 @@ function installLocalFabricServer(target, manager) {
|
|
|
2907
2411
|
shell: process.platform === "win32"
|
|
2908
2412
|
});
|
|
2909
2413
|
}
|
|
2910
|
-
function createInitialMeta(
|
|
2414
|
+
function createInitialMeta() {
|
|
2911
2415
|
return {
|
|
2912
|
-
revision: sha256
|
|
2913
|
-
nodes: {
|
|
2914
|
-
|
|
2915
|
-
file: ".fabric/bootstrap/README.md",
|
|
2916
|
-
scope_glob: "**",
|
|
2917
|
-
deps: [],
|
|
2918
|
-
priority: "high",
|
|
2919
|
-
layer: "L0",
|
|
2920
|
-
topology_type: "mirror",
|
|
2921
|
-
hash: agentsHash
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2416
|
+
revision: "sha256:initial",
|
|
2417
|
+
nodes: {},
|
|
2418
|
+
counters: defaultAgentsMetaCounters()
|
|
2924
2419
|
};
|
|
2925
2420
|
}
|
|
2926
2421
|
function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
@@ -2930,127 +2425,12 @@ function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
|
2930
2425
|
ts: Date.now(),
|
|
2931
2426
|
schema_version: 1,
|
|
2932
2427
|
event_type: "reapply_completed",
|
|
2933
|
-
preserved_ledger: payload.preserved_ledger
|
|
2934
|
-
preserved_meta: payload.preserved_meta,
|
|
2935
|
-
rules_count: payload.rules_count
|
|
2428
|
+
preserved_ledger: payload.preserved_ledger
|
|
2936
2429
|
};
|
|
2937
2430
|
const line = `${JSON.stringify(event)}
|
|
2938
2431
|
`;
|
|
2939
2432
|
appendFileSync(eventsPath, line, "utf8");
|
|
2940
2433
|
}
|
|
2941
|
-
function buildInitialTaxonomyMarkdown(forensicReport) {
|
|
2942
|
-
const frameworkInfo = forensicReport.framework;
|
|
2943
|
-
const framework = [frameworkInfo?.kind ?? "unknown", frameworkInfo?.subkind ?? ""].filter((value) => value.trim() !== "").join(" / ") || "unknown";
|
|
2944
|
-
const keyDirs = forensicReport.topology?.key_dirs?.slice(0, 8) ?? [];
|
|
2945
|
-
const candidateFiles = forensicReport.candidate_files?.slice(0, 8) ?? [];
|
|
2946
|
-
const generatedAt = forensicReport.generated_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2947
|
-
return `# Fabric Initial Taxonomy
|
|
2948
|
-
|
|
2949
|
-
**Date**: ${generatedAt}
|
|
2950
|
-
**Base Architecture**: L0/L1/L2 Tiered System
|
|
2951
|
-
**Detected Framework**: ${framework}
|
|
2952
|
-
|
|
2953
|
-
## Origin Logic
|
|
2954
|
-
|
|
2955
|
-
- **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
|
|
2956
|
-
- **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
|
|
2957
|
-
- **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
|
|
2958
|
-
|
|
2959
|
-
## Initial L1 Buckets
|
|
2960
|
-
|
|
2961
|
-
${formatInitialL1Buckets(keyDirs)}
|
|
2962
|
-
|
|
2963
|
-
## L2 Candidate Signals
|
|
2964
|
-
|
|
2965
|
-
${formatInitialL2Signals(candidateFiles)}
|
|
2966
|
-
|
|
2967
|
-
## Evolution Guide
|
|
2968
|
-
|
|
2969
|
-
- \u6D89\u53CA\u5168\u4ED3\u534F\u4F5C\u7A33\u5B9A\u6027\u7684\u89C4\u5219\u8FDB\u5165 L0\u3002
|
|
2970
|
-
- \u6D89\u53CA\u6280\u672F\u9886\u57DF\u3001\u6846\u67B6\u6A21\u5757\u6216\u529F\u80FD\u6A21\u5757\u7684\u89C4\u5219\u8FDB\u5165 L1\u3002
|
|
2971
|
-
- \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
|
|
2972
|
-
- \u51B2\u7A81\u65F6\u6267\u884C\u89E3\u91CA\u56FA\u5B9A\u4E3A L2 > L1 > L0\uFF1B\u540C\u5C42\u5185\u624D\u4F7F\u7528 priority \u6392\u5E8F\u3002
|
|
2973
|
-
`;
|
|
2974
|
-
}
|
|
2975
|
-
function formatInitialL1Buckets(keyDirs) {
|
|
2976
|
-
if (keyDirs.length === 0) {
|
|
2977
|
-
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";
|
|
2978
|
-
}
|
|
2979
|
-
return keyDirs.map((dir) => `- **L1-${sanitizeTaxonomyLabel(dir)}**: \u6302\u8F7D\u4F9D\u636E\u2014\u2014forensic topology detected \`${dir}\`.`).join("\n");
|
|
2980
|
-
}
|
|
2981
|
-
function formatInitialL2Signals(candidateFiles) {
|
|
2982
|
-
if (candidateFiles.length === 0) {
|
|
2983
|
-
return "- \u6682\u672A\u8BC6\u522B\u660E\u786E L2 \u5019\u9009\u6587\u4EF6\u3002";
|
|
2984
|
-
}
|
|
2985
|
-
return candidateFiles.map((entry) => `- \`${entry.path}\`: ${entry.family} \u2014 ${entry.rationale}`).join("\n");
|
|
2986
|
-
}
|
|
2987
|
-
function sanitizeTaxonomyLabel(value) {
|
|
2988
|
-
const sanitized = value.replaceAll("\\", "/").split("/").filter(Boolean).join("-").replace(/[^A-Za-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
2989
|
-
return sanitized === "" ? "General" : sanitized;
|
|
2990
|
-
}
|
|
2991
|
-
function ensureTrailingNewline2(value) {
|
|
2992
|
-
return value.endsWith("\n") ? value : `${value}
|
|
2993
|
-
`;
|
|
2994
|
-
}
|
|
2995
|
-
function findTemplatePath3(relativePath) {
|
|
2996
|
-
const currentModuleDir = dirname5(fileURLToPath4(import.meta.url));
|
|
2997
|
-
const candidates = [
|
|
2998
|
-
...templateCandidatesFrom3(process.cwd(), relativePath),
|
|
2999
|
-
...templateCandidatesFrom3(currentModuleDir, relativePath)
|
|
3000
|
-
];
|
|
3001
|
-
for (const candidate of candidates) {
|
|
3002
|
-
if (existsSync9(candidate)) {
|
|
3003
|
-
return candidate;
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
|
-
throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
|
|
3007
|
-
}
|
|
3008
|
-
function templateCandidatesFrom3(start, relativePath) {
|
|
3009
|
-
const candidates = [];
|
|
3010
|
-
let current = resolve9(start);
|
|
3011
|
-
while (true) {
|
|
3012
|
-
candidates.push(join8(current, ...relativePath.split("/")));
|
|
3013
|
-
const parent = dirname5(current);
|
|
3014
|
-
if (parent === current || parse3(current).root === current) {
|
|
3015
|
-
break;
|
|
3016
|
-
}
|
|
3017
|
-
current = parent;
|
|
3018
|
-
}
|
|
3019
|
-
return candidates.reverse();
|
|
3020
|
-
}
|
|
3021
|
-
function hasClaudeInitReminderHook(stopHooks) {
|
|
3022
|
-
return stopHooks.some((entry) => isClaudeInitReminderStopEntry(entry));
|
|
3023
|
-
}
|
|
3024
|
-
function removeClaudeInitReminderHook(stopHooks) {
|
|
3025
|
-
return stopHooks.filter((entry) => !isClaudeInitReminderStopEntry(entry));
|
|
3026
|
-
}
|
|
3027
|
-
function isClaudeInitReminderStopEntry(entry) {
|
|
3028
|
-
if (!isRecord(entry) || !Array.isArray(entry.hooks)) {
|
|
3029
|
-
return false;
|
|
3030
|
-
}
|
|
3031
|
-
return entry.hooks.some(
|
|
3032
|
-
(hook) => isRecord(hook) && hook.type === "command" && typeof hook.command === "string" && (hook.command.includes("fabric-init-reminder.cjs") || hook.command.includes("agents-md-init-reminder.cjs"))
|
|
3033
|
-
);
|
|
3034
|
-
}
|
|
3035
|
-
function isRecord(value) {
|
|
3036
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3037
|
-
}
|
|
3038
|
-
function formatClaudeSettingsAction(settingsPath, action) {
|
|
3039
|
-
switch (action) {
|
|
3040
|
-
case "created":
|
|
3041
|
-
return t("cli.init.claude-settings.created", { label: createdLabel(), path: settingsPath });
|
|
3042
|
-
case "updated":
|
|
3043
|
-
return t("cli.init.claude-settings.updated", { label: updatedLabel(), path: settingsPath });
|
|
3044
|
-
case "overwritten":
|
|
3045
|
-
return t("cli.init.claude-settings.updated", { label: overwrittenLabel(), path: settingsPath });
|
|
3046
|
-
case "skipped":
|
|
3047
|
-
return t("cli.init.claude-settings.skipped", { label: skippedLabel(), path: settingsPath });
|
|
3048
|
-
case "skipped-invalid":
|
|
3049
|
-
return t("cli.init.claude-settings.skipped-invalid", { label: skippedLabel(), path: settingsPath });
|
|
3050
|
-
default:
|
|
3051
|
-
return t("cli.init.claude-settings.updated", { label: updatedLabel(), path: settingsPath });
|
|
3052
|
-
}
|
|
3053
|
-
}
|
|
3054
2434
|
function formatInitStageHeader(message) {
|
|
3055
2435
|
return `${nextLabel()} ${paint.muted(message)}`;
|
|
3056
2436
|
}
|
|
@@ -3101,9 +2481,8 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
3101
2481
|
})
|
|
3102
2482
|
);
|
|
3103
2483
|
console.log(t("cli.init.plan.writes"));
|
|
3104
|
-
console.log(` - ${target}/.fabric/
|
|
2484
|
+
console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
|
|
3105
2485
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
3106
|
-
console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
|
|
3107
2486
|
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
3108
2487
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
3109
2488
|
}
|
|
@@ -3137,18 +2516,6 @@ function printInitCapabilitySummary(supports, stageResults, options) {
|
|
|
3137
2516
|
console.log(formatCapabilityTableRow(row, widths));
|
|
3138
2517
|
}
|
|
3139
2518
|
}
|
|
3140
|
-
function formatCodexHooksAction(configPath, action) {
|
|
3141
|
-
switch (action) {
|
|
3142
|
-
case "created":
|
|
3143
|
-
return t("cli.init.codex-hooks.created", { label: createdLabel(), path: configPath });
|
|
3144
|
-
case "overwritten":
|
|
3145
|
-
return t("cli.init.codex-hooks.updated", { label: overwrittenLabel(), path: configPath });
|
|
3146
|
-
case "skipped":
|
|
3147
|
-
return t("cli.init.codex-hooks.skipped", { label: skippedLabel(), path: configPath });
|
|
3148
|
-
default:
|
|
3149
|
-
return t("cli.init.codex-hooks.updated", { label: updatedLabel(), path: configPath });
|
|
3150
|
-
}
|
|
3151
|
-
}
|
|
3152
2519
|
function toCapabilityRow(support, stageResults, options) {
|
|
3153
2520
|
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
3154
2521
|
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.init.capabilities.status.na");
|
|
@@ -3209,18 +2576,6 @@ function formatCapabilityDivider(widths) {
|
|
|
3209
2576
|
}
|
|
3210
2577
|
function formatInitReasonMessage(supports) {
|
|
3211
2578
|
const detected = supports.filter((support) => support.detected);
|
|
3212
|
-
const installedSkillClients = detected.filter((support) => hasInstalledCapability(support, "skill"));
|
|
3213
|
-
const hasClaudeSkill = installedSkillClients.some((support) => support.clientKind === "ClaudeCodeCLI");
|
|
3214
|
-
const hasCodexSkill = installedSkillClients.some((support) => support.clientKind === "CodexCLI");
|
|
3215
|
-
if (hasClaudeSkill && hasCodexSkill) {
|
|
3216
|
-
return t("cli.init.reason-message.multi-body");
|
|
3217
|
-
}
|
|
3218
|
-
if (hasClaudeSkill) {
|
|
3219
|
-
return t("cli.init.reason-message.claude-body");
|
|
3220
|
-
}
|
|
3221
|
-
if (hasCodexSkill) {
|
|
3222
|
-
return t("cli.init.reason-message.codex-body");
|
|
3223
|
-
}
|
|
3224
2579
|
if (detected.some((support) => support.capabilities.skill)) {
|
|
3225
2580
|
return t("cli.init.reason-message.installable-body");
|
|
3226
2581
|
}
|
|
@@ -3232,11 +2587,11 @@ function yesNoLabel(value) {
|
|
|
3232
2587
|
function formatInitPathAction(path, action) {
|
|
3233
2588
|
return t("cli.init.created-path", { label: labelForInitWriteAction(action), path });
|
|
3234
2589
|
}
|
|
3235
|
-
function
|
|
3236
|
-
if (action === "
|
|
2590
|
+
function formatAgentsMdAction(path, action) {
|
|
2591
|
+
if (action === "preserved") {
|
|
3237
2592
|
return t("cli.init.skipped-existing-path", { label: skippedLabel(), path });
|
|
3238
2593
|
}
|
|
3239
|
-
return
|
|
2594
|
+
return t("cli.init.created-path", { label: createdLabel(), path });
|
|
3240
2595
|
}
|
|
3241
2596
|
function labelForInitWriteAction(action) {
|
|
3242
2597
|
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
@@ -3253,9 +2608,6 @@ function nextLabel() {
|
|
|
3253
2608
|
function reasonLabel() {
|
|
3254
2609
|
return paint.human(t("cli.shared.reason"));
|
|
3255
2610
|
}
|
|
3256
|
-
function updatedLabel() {
|
|
3257
|
-
return paint.success(t("cli.shared.updated"));
|
|
3258
|
-
}
|
|
3259
2611
|
function overwrittenLabel() {
|
|
3260
2612
|
return paint.warn(t("cli.init.force.overwritten"));
|
|
3261
2613
|
}
|
|
@@ -3268,13 +2620,10 @@ function skippedStageLabel() {
|
|
|
3268
2620
|
function failedStageLabel() {
|
|
3269
2621
|
return paint.error(t("cli.init.stages.failed"));
|
|
3270
2622
|
}
|
|
3271
|
-
function
|
|
2623
|
+
function writeStderr2(message) {
|
|
3272
2624
|
process.stderr.write(`${message}
|
|
3273
2625
|
`);
|
|
3274
2626
|
}
|
|
3275
|
-
function sha256(content) {
|
|
3276
|
-
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
3277
|
-
}
|
|
3278
2627
|
export {
|
|
3279
2628
|
buildInitExecutionPlan,
|
|
3280
2629
|
buildInitFabricPlan,
|