@fenglimg/fabric-cli 1.8.0-rc.3 → 2.0.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 +6 -6
- package/dist/chunk-6ICJICVU.js +10 -0
- package/dist/chunk-74SZWYPH.js +658 -0
- package/dist/chunk-EYIDD2YS.js +1000 -0
- package/dist/{chunk-QPCRBQ5Y.js → chunk-OBQU6NHO.js} +1 -52
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/doctor-T7JWODKG.js +282 -0
- package/dist/hooks-Y74Y5LQS.js +12 -0
- package/dist/index.js +7 -5
- package/dist/{init-7EYGUJNJ.js → init-BIRSIOXO.js} +312 -1022
- package/dist/plan-context-hint-QMUPAXIB.js +98 -0
- package/dist/scan-LMK3UCWL.js +22 -0
- package/dist/{serve-466QXQ5Q.js → serve-H554BHLG.js} +8 -4
- package/package.json +3 -3
- package/templates/agents-md/AGENTS.md.template +55 -17
- package/templates/bootstrap/CLAUDE.md +1 -1
- package/templates/bootstrap/codex-AGENTS-header.md +1 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +1 -1
- package/templates/hooks/configs/README.md +73 -0
- package/templates/hooks/configs/claude-code.json +37 -0
- package/templates/hooks/configs/codex-hooks.json +20 -0
- package/templates/hooks/configs/cursor-hooks.json +20 -0
- package/templates/hooks/fabric-hint.cjs +1307 -0
- package/templates/hooks/knowledge-hint-broad.cjs +464 -0
- package/templates/hooks/knowledge-hint-narrow.cjs +826 -0
- package/templates/hooks/lib/session-digest-writer.cjs +172 -0
- package/templates/skills/fabric-archive/SKILL.md +486 -0
- package/templates/skills/fabric-import/SKILL.md +588 -0
- package/templates/skills/fabric-review/SKILL.md +382 -0
- package/dist/chunk-NMMUETVK.js +0 -216
- package/dist/doctor-F52XWWZC.js +0 -98
- 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,247 +1,77 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
ClaudeCodeCLIWriter,
|
|
4
|
+
CursorWriter,
|
|
5
|
+
addArchiveSkillPointer,
|
|
6
|
+
createServerEntry,
|
|
7
|
+
hooksCommand,
|
|
8
|
+
installArchiveHintHook,
|
|
9
|
+
installFabricArchiveSkill,
|
|
10
|
+
installFabricImportSkill,
|
|
11
|
+
installFabricReviewSkill,
|
|
12
|
+
installHooks,
|
|
13
|
+
installKnowledgeHintBroadHook,
|
|
14
|
+
installKnowledgeHintNarrowHook,
|
|
15
|
+
mergeClaudeCodeHookConfig,
|
|
16
|
+
mergeCodexHookConfig,
|
|
17
|
+
mergeCursorHookConfig,
|
|
18
|
+
normalizeConfigPath,
|
|
19
|
+
writeJsonClientConfig
|
|
20
|
+
} from "./chunk-74SZWYPH.js";
|
|
21
|
+
import {
|
|
22
|
+
detectFramework,
|
|
23
|
+
runInitScan
|
|
24
|
+
} from "./chunk-EYIDD2YS.js";
|
|
6
25
|
import {
|
|
7
|
-
createDebugLogger,
|
|
8
26
|
displayWidth,
|
|
9
27
|
padEnd,
|
|
10
|
-
paint
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
paint
|
|
29
|
+
} from "./chunk-WWNXR34K.js";
|
|
30
|
+
import {
|
|
13
31
|
t
|
|
14
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-6ICJICVU.js";
|
|
33
|
+
import {
|
|
34
|
+
createDebugLogger,
|
|
35
|
+
resolveDevMode
|
|
36
|
+
} from "./chunk-OBQU6NHO.js";
|
|
15
37
|
|
|
16
38
|
// src/commands/init.ts
|
|
17
|
-
import {
|
|
39
|
+
import { randomUUID } from "crypto";
|
|
40
|
+
import { homedir as homedir4 } from "os";
|
|
18
41
|
import * as childProcess from "child_process";
|
|
19
|
-
import { appendFileSync,
|
|
20
|
-
import { dirname as
|
|
21
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
42
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
43
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "path";
|
|
22
44
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
45
|
+
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
46
|
+
import { atomicWriteJson, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
47
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
25
48
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
26
49
|
|
|
27
|
-
// src/
|
|
28
|
-
import { existsSync
|
|
29
|
-
import {
|
|
50
|
+
// src/commands/config.ts
|
|
51
|
+
import { existsSync as existsSync4 } from "fs";
|
|
52
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
53
|
+
import { resolve as resolve3 } from "path";
|
|
30
54
|
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
|
|
106
|
-
import { resolve as resolve5 } from "path";
|
|
107
55
|
import { defineCommand } from "citty";
|
|
108
56
|
|
|
109
57
|
// src/config/resolver.ts
|
|
110
|
-
import { existsSync as existsSync5 } from "fs";
|
|
111
|
-
import { join as join5 } from "path";
|
|
112
|
-
import { homedir as homedir4 } from "os";
|
|
113
|
-
|
|
114
|
-
// src/config/claude-code.ts
|
|
115
58
|
import { existsSync as existsSync3 } from "fs";
|
|
116
|
-
import { join as join3
|
|
117
|
-
import { homedir as
|
|
118
|
-
|
|
119
|
-
// src/config/json.ts
|
|
120
|
-
import { existsSync as existsSync2 } from "fs";
|
|
121
|
-
import { mkdir, readFile } from "fs/promises";
|
|
122
|
-
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
123
|
-
import { homedir } from "os";
|
|
124
|
-
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
125
|
-
|
|
126
|
-
// src/config/writer.ts
|
|
127
|
-
function createServerEntry(serverPath) {
|
|
128
|
-
return {
|
|
129
|
-
command: process.execPath,
|
|
130
|
-
args: [serverPath]
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// src/config/json.ts
|
|
135
|
-
function deepMerge(target, source) {
|
|
136
|
-
if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
137
|
-
return source;
|
|
138
|
-
}
|
|
139
|
-
const out = { ...target };
|
|
140
|
-
for (const key of Object.keys(source)) {
|
|
141
|
-
out[key] = deepMerge(
|
|
142
|
-
target[key],
|
|
143
|
-
source[key]
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
return out;
|
|
147
|
-
}
|
|
148
|
-
function expandHome(filePath) {
|
|
149
|
-
if (filePath === "~") {
|
|
150
|
-
return homedir();
|
|
151
|
-
}
|
|
152
|
-
if (filePath.startsWith("~/")) {
|
|
153
|
-
return join2(homedir(), filePath.slice(2));
|
|
154
|
-
}
|
|
155
|
-
return filePath;
|
|
156
|
-
}
|
|
157
|
-
function normalizeConfigPath(filePath) {
|
|
158
|
-
return resolve2(expandHome(filePath));
|
|
159
|
-
}
|
|
160
|
-
async function readJsonConfig(configPath) {
|
|
161
|
-
try {
|
|
162
|
-
const raw = await readFile(configPath, "utf8");
|
|
163
|
-
if (raw.trim().length === 0) {
|
|
164
|
-
return {};
|
|
165
|
-
}
|
|
166
|
-
const parsed = JSON.parse(raw);
|
|
167
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
168
|
-
throw new Error(`Expected JSON object in ${configPath}`);
|
|
169
|
-
}
|
|
170
|
-
return parsed;
|
|
171
|
-
} catch (error) {
|
|
172
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
173
|
-
return {};
|
|
174
|
-
}
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
179
|
-
const existing = await readJsonConfig(configPath);
|
|
180
|
-
const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
|
|
181
|
-
await mkdir(dirname2(configPath), { recursive: true });
|
|
182
|
-
await atomicWriteJson(configPath, merged, { indent: 2 });
|
|
183
|
-
}
|
|
184
|
-
var JsonClientConfigWriter = class {
|
|
185
|
-
configuredPath;
|
|
186
|
-
constructor(configuredPath) {
|
|
187
|
-
this.configuredPath = configuredPath;
|
|
188
|
-
}
|
|
189
|
-
async detect(workspaceRoot, overridePath) {
|
|
190
|
-
const explicitPath = overridePath ?? this.configuredPath;
|
|
191
|
-
if (explicitPath !== void 0) {
|
|
192
|
-
return normalizeConfigPath(explicitPath);
|
|
193
|
-
}
|
|
194
|
-
const configPath = this.defaultPath(workspaceRoot);
|
|
195
|
-
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
196
|
-
}
|
|
197
|
-
async write(serverPath, workspaceRoot, overridePath) {
|
|
198
|
-
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
199
|
-
if (configPath === null) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
206
|
-
clientKind = "ClaudeCodeCLI";
|
|
207
|
-
scope;
|
|
208
|
-
constructor(configuredPath, scope = "project") {
|
|
209
|
-
super(configuredPath);
|
|
210
|
-
this.scope = scope;
|
|
211
|
-
}
|
|
212
|
-
// Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
|
|
213
|
-
// or ~/.claude.json for user scope.
|
|
214
|
-
// Detection still checks ~/.claude to confirm Claude Code is installed.
|
|
215
|
-
defaultPath(workspaceRoot) {
|
|
216
|
-
const globalClaudeDir = join2(homedir(), ".claude");
|
|
217
|
-
const projectClaudeDir = join2(workspaceRoot, ".claude");
|
|
218
|
-
if (!existsSync2(globalClaudeDir) && !existsSync2(projectClaudeDir)) {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
return this.scope === "user" ? join2(homedir(), ".claude.json") : join2(workspaceRoot, ".mcp.json");
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
var CursorWriter = class extends JsonClientConfigWriter {
|
|
225
|
-
clientKind = "Cursor";
|
|
226
|
-
constructor(configuredPath) {
|
|
227
|
-
super(configuredPath);
|
|
228
|
-
}
|
|
229
|
-
defaultPath(workspaceRoot) {
|
|
230
|
-
const cursorDir = join2(workspaceRoot, ".cursor");
|
|
231
|
-
return existsSync2(cursorDir) ? join2(cursorDir, "mcp.json") : null;
|
|
232
|
-
}
|
|
233
|
-
};
|
|
59
|
+
import { join as join3 } from "path";
|
|
60
|
+
import { homedir as homedir3 } from "os";
|
|
234
61
|
|
|
235
62
|
// src/config/claude-code.ts
|
|
63
|
+
import { existsSync } from "fs";
|
|
64
|
+
import { join, resolve } from "path";
|
|
65
|
+
import { homedir, platform } from "os";
|
|
236
66
|
function getClaudeDesktopConfigPath() {
|
|
237
67
|
const os = platform();
|
|
238
68
|
if (os === "darwin") {
|
|
239
|
-
return
|
|
69
|
+
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
240
70
|
}
|
|
241
71
|
if (os === "win32") {
|
|
242
|
-
return
|
|
72
|
+
return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
243
73
|
}
|
|
244
|
-
return
|
|
74
|
+
return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
245
75
|
}
|
|
246
76
|
var ClaudeCodeDesktopWriter = class {
|
|
247
77
|
clientKind = "ClaudeCodeDesktop";
|
|
@@ -251,7 +81,7 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
251
81
|
}
|
|
252
82
|
async detect(_workspaceRoot, overridePath) {
|
|
253
83
|
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
254
|
-
return
|
|
84
|
+
return existsSync(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
255
85
|
}
|
|
256
86
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
257
87
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -266,17 +96,17 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
266
96
|
};
|
|
267
97
|
|
|
268
98
|
// src/config/toml.ts
|
|
269
|
-
import { existsSync as
|
|
270
|
-
import { mkdir
|
|
271
|
-
import { dirname
|
|
272
|
-
import { homedir as
|
|
273
|
-
import { atomicWriteText
|
|
274
|
-
function
|
|
99
|
+
import { existsSync as existsSync2 } from "fs";
|
|
100
|
+
import { mkdir, readFile } from "fs/promises";
|
|
101
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
102
|
+
import { homedir as homedir2 } from "os";
|
|
103
|
+
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
104
|
+
function expandHome(filePath) {
|
|
275
105
|
if (filePath === "~") {
|
|
276
|
-
return
|
|
106
|
+
return homedir2();
|
|
277
107
|
}
|
|
278
108
|
if (filePath.startsWith("~/")) {
|
|
279
|
-
return
|
|
109
|
+
return join2(homedir2(), filePath.slice(2));
|
|
280
110
|
}
|
|
281
111
|
return filePath;
|
|
282
112
|
}
|
|
@@ -325,7 +155,7 @@ ${block}`;
|
|
|
325
155
|
}
|
|
326
156
|
async function readTomlConfigText(configPath) {
|
|
327
157
|
try {
|
|
328
|
-
return await
|
|
158
|
+
return await readFile(configPath, "utf8");
|
|
329
159
|
} catch (error) {
|
|
330
160
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
331
161
|
return "";
|
|
@@ -342,10 +172,10 @@ var CodexTOMLConfigWriter = class {
|
|
|
342
172
|
async detect(_workspaceRoot, overridePath) {
|
|
343
173
|
const explicitPath = overridePath ?? this.configuredPath;
|
|
344
174
|
if (explicitPath !== void 0) {
|
|
345
|
-
return
|
|
175
|
+
return resolve2(expandHome(explicitPath));
|
|
346
176
|
}
|
|
347
|
-
const codexDir =
|
|
348
|
-
return
|
|
177
|
+
const codexDir = join2(homedir2(), ".codex");
|
|
178
|
+
return existsSync2(codexDir) ? resolve2(join2(codexDir, "config.toml")) : null;
|
|
349
179
|
}
|
|
350
180
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
351
181
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -354,8 +184,8 @@ var CodexTOMLConfigWriter = class {
|
|
|
354
184
|
}
|
|
355
185
|
const rawConfig = await readTomlConfigText(configPath);
|
|
356
186
|
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
357
|
-
await
|
|
358
|
-
await
|
|
187
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
188
|
+
await atomicWriteText(configPath, nextConfig);
|
|
359
189
|
}
|
|
360
190
|
};
|
|
361
191
|
|
|
@@ -375,25 +205,25 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
375
205
|
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
376
206
|
addIfDetected(
|
|
377
207
|
writers,
|
|
378
|
-
|
|
208
|
+
existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude")),
|
|
379
209
|
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
380
210
|
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
381
211
|
);
|
|
382
212
|
addIfDetected(
|
|
383
213
|
writers,
|
|
384
|
-
|
|
214
|
+
existsSync3(getClaudeDesktopConfigPath()),
|
|
385
215
|
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
386
216
|
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
387
217
|
);
|
|
388
218
|
addIfDetected(
|
|
389
219
|
writers,
|
|
390
|
-
|
|
220
|
+
existsSync3(join3(workspaceRoot, ".cursor")),
|
|
391
221
|
(configuredPath) => new CursorWriter(configuredPath),
|
|
392
222
|
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
393
223
|
);
|
|
394
224
|
addIfDetected(
|
|
395
225
|
writers,
|
|
396
|
-
|
|
226
|
+
existsSync3(join3(homedir3(), ".codex")),
|
|
397
227
|
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
398
228
|
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
399
229
|
);
|
|
@@ -401,10 +231,10 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
401
231
|
}
|
|
402
232
|
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
403
233
|
const clientPaths = fabricConfig.clientPaths;
|
|
404
|
-
const claudeDetected =
|
|
405
|
-
const claudeDesktopDetected =
|
|
406
|
-
const cursorDetected =
|
|
407
|
-
const codexDetected =
|
|
234
|
+
const claudeDetected = existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude"));
|
|
235
|
+
const claudeDesktopDetected = existsSync3(getClaudeDesktopConfigPath());
|
|
236
|
+
const cursorDetected = existsSync3(join3(workspaceRoot, ".cursor"));
|
|
237
|
+
const codexDetected = existsSync3(join3(homedir3(), ".codex"));
|
|
408
238
|
return [
|
|
409
239
|
{
|
|
410
240
|
clientKind: "ClaudeCodeCLI",
|
|
@@ -462,299 +292,18 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
462
292
|
skill: true
|
|
463
293
|
},
|
|
464
294
|
installedCapabilities: {
|
|
465
|
-
hook:
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
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
|
-
// src/commands/hooks.ts
|
|
613
|
-
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
614
|
-
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, parse as parse2, resolve as resolve6 } from "path";
|
|
615
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
616
|
-
import { defineCommand as defineCommand2 } from "citty";
|
|
617
|
-
import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
618
|
-
var hooksCommand = defineCommand2({
|
|
619
|
-
meta: {
|
|
620
|
-
name: "hooks",
|
|
621
|
-
description: t("cli.hooks.description")
|
|
622
|
-
},
|
|
623
|
-
subCommands: {
|
|
624
|
-
install: defineCommand2({
|
|
625
|
-
meta: {
|
|
626
|
-
name: "install",
|
|
627
|
-
description: t("cli.hooks.install.description")
|
|
628
|
-
},
|
|
629
|
-
args: {
|
|
630
|
-
target: {
|
|
631
|
-
type: "string",
|
|
632
|
-
description: t("cli.hooks.install.args.target.description"),
|
|
633
|
-
default: process.cwd()
|
|
634
|
-
}
|
|
635
|
-
},
|
|
636
|
-
async run({ args }) {
|
|
637
|
-
const result = await installHooks(args.target);
|
|
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
|
-
}
|
|
295
|
+
hook: existsSync3(join3(workspaceRoot, ".codex", "hooks.json")),
|
|
296
|
+
// v2/rc.2: v1 client-side init skill removed; skill-installation probes
|
|
297
|
+
// will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
|
|
298
|
+
// fabric-review, fabric-import). Until then there is nothing to probe.
|
|
299
|
+
skill: false
|
|
650
300
|
}
|
|
651
|
-
})
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
async function installHooks(target, options = {}) {
|
|
655
|
-
const normalizedTarget = normalizeTarget2(target);
|
|
656
|
-
assertExistingDirectory(normalizedTarget);
|
|
657
|
-
const huskyDir = join6(normalizedTarget, ".husky");
|
|
658
|
-
const hookPath = join6(huskyDir, "pre-commit");
|
|
659
|
-
const packageJsonPath = join6(normalizedTarget, "package.json");
|
|
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
|
-
};
|
|
697
|
-
}
|
|
698
|
-
function normalizeTarget2(targetInput) {
|
|
699
|
-
return isAbsolute2(targetInput) ? targetInput : resolve6(process.cwd(), targetInput);
|
|
700
|
-
}
|
|
701
|
-
function assertExistingDirectory(target) {
|
|
702
|
-
if (!existsSync6(target) || !statSync(target).isDirectory()) {
|
|
703
|
-
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
async function installHookFile(hookPath, templateContent, force) {
|
|
707
|
-
if (existsSync6(hookPath)) {
|
|
708
|
-
if (force) {
|
|
709
|
-
await atomicWriteText3(hookPath, templateContent);
|
|
710
|
-
return "overwritten";
|
|
711
301
|
}
|
|
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
302
|
];
|
|
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
303
|
}
|
|
755
304
|
|
|
756
305
|
// src/commands/config.ts
|
|
757
|
-
var
|
|
306
|
+
var CLIENT_ALIASES = {
|
|
758
307
|
claude: "ClaudeCodeCLI",
|
|
759
308
|
claudecodecli: "ClaudeCodeCLI",
|
|
760
309
|
"claude-code-cli": "ClaudeCodeCLI",
|
|
@@ -767,14 +316,14 @@ var CLIENT_ALIASES2 = {
|
|
|
767
316
|
"codex-cli": "CodexCLI",
|
|
768
317
|
codex: "CodexCLI"
|
|
769
318
|
};
|
|
770
|
-
function
|
|
319
|
+
function parseClientFilter(value) {
|
|
771
320
|
if (value === void 0 || value.trim().length === 0) {
|
|
772
321
|
return null;
|
|
773
322
|
}
|
|
774
323
|
const clients = /* @__PURE__ */ new Set();
|
|
775
324
|
for (const rawClient of value.split(",")) {
|
|
776
325
|
const alias = rawClient.trim().toLowerCase();
|
|
777
|
-
const clientKind =
|
|
326
|
+
const clientKind = CLIENT_ALIASES[alias];
|
|
778
327
|
if (clientKind === void 0) {
|
|
779
328
|
throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
|
|
780
329
|
}
|
|
@@ -783,11 +332,11 @@ function parseClientFilter2(value) {
|
|
|
783
332
|
return clients;
|
|
784
333
|
}
|
|
785
334
|
async function loadFabricConfig(workspaceRoot) {
|
|
786
|
-
const configPath =
|
|
787
|
-
if (!
|
|
335
|
+
const configPath = resolve3(workspaceRoot, "fabric.config.json");
|
|
336
|
+
if (!existsSync4(configPath)) {
|
|
788
337
|
return {};
|
|
789
338
|
}
|
|
790
|
-
const parsed = JSON.parse(await
|
|
339
|
+
const parsed = JSON.parse(await readFile2(configPath, "utf8"));
|
|
791
340
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
792
341
|
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
793
342
|
}
|
|
@@ -795,21 +344,21 @@ async function loadFabricConfig(workspaceRoot) {
|
|
|
795
344
|
}
|
|
796
345
|
function resolveServerPath(override) {
|
|
797
346
|
if (override) return override;
|
|
798
|
-
if (process.env.FAB_SERVER_PATH) return
|
|
799
|
-
return
|
|
347
|
+
if (process.env.FAB_SERVER_PATH) return resolve3(process.env.FAB_SERVER_PATH);
|
|
348
|
+
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
800
349
|
}
|
|
801
|
-
function
|
|
350
|
+
function writeStderr(message) {
|
|
802
351
|
process.stderr.write(`${message}
|
|
803
352
|
`);
|
|
804
353
|
}
|
|
805
|
-
var configCmd =
|
|
354
|
+
var configCmd = defineCommand({
|
|
806
355
|
meta: {
|
|
807
356
|
name: "config",
|
|
808
357
|
description: t("cli.config.description")
|
|
809
358
|
},
|
|
810
359
|
subCommands: {
|
|
811
360
|
hooks: hooksCommand,
|
|
812
|
-
install:
|
|
361
|
+
install: defineCommand({
|
|
813
362
|
meta: {
|
|
814
363
|
name: "install",
|
|
815
364
|
description: t("cli.config.install.description")
|
|
@@ -826,26 +375,26 @@ var configCmd = defineCommand3({
|
|
|
826
375
|
}
|
|
827
376
|
},
|
|
828
377
|
async run({ args }) {
|
|
829
|
-
const selectedClients =
|
|
378
|
+
const selectedClients = parseClientFilter(args.clients);
|
|
830
379
|
const result = await installMcpClients(process.cwd(), {
|
|
831
380
|
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
832
381
|
dryRun: args["dry-run"]
|
|
833
382
|
});
|
|
834
383
|
if (result.details.length === 0) {
|
|
835
|
-
|
|
384
|
+
writeStderr(t("cli.config.install.no-configs"));
|
|
836
385
|
return;
|
|
837
386
|
}
|
|
838
387
|
for (const detail of result.details) {
|
|
839
388
|
if (detail.action === "skipped") {
|
|
840
|
-
|
|
389
|
+
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
841
390
|
continue;
|
|
842
391
|
}
|
|
843
392
|
if (detail.action === "dry-run" && detail.path !== null) {
|
|
844
|
-
|
|
393
|
+
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
845
394
|
continue;
|
|
846
395
|
}
|
|
847
396
|
if (detail.path !== null) {
|
|
848
|
-
|
|
397
|
+
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
849
398
|
}
|
|
850
399
|
}
|
|
851
400
|
}
|
|
@@ -853,7 +402,7 @@ var configCmd = defineCommand3({
|
|
|
853
402
|
}
|
|
854
403
|
});
|
|
855
404
|
async function installMcpClients(target, options = {}) {
|
|
856
|
-
const workspaceRoot =
|
|
405
|
+
const workspaceRoot = resolve3(target);
|
|
857
406
|
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
858
407
|
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
859
408
|
const serverPath = resolveServerPath(options.localServerPath);
|
|
@@ -884,9 +433,9 @@ async function installMcpClients(target, options = {}) {
|
|
|
884
433
|
|
|
885
434
|
// src/scanner/forensic.ts
|
|
886
435
|
import { execFileSync } from "child_process";
|
|
887
|
-
import { existsSync as
|
|
436
|
+
import { existsSync as existsSync5, readdirSync, readFileSync, statSync } from "fs";
|
|
888
437
|
import { createRequire } from "module";
|
|
889
|
-
import { basename, extname, isAbsolute
|
|
438
|
+
import { basename, extname, isAbsolute, join as join4, posix, relative, resolve as resolve4, sep } from "path";
|
|
890
439
|
import {
|
|
891
440
|
forensicReportSchema
|
|
892
441
|
} from "@fenglimg/fabric-shared";
|
|
@@ -972,7 +521,7 @@ var parserInitPromise = null;
|
|
|
972
521
|
var languagePromiseByKind = {};
|
|
973
522
|
var parserBundlePromiseByKind = {};
|
|
974
523
|
async function buildForensicReport(targetInput) {
|
|
975
|
-
const target =
|
|
524
|
+
const target = normalizeTarget(targetInput);
|
|
976
525
|
const framework = detectFramework(target);
|
|
977
526
|
const topology = buildTopology(target);
|
|
978
527
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -1008,11 +557,11 @@ async function buildForensicReport(targetInput) {
|
|
|
1008
557
|
}
|
|
1009
558
|
return validation.data;
|
|
1010
559
|
}
|
|
1011
|
-
function
|
|
1012
|
-
return
|
|
560
|
+
function normalizeTarget(targetInput) {
|
|
561
|
+
return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
|
|
1013
562
|
}
|
|
1014
563
|
function buildTopology(root) {
|
|
1015
|
-
|
|
564
|
+
assertExistingDirectory(root);
|
|
1016
565
|
const byExt = {};
|
|
1017
566
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
1018
567
|
const files = [];
|
|
@@ -1025,7 +574,7 @@ function buildTopology(root) {
|
|
|
1025
574
|
continue;
|
|
1026
575
|
}
|
|
1027
576
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1028
|
-
const absolutePath =
|
|
577
|
+
const absolutePath = join4(current, entry.name);
|
|
1029
578
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
1030
579
|
if (relativePath.length === 0) {
|
|
1031
580
|
continue;
|
|
@@ -1045,7 +594,7 @@ function buildTopology(root) {
|
|
|
1045
594
|
if (!entry.isFile()) {
|
|
1046
595
|
continue;
|
|
1047
596
|
}
|
|
1048
|
-
const stats =
|
|
597
|
+
const stats = statSync(absolutePath);
|
|
1049
598
|
const extension = extname(entry.name) || "[none]";
|
|
1050
599
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
1051
600
|
totalFiles += 1;
|
|
@@ -1063,8 +612,8 @@ function buildTopology(root) {
|
|
|
1063
612
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
1064
613
|
};
|
|
1065
614
|
}
|
|
1066
|
-
function
|
|
1067
|
-
if (!
|
|
615
|
+
function assertExistingDirectory(target) {
|
|
616
|
+
if (!existsSync5(target) || !statSync(target).isDirectory()) {
|
|
1068
617
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
1069
618
|
}
|
|
1070
619
|
}
|
|
@@ -1113,7 +662,7 @@ function getEntryPointReason(relativePath) {
|
|
|
1113
662
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
1114
663
|
const samples = [];
|
|
1115
664
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
1116
|
-
const absolutePath =
|
|
665
|
+
const absolutePath = join4(target, ...entryPoint.path.split("/"));
|
|
1117
666
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
1118
667
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
1119
668
|
frameworkKind,
|
|
@@ -1133,7 +682,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
1133
682
|
}
|
|
1134
683
|
function readFirstLines(path, lineLimit) {
|
|
1135
684
|
try {
|
|
1136
|
-
const lines =
|
|
685
|
+
const lines = readFileSync(path, "utf8").split(/\r?\n/);
|
|
1137
686
|
if (lines.at(-1) === "") {
|
|
1138
687
|
lines.pop();
|
|
1139
688
|
}
|
|
@@ -1150,12 +699,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
1150
699
|
}
|
|
1151
700
|
}
|
|
1152
701
|
function readPackageDependencies(target) {
|
|
1153
|
-
const packageJsonPath =
|
|
1154
|
-
if (!
|
|
702
|
+
const packageJsonPath = join4(target, "package.json");
|
|
703
|
+
if (!existsSync5(packageJsonPath)) {
|
|
1155
704
|
return /* @__PURE__ */ new Map();
|
|
1156
705
|
}
|
|
1157
706
|
try {
|
|
1158
|
-
const packageJson = JSON.parse(
|
|
707
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1159
708
|
return new Map([
|
|
1160
709
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
1161
710
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -1491,16 +1040,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
1491
1040
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1492
1041
|
}
|
|
1493
1042
|
function readReadmeInfo(target) {
|
|
1494
|
-
const readmePath =
|
|
1495
|
-
const hasContributing =
|
|
1496
|
-
if (!
|
|
1043
|
+
const readmePath = join4(target, "README.md");
|
|
1044
|
+
const hasContributing = existsSync5(join4(target, "CONTRIBUTING.md"));
|
|
1045
|
+
if (!existsSync5(readmePath)) {
|
|
1497
1046
|
return {
|
|
1498
1047
|
quality: "missing",
|
|
1499
1048
|
line_count: 0,
|
|
1500
1049
|
has_contributing: hasContributing
|
|
1501
1050
|
};
|
|
1502
1051
|
}
|
|
1503
|
-
const readme =
|
|
1052
|
+
const readme = readFileSync(readmePath, "utf8");
|
|
1504
1053
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
1505
1054
|
return {
|
|
1506
1055
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -1733,7 +1282,7 @@ function buildDomainAssertion(codeSamples) {
|
|
|
1733
1282
|
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
1734
1283
|
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
1735
1284
|
]),
|
|
1736
|
-
proposedRule: "Preserve domain-specific module names when
|
|
1285
|
+
proposedRule: "Preserve domain-specific module names when authoring knowledge entries that reference these modules."
|
|
1737
1286
|
});
|
|
1738
1287
|
}
|
|
1739
1288
|
function createAssertion(input) {
|
|
@@ -1978,10 +1527,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1978
1527
|
return recommendations;
|
|
1979
1528
|
}
|
|
1980
1529
|
function readProjectName(target) {
|
|
1981
|
-
const packageJsonPath =
|
|
1982
|
-
if (
|
|
1530
|
+
const packageJsonPath = join4(target, "package.json");
|
|
1531
|
+
if (existsSync5(packageJsonPath)) {
|
|
1983
1532
|
try {
|
|
1984
|
-
const packageJson = JSON.parse(
|
|
1533
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1985
1534
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1986
1535
|
return packageJson.name;
|
|
1987
1536
|
}
|
|
@@ -1992,7 +1541,7 @@ function readProjectName(target) {
|
|
|
1992
1541
|
return basename(target);
|
|
1993
1542
|
}
|
|
1994
1543
|
function getCliVersion() {
|
|
1995
|
-
return true ? "
|
|
1544
|
+
return true ? "2.0.0" : "unknown";
|
|
1996
1545
|
}
|
|
1997
1546
|
function sortRecord(record) {
|
|
1998
1547
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -2002,18 +1551,19 @@ function toPosixPath(path) {
|
|
|
2002
1551
|
}
|
|
2003
1552
|
|
|
2004
1553
|
// src/commands/init.ts
|
|
2005
|
-
var
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1554
|
+
var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
|
|
1555
|
+
|
|
1556
|
+
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
1557
|
+
|
|
1558
|
+
Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
|
|
1559
|
+
Run \`fabric doctor\` to verify state.
|
|
1560
|
+
|
|
1561
|
+
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1562
|
+
`;
|
|
1563
|
+
var LOCAL_FABRIC_SERVER_PATH = join5("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
2014
1564
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
2015
1565
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
2016
|
-
var initCommand =
|
|
1566
|
+
var initCommand = defineCommand2({
|
|
2017
1567
|
meta: {
|
|
2018
1568
|
name: "init",
|
|
2019
1569
|
description: t("cli.init.description")
|
|
@@ -2095,13 +1645,13 @@ async function runInitCommand(args) {
|
|
|
2095
1645
|
logger(step);
|
|
2096
1646
|
}
|
|
2097
1647
|
if (intent.options.planOnly) {
|
|
2098
|
-
|
|
1648
|
+
writeStderr2(t("cli.init.compat.plan"));
|
|
2099
1649
|
}
|
|
2100
1650
|
if (args.interactive === false) {
|
|
2101
|
-
|
|
1651
|
+
writeStderr2(t("cli.init.compat.interactive"));
|
|
2102
1652
|
}
|
|
2103
1653
|
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
2104
|
-
|
|
1654
|
+
writeStderr2(t("cli.init.compat.legacy-stage-flags"));
|
|
2105
1655
|
}
|
|
2106
1656
|
const supports = detectClientSupports(intent.target);
|
|
2107
1657
|
const basePlan = await buildInitExecutionPlan({
|
|
@@ -2117,10 +1667,44 @@ async function runInitCommand(args) {
|
|
|
2117
1667
|
process.exitCode = 130;
|
|
2118
1668
|
return;
|
|
2119
1669
|
}
|
|
2120
|
-
|
|
1670
|
+
const result = await executeInitExecutionPlan(plan);
|
|
1671
|
+
try {
|
|
1672
|
+
await maybeWriteImportSentinel({
|
|
1673
|
+
target: intent.target,
|
|
1674
|
+
planOnly: intent.options.planOnly === true,
|
|
1675
|
+
wizardEnabled: intent.wizardEnabled,
|
|
1676
|
+
terminalInteractive: intent.interactiveSummary
|
|
1677
|
+
});
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
if (!intent.options.planOnly) {
|
|
1681
|
+
console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
|
|
1682
|
+
}
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1685
|
+
async function maybeWriteImportSentinel(opts) {
|
|
1686
|
+
if (opts.planOnly) return;
|
|
1687
|
+
if (process.env.FABRIC_NONINTERACTIVE === "1") return;
|
|
1688
|
+
if (!opts.wizardEnabled || !opts.terminalInteractive) return;
|
|
1689
|
+
if (!Boolean(process.stdin.isTTY)) return;
|
|
1690
|
+
const answer = await confirm({
|
|
1691
|
+
message: "\u4E0B\u6B21\u5F00 AI \u65F6\u8BA9\u6211\u4ECE git log \u62BD\u66F4\u591A\u77E5\u8BC6\u5417?",
|
|
1692
|
+
initialValue: true
|
|
1693
|
+
});
|
|
1694
|
+
if (isCancel(answer) || answer !== true) return;
|
|
1695
|
+
const fabricDir = join5(opts.target, ".fabric");
|
|
1696
|
+
const sentinelPath = join5(fabricDir, ".import-requested");
|
|
1697
|
+
try {
|
|
1698
|
+
if (!existsSync6(fabricDir)) {
|
|
1699
|
+
mkdirSync(fabricDir, { recursive: true });
|
|
1700
|
+
}
|
|
1701
|
+
writeFileSync(sentinelPath, "", "utf8");
|
|
1702
|
+
log.success("\u4E0B\u6B21\u5F00 AI \u4F1A\u770B\u5230\u63D0\u793A");
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
2121
1705
|
}
|
|
2122
1706
|
function resolveInitCliIntent(args, targetInput) {
|
|
2123
|
-
const target =
|
|
1707
|
+
const target = normalizeTarget2(targetInput);
|
|
2124
1708
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
2125
1709
|
const claudeMcpScope = resolveClaudeMcpScope(args.scope);
|
|
2126
1710
|
const terminalInteractive = isInteractiveInit();
|
|
@@ -2150,7 +1734,7 @@ function resolveClaudeMcpScope(raw) {
|
|
|
2150
1734
|
if (raw === "user") {
|
|
2151
1735
|
return "user";
|
|
2152
1736
|
}
|
|
2153
|
-
|
|
1737
|
+
writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
|
|
2154
1738
|
return "project";
|
|
2155
1739
|
}
|
|
2156
1740
|
async function buildInitExecutionPlan(input) {
|
|
@@ -2190,10 +1774,10 @@ async function buildInitExecutionPlan(input) {
|
|
|
2190
1774
|
}
|
|
2191
1775
|
async function executeInitExecutionPlan(plan) {
|
|
2192
1776
|
if (plan.options.force) {
|
|
2193
|
-
|
|
1777
|
+
writeStderr2(t("cli.init.force.warning", { path: plan.target }));
|
|
2194
1778
|
}
|
|
2195
1779
|
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
2196
|
-
|
|
1780
|
+
writeStderr2(formatInitModeBanner(plan.options));
|
|
2197
1781
|
}
|
|
2198
1782
|
if (plan.interactive) {
|
|
2199
1783
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
@@ -2238,98 +1822,77 @@ async function executeInitExecutionPlan(plan) {
|
|
|
2238
1822
|
finalSupports
|
|
2239
1823
|
};
|
|
2240
1824
|
}
|
|
1825
|
+
var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
|
|
1826
|
+
function resolvePersonalFabricRoot() {
|
|
1827
|
+
return process.env.FABRIC_HOME ?? homedir4();
|
|
1828
|
+
}
|
|
2241
1829
|
async function buildInitFabricPlan(target, options) {
|
|
2242
|
-
|
|
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");
|
|
1830
|
+
assertExistingDirectory2(target);
|
|
1831
|
+
const fabricDir = join5(target, ".fabric");
|
|
1832
|
+
const agentsMdPath = join5(target, "AGENTS.md");
|
|
1833
|
+
const agentsMdAction = existsSync6(agentsMdPath) ? "preserved" : "created";
|
|
1834
|
+
const knowledgeDir = join5(fabricDir, "knowledge");
|
|
1835
|
+
const personalKnowledgeDir = join5(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1836
|
+
const forensicPath = join5(fabricDir, "forensic.json");
|
|
1837
|
+
const eventsPath = join5(fabricDir, "events.jsonl");
|
|
1838
|
+
const metaPath = join5(fabricDir, "agents.meta.json");
|
|
2257
1839
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
2258
|
-
const
|
|
1840
|
+
const knowledgeDirAction = existsSync6(knowledgeDir) ? "overwritten" : "created";
|
|
2259
1841
|
const metaAction = planFreshPath(metaPath, options);
|
|
2260
|
-
const taxonomyAction = planFreshPath(taxonomyPath, options);
|
|
2261
1842
|
const eventsAction = planFreshPath(eventsPath, options);
|
|
2262
1843
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
2263
1844
|
const forensicReport = await buildForensicReport(target);
|
|
2264
|
-
const
|
|
2265
|
-
const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
|
|
2266
|
-
const bootstrapHash = sha256(bootstrapContent);
|
|
2267
|
-
const meta = createInitialMeta(bootstrapHash);
|
|
1845
|
+
const meta = createInitialMeta();
|
|
2268
1846
|
return {
|
|
2269
1847
|
target,
|
|
2270
1848
|
options,
|
|
2271
1849
|
fabricDir,
|
|
2272
1850
|
replaceFabricDir,
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
1851
|
+
agentsMdPath,
|
|
1852
|
+
agentsMdAction,
|
|
1853
|
+
knowledgeDir,
|
|
1854
|
+
knowledgeDirAction,
|
|
1855
|
+
personalKnowledgeDir,
|
|
2276
1856
|
metaPath,
|
|
2277
1857
|
metaAction,
|
|
2278
1858
|
meta,
|
|
2279
|
-
taxonomyPath,
|
|
2280
|
-
taxonomyAction,
|
|
2281
|
-
taxonomyContent,
|
|
2282
|
-
rulesDir,
|
|
2283
1859
|
eventsPath,
|
|
2284
1860
|
eventsAction,
|
|
2285
1861
|
forensicPath,
|
|
2286
1862
|
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)
|
|
1863
|
+
forensicReport
|
|
2310
1864
|
};
|
|
2311
1865
|
}
|
|
2312
1866
|
async function executeInitFabricPlan(plan) {
|
|
2313
1867
|
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
1868
|
if (plan.replaceFabricDir) {
|
|
2317
1869
|
rmSync(plan.fabricDir, { force: true });
|
|
2318
1870
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
1871
|
+
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1872
|
+
if (plan.agentsMdAction === "created" && !existsSync6(plan.agentsMdPath)) {
|
|
1873
|
+
await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1874
|
+
}
|
|
1875
|
+
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1876
|
+
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1877
|
+
const teamSubDir = join5(plan.knowledgeDir, sub);
|
|
1878
|
+
mkdirSync(teamSubDir, { recursive: true });
|
|
1879
|
+
const teamGitkeep = join5(teamSubDir, ".gitkeep");
|
|
1880
|
+
if (!existsSync6(teamGitkeep)) {
|
|
1881
|
+
writeFileSync(teamGitkeep, "", "utf8");
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
try {
|
|
1885
|
+
mkdirSync(plan.personalKnowledgeDir, { recursive: true });
|
|
1886
|
+
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1887
|
+
mkdirSync(join5(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1888
|
+
}
|
|
1889
|
+
} catch {
|
|
1890
|
+
}
|
|
1891
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1892
|
+
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
2330
1893
|
if (isReapply) {
|
|
2331
|
-
if (!
|
|
2332
|
-
|
|
1894
|
+
if (!existsSync6(plan.eventsPath)) {
|
|
1895
|
+
mkdirSync(dirname2(plan.eventsPath), { recursive: true });
|
|
2333
1896
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2334
1897
|
}
|
|
2335
1898
|
} else {
|
|
@@ -2337,46 +1900,33 @@ async function executeInitFabricPlan(plan) {
|
|
|
2337
1900
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2338
1901
|
}
|
|
2339
1902
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
2340
|
-
await
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
1903
|
+
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
1904
|
+
if (!plan.options?.reapply) {
|
|
1905
|
+
try {
|
|
1906
|
+
await runInitScan(plan.target, { source: "init" });
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
writeStderr2(
|
|
1909
|
+
`[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
2348
1913
|
if (isReapply) {
|
|
2349
1914
|
appendReapplyLedgerEvent(plan.eventsPath, {
|
|
2350
|
-
preserved_ledger: true
|
|
2351
|
-
preserved_meta: preserveMeta,
|
|
2352
|
-
rules_count: existingRules.length
|
|
1915
|
+
preserved_ledger: true
|
|
2353
1916
|
});
|
|
2354
1917
|
}
|
|
2355
1918
|
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
1919
|
+
agentsMdPath: plan.agentsMdPath,
|
|
1920
|
+
agentsMdAction: plan.agentsMdAction,
|
|
1921
|
+
knowledgeDir: plan.knowledgeDir,
|
|
1922
|
+
knowledgeDirAction: plan.knowledgeDirAction,
|
|
1923
|
+
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2358
1924
|
metaPath: plan.metaPath,
|
|
2359
1925
|
metaAction: plan.metaAction,
|
|
2360
|
-
taxonomyPath: plan.taxonomyPath,
|
|
2361
|
-
taxonomyAction: plan.taxonomyAction,
|
|
2362
1926
|
eventsPath: plan.eventsPath,
|
|
2363
1927
|
eventsAction: plan.eventsAction,
|
|
2364
1928
|
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
|
|
1929
|
+
forensicAction: plan.forensicAction
|
|
2380
1930
|
};
|
|
2381
1931
|
}
|
|
2382
1932
|
async function initFabric(target, options) {
|
|
@@ -2421,22 +1971,11 @@ function exhaustiveInitStagePlan(value) {
|
|
|
2421
1971
|
throw new Error(`Unsupported init stage plan: ${JSON.stringify(value)}`);
|
|
2422
1972
|
}
|
|
2423
1973
|
function printInitScaffoldResult(created) {
|
|
2424
|
-
console.log(
|
|
1974
|
+
console.log(formatAgentsMdAction(created.agentsMdPath, created.agentsMdAction));
|
|
1975
|
+
console.log(formatInitPathAction(created.knowledgeDir, created.knowledgeDirAction));
|
|
2425
1976
|
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
2426
|
-
console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
|
|
2427
1977
|
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
2428
1978
|
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
1979
|
}
|
|
2441
1980
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
2442
1981
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
@@ -2470,30 +2009,17 @@ function printInitPlanPreview(plan) {
|
|
|
2470
2009
|
}
|
|
2471
2010
|
function buildPlanOnlyScaffoldResult(plan) {
|
|
2472
2011
|
return {
|
|
2473
|
-
|
|
2474
|
-
|
|
2012
|
+
agentsMdPath: plan.agentsMdPath,
|
|
2013
|
+
agentsMdAction: plan.agentsMdAction,
|
|
2014
|
+
knowledgeDir: plan.knowledgeDir,
|
|
2015
|
+
knowledgeDirAction: plan.knowledgeDirAction,
|
|
2016
|
+
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2475
2017
|
metaPath: plan.metaPath,
|
|
2476
2018
|
metaAction: plan.metaAction,
|
|
2477
|
-
taxonomyPath: plan.taxonomyPath,
|
|
2478
|
-
taxonomyAction: plan.taxonomyAction,
|
|
2479
2019
|
eventsPath: plan.eventsPath,
|
|
2480
2020
|
eventsAction: plan.eventsAction,
|
|
2481
2021
|
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
|
|
2022
|
+
forensicAction: plan.forensicAction
|
|
2497
2023
|
};
|
|
2498
2024
|
}
|
|
2499
2025
|
async function executeInitStagePlan(plan, stageName) {
|
|
@@ -2508,25 +2034,38 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2508
2034
|
try {
|
|
2509
2035
|
switch (stage.name) {
|
|
2510
2036
|
case "bootstrap": {
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2037
|
+
const installResults = [];
|
|
2038
|
+
installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
|
|
2039
|
+
installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
|
|
2040
|
+
installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
|
|
2041
|
+
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
2042
|
+
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
2043
|
+
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
2044
|
+
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
2045
|
+
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
2046
|
+
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
2047
|
+
installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
|
|
2048
|
+
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
2049
|
+
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
2050
|
+
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
2051
|
+
for (const result of installResults) {
|
|
2052
|
+
if (result.status === "error") {
|
|
2053
|
+
writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
2054
|
+
}
|
|
2515
2055
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
);
|
|
2056
|
+
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
2057
|
+
console.log(formatInitStageResult("bootstrap", "completed", installedCount, skippedCount, note2));
|
|
2519
2058
|
return { name: "bootstrap", disposition: "ran" };
|
|
2520
2059
|
}
|
|
2521
2060
|
case "mcp": {
|
|
2522
2061
|
if (stage.installMode === "local") {
|
|
2523
2062
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
2524
|
-
|
|
2525
|
-
|
|
2063
|
+
writeStderr2(t("cli.init.mcp.install.local"));
|
|
2064
|
+
writeStderr2(t("cli.init.mcp.local.installing", { manager }));
|
|
2526
2065
|
installLocalFabricServer(plan.target, manager);
|
|
2527
|
-
|
|
2066
|
+
writeStderr2(t("cli.init.mcp.local.installed"));
|
|
2528
2067
|
} else {
|
|
2529
|
-
|
|
2068
|
+
writeStderr2(t("cli.init.mcp.install.global"));
|
|
2530
2069
|
}
|
|
2531
2070
|
const result = await installMcpClients(plan.target, {
|
|
2532
2071
|
force: plan.options.force,
|
|
@@ -2549,15 +2088,15 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2549
2088
|
return exhaustiveInitStagePlan(stage);
|
|
2550
2089
|
}
|
|
2551
2090
|
} catch (error) {
|
|
2552
|
-
|
|
2091
|
+
writeStderr2(formatInitStageFailure(stageName, error));
|
|
2553
2092
|
return { name: stageName, disposition: "failed" };
|
|
2554
2093
|
}
|
|
2555
2094
|
}
|
|
2556
2095
|
function shouldReplaceWritableDirectory(path, options) {
|
|
2557
|
-
if (!
|
|
2096
|
+
if (!existsSync6(path)) {
|
|
2558
2097
|
return false;
|
|
2559
2098
|
}
|
|
2560
|
-
if (
|
|
2099
|
+
if (statSync2(path).isDirectory()) {
|
|
2561
2100
|
return false;
|
|
2562
2101
|
}
|
|
2563
2102
|
if (!options?.force) {
|
|
@@ -2566,7 +2105,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
2566
2105
|
return true;
|
|
2567
2106
|
}
|
|
2568
2107
|
function planFreshPath(path, options) {
|
|
2569
|
-
if (!
|
|
2108
|
+
if (!existsSync6(path)) {
|
|
2570
2109
|
return "created";
|
|
2571
2110
|
}
|
|
2572
2111
|
if (!options?.force) {
|
|
@@ -2575,131 +2114,11 @@ function planFreshPath(path, options) {
|
|
|
2575
2114
|
return "overwritten";
|
|
2576
2115
|
}
|
|
2577
2116
|
function preparePlannedPath(path, action) {
|
|
2578
|
-
|
|
2579
|
-
if (action === "overwritten" &&
|
|
2117
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
2118
|
+
if (action === "overwritten" && existsSync6(path)) {
|
|
2580
2119
|
rmSync(path, { recursive: true, force: true });
|
|
2581
2120
|
}
|
|
2582
2121
|
}
|
|
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
2122
|
function createDefaultInitWizardAdapter() {
|
|
2704
2123
|
return {
|
|
2705
2124
|
async run(context) {
|
|
@@ -2871,23 +2290,23 @@ function formatInitModeBadge(options) {
|
|
|
2871
2290
|
}
|
|
2872
2291
|
return t("cli.init.mode.badge.default");
|
|
2873
2292
|
}
|
|
2874
|
-
function
|
|
2875
|
-
return
|
|
2293
|
+
function normalizeTarget2(targetInput) {
|
|
2294
|
+
return isAbsolute2(targetInput) ? targetInput : resolve5(process.cwd(), targetInput);
|
|
2876
2295
|
}
|
|
2877
|
-
function
|
|
2878
|
-
if (!
|
|
2296
|
+
function assertExistingDirectory2(target) {
|
|
2297
|
+
if (!existsSync6(target) || !statSync2(target).isDirectory()) {
|
|
2879
2298
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2880
2299
|
}
|
|
2881
2300
|
}
|
|
2882
2301
|
function detectPackageManager(cwd) {
|
|
2883
|
-
const workspaceRoot =
|
|
2884
|
-
if (
|
|
2302
|
+
const workspaceRoot = resolve5(cwd);
|
|
2303
|
+
if (existsSync6(join5(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2885
2304
|
return "pnpm";
|
|
2886
2305
|
}
|
|
2887
|
-
if (
|
|
2306
|
+
if (existsSync6(join5(workspaceRoot, "yarn.lock"))) {
|
|
2888
2307
|
return "yarn";
|
|
2889
2308
|
}
|
|
2890
|
-
if (
|
|
2309
|
+
if (existsSync6(join5(workspaceRoot, "package-lock.json"))) {
|
|
2891
2310
|
return "npm";
|
|
2892
2311
|
}
|
|
2893
2312
|
return "npm";
|
|
@@ -2896,7 +2315,7 @@ function resolveMcpInstallMode(rawMode) {
|
|
|
2896
2315
|
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
2897
2316
|
return rawMode ?? "global";
|
|
2898
2317
|
}
|
|
2899
|
-
|
|
2318
|
+
writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
2900
2319
|
return "global";
|
|
2901
2320
|
}
|
|
2902
2321
|
function installLocalFabricServer(target, manager) {
|
|
@@ -2907,20 +2326,11 @@ function installLocalFabricServer(target, manager) {
|
|
|
2907
2326
|
shell: process.platform === "win32"
|
|
2908
2327
|
});
|
|
2909
2328
|
}
|
|
2910
|
-
function createInitialMeta(
|
|
2329
|
+
function createInitialMeta() {
|
|
2911
2330
|
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
|
-
}
|
|
2331
|
+
revision: "sha256:initial",
|
|
2332
|
+
nodes: {},
|
|
2333
|
+
counters: defaultAgentsMetaCounters()
|
|
2924
2334
|
};
|
|
2925
2335
|
}
|
|
2926
2336
|
function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
@@ -2930,125 +2340,36 @@ function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
|
2930
2340
|
ts: Date.now(),
|
|
2931
2341
|
schema_version: 1,
|
|
2932
2342
|
event_type: "reapply_completed",
|
|
2933
|
-
preserved_ledger: payload.preserved_ledger
|
|
2934
|
-
preserved_meta: payload.preserved_meta,
|
|
2935
|
-
rules_count: payload.rules_count
|
|
2343
|
+
preserved_ledger: payload.preserved_ledger
|
|
2936
2344
|
};
|
|
2937
2345
|
const line = `${JSON.stringify(event)}
|
|
2938
2346
|
`;
|
|
2939
2347
|
appendFileSync(eventsPath, line, "utf8");
|
|
2940
2348
|
}
|
|
2941
|
-
function
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
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;
|
|
2349
|
+
async function runBestEffort(step, fn) {
|
|
2350
|
+
try {
|
|
2351
|
+
return await fn();
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
return [
|
|
2354
|
+
{
|
|
2355
|
+
step,
|
|
2356
|
+
path: "",
|
|
2357
|
+
status: "error",
|
|
2358
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2359
|
+
}
|
|
2360
|
+
];
|
|
3030
2361
|
}
|
|
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
2362
|
}
|
|
3035
|
-
function
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
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 });
|
|
2363
|
+
async function runBestEffortSingle(step, fn) {
|
|
2364
|
+
try {
|
|
2365
|
+
return await fn();
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
return {
|
|
2368
|
+
step,
|
|
2369
|
+
path: "",
|
|
2370
|
+
status: "error",
|
|
2371
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2372
|
+
};
|
|
3052
2373
|
}
|
|
3053
2374
|
}
|
|
3054
2375
|
function formatInitStageHeader(message) {
|
|
@@ -3101,9 +2422,8 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
3101
2422
|
})
|
|
3102
2423
|
);
|
|
3103
2424
|
console.log(t("cli.init.plan.writes"));
|
|
3104
|
-
console.log(` - ${target}/.fabric/
|
|
2425
|
+
console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
|
|
3105
2426
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
3106
|
-
console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
|
|
3107
2427
|
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
3108
2428
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
3109
2429
|
}
|
|
@@ -3137,18 +2457,6 @@ function printInitCapabilitySummary(supports, stageResults, options) {
|
|
|
3137
2457
|
console.log(formatCapabilityTableRow(row, widths));
|
|
3138
2458
|
}
|
|
3139
2459
|
}
|
|
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
2460
|
function toCapabilityRow(support, stageResults, options) {
|
|
3153
2461
|
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
3154
2462
|
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.init.capabilities.status.na");
|
|
@@ -3209,18 +2517,6 @@ function formatCapabilityDivider(widths) {
|
|
|
3209
2517
|
}
|
|
3210
2518
|
function formatInitReasonMessage(supports) {
|
|
3211
2519
|
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
2520
|
if (detected.some((support) => support.capabilities.skill)) {
|
|
3225
2521
|
return t("cli.init.reason-message.installable-body");
|
|
3226
2522
|
}
|
|
@@ -3232,11 +2528,11 @@ function yesNoLabel(value) {
|
|
|
3232
2528
|
function formatInitPathAction(path, action) {
|
|
3233
2529
|
return t("cli.init.created-path", { label: labelForInitWriteAction(action), path });
|
|
3234
2530
|
}
|
|
3235
|
-
function
|
|
3236
|
-
if (action === "
|
|
2531
|
+
function formatAgentsMdAction(path, action) {
|
|
2532
|
+
if (action === "preserved") {
|
|
3237
2533
|
return t("cli.init.skipped-existing-path", { label: skippedLabel(), path });
|
|
3238
2534
|
}
|
|
3239
|
-
return
|
|
2535
|
+
return t("cli.init.created-path", { label: createdLabel(), path });
|
|
3240
2536
|
}
|
|
3241
2537
|
function labelForInitWriteAction(action) {
|
|
3242
2538
|
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
@@ -3253,9 +2549,6 @@ function nextLabel() {
|
|
|
3253
2549
|
function reasonLabel() {
|
|
3254
2550
|
return paint.human(t("cli.shared.reason"));
|
|
3255
2551
|
}
|
|
3256
|
-
function updatedLabel() {
|
|
3257
|
-
return paint.success(t("cli.shared.updated"));
|
|
3258
|
-
}
|
|
3259
2552
|
function overwrittenLabel() {
|
|
3260
2553
|
return paint.warn(t("cli.init.force.overwritten"));
|
|
3261
2554
|
}
|
|
@@ -3268,13 +2561,10 @@ function skippedStageLabel() {
|
|
|
3268
2561
|
function failedStageLabel() {
|
|
3269
2562
|
return paint.error(t("cli.init.stages.failed"));
|
|
3270
2563
|
}
|
|
3271
|
-
function
|
|
2564
|
+
function writeStderr2(message) {
|
|
3272
2565
|
process.stderr.write(`${message}
|
|
3273
2566
|
`);
|
|
3274
2567
|
}
|
|
3275
|
-
function sha256(content) {
|
|
3276
|
-
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
3277
|
-
}
|
|
3278
2568
|
export {
|
|
3279
2569
|
buildInitExecutionPlan,
|
|
3280
2570
|
buildInitFabricPlan,
|