@fenglimg/fabric-cli 1.8.0-rc.3 → 2.0.0-rc.10
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-AW3G7ZH5.js +576 -0
- package/dist/chunk-HQLEHH4O.js +321 -0
- package/dist/chunk-MT3R57VG.js +1000 -0
- package/dist/{chunk-QPCRBQ5Y.js → chunk-OBQU6NHO.js} +1 -52
- package/dist/chunk-WPTA74BY.js +184 -0
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/doctor-RILCO5OG.js +282 -0
- package/dist/hooks-NX32PPEN.js +13 -0
- package/dist/index.js +8 -5
- package/dist/{init-7EYGUJNJ.js → init-SAVH4SKE.js} +281 -1235
- package/dist/plan-context-hint-QMUPAXIB.js +98 -0
- package/dist/scan-ELSNCSKS.js +22 -0
- package/dist/{serve-466QXQ5Q.js → serve-NGLXHDYC.js} +8 -4
- package/dist/uninstall-DBAR2JBS.js +1082 -0
- 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 +1337 -0
- package/templates/hooks/knowledge-hint-broad.cjs +612 -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 +560 -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,760 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-
|
|
3
|
+
hooksCommand,
|
|
4
|
+
installHooks
|
|
5
|
+
} from "./chunk-WPTA74BY.js";
|
|
6
|
+
import {
|
|
7
|
+
detectFramework,
|
|
8
|
+
runInitScan
|
|
9
|
+
} from "./chunk-MT3R57VG.js";
|
|
10
|
+
import {
|
|
11
|
+
detectClientSupports,
|
|
12
|
+
resolveClients
|
|
13
|
+
} from "./chunk-HQLEHH4O.js";
|
|
14
|
+
import {
|
|
15
|
+
addArchiveSkillPointer,
|
|
16
|
+
installArchiveHintHook,
|
|
17
|
+
installFabricArchiveSkill,
|
|
18
|
+
installFabricImportSkill,
|
|
19
|
+
installFabricReviewSkill,
|
|
20
|
+
installKnowledgeHintBroadHook,
|
|
21
|
+
installKnowledgeHintNarrowHook,
|
|
22
|
+
mergeClaudeCodeHookConfig,
|
|
23
|
+
mergeCodexHookConfig,
|
|
24
|
+
mergeCursorHookConfig
|
|
25
|
+
} from "./chunk-AW3G7ZH5.js";
|
|
6
26
|
import {
|
|
7
|
-
createDebugLogger,
|
|
8
27
|
displayWidth,
|
|
9
28
|
padEnd,
|
|
10
|
-
paint
|
|
11
|
-
|
|
12
|
-
|
|
29
|
+
paint
|
|
30
|
+
} from "./chunk-WWNXR34K.js";
|
|
31
|
+
import {
|
|
32
|
+
createDebugLogger,
|
|
33
|
+
resolveDevMode
|
|
34
|
+
} from "./chunk-OBQU6NHO.js";
|
|
35
|
+
import {
|
|
13
36
|
t
|
|
14
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-6ICJICVU.js";
|
|
15
38
|
|
|
16
39
|
// src/commands/init.ts
|
|
17
|
-
import {
|
|
40
|
+
import { randomUUID } from "crypto";
|
|
41
|
+
import { homedir } from "os";
|
|
18
42
|
import * as childProcess from "child_process";
|
|
19
|
-
import { appendFileSync,
|
|
20
|
-
import { dirname
|
|
21
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
43
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
44
|
+
import { dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve3 } from "path";
|
|
22
45
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
46
|
+
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
47
|
+
import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
48
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
25
49
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
26
50
|
|
|
27
|
-
// src/
|
|
28
|
-
import { existsSync
|
|
29
|
-
import {
|
|
51
|
+
// src/commands/config.ts
|
|
52
|
+
import { existsSync } from "fs";
|
|
53
|
+
import { readFile } from "fs/promises";
|
|
54
|
+
import { resolve } from "path";
|
|
30
55
|
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
56
|
import { defineCommand } from "citty";
|
|
108
|
-
|
|
109
|
-
// 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
|
-
import { existsSync as existsSync3 } from "fs";
|
|
116
|
-
import { join as join3, resolve as resolve3 } from "path";
|
|
117
|
-
import { homedir as homedir2, platform } from "os";
|
|
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
|
-
};
|
|
234
|
-
|
|
235
|
-
// src/config/claude-code.ts
|
|
236
|
-
function getClaudeDesktopConfigPath() {
|
|
237
|
-
const os = platform();
|
|
238
|
-
if (os === "darwin") {
|
|
239
|
-
return join3(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
240
|
-
}
|
|
241
|
-
if (os === "win32") {
|
|
242
|
-
return join3(process.env.APPDATA ?? join3(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
243
|
-
}
|
|
244
|
-
return join3(homedir2(), ".config", "Claude", "claude_desktop_config.json");
|
|
245
|
-
}
|
|
246
|
-
var ClaudeCodeDesktopWriter = class {
|
|
247
|
-
clientKind = "ClaudeCodeDesktop";
|
|
248
|
-
configuredPath;
|
|
249
|
-
constructor(configuredPath) {
|
|
250
|
-
this.configuredPath = configuredPath;
|
|
251
|
-
}
|
|
252
|
-
async detect(_workspaceRoot, overridePath) {
|
|
253
|
-
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
254
|
-
return existsSync3(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
255
|
-
}
|
|
256
|
-
async write(serverPath, workspaceRoot, overridePath) {
|
|
257
|
-
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
258
|
-
if (configPath === null) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
await writeJsonClientConfig(configPath, {
|
|
262
|
-
command: process.execPath,
|
|
263
|
-
args: [serverPath]
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
// src/config/toml.ts
|
|
269
|
-
import { existsSync as existsSync4 } from "fs";
|
|
270
|
-
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
271
|
-
import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
|
|
272
|
-
import { homedir as homedir3 } from "os";
|
|
273
|
-
import { atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
274
|
-
function expandHome2(filePath) {
|
|
275
|
-
if (filePath === "~") {
|
|
276
|
-
return homedir3();
|
|
277
|
-
}
|
|
278
|
-
if (filePath.startsWith("~/")) {
|
|
279
|
-
return join4(homedir3(), filePath.slice(2));
|
|
280
|
-
}
|
|
281
|
-
return filePath;
|
|
282
|
-
}
|
|
283
|
-
function escapeTomlString(value) {
|
|
284
|
-
return JSON.stringify(value);
|
|
285
|
-
}
|
|
286
|
-
function serializeTomlStringArray(values) {
|
|
287
|
-
return `[${values.map((value) => escapeTomlString(value)).join(", ")}]`;
|
|
288
|
-
}
|
|
289
|
-
function serializeTomlInlineTable(values) {
|
|
290
|
-
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key} = ${escapeTomlString(value)}`);
|
|
291
|
-
return `{ ${entries.join(", ")} }`;
|
|
292
|
-
}
|
|
293
|
-
function serializeCodexServerBlock(serverName, serverEntry) {
|
|
294
|
-
const lines = [
|
|
295
|
-
`[mcp_servers.${serverName}]`,
|
|
296
|
-
`command = ${escapeTomlString(serverEntry.command)}`,
|
|
297
|
-
`args = ${serializeTomlStringArray(serverEntry.args)}`
|
|
298
|
-
];
|
|
299
|
-
if (serverEntry.env !== void 0 && Object.keys(serverEntry.env).length > 0) {
|
|
300
|
-
lines.push(`env = ${serializeTomlInlineTable(serverEntry.env)}`);
|
|
301
|
-
}
|
|
302
|
-
return `${lines.join("\n")}
|
|
303
|
-
`;
|
|
304
|
-
}
|
|
305
|
-
function trimTrailingBlankLines(value) {
|
|
306
|
-
return value.replace(/\s+$/u, "");
|
|
307
|
-
}
|
|
308
|
-
function upsertCodexServerBlock(rawConfig, serverName, serverEntry) {
|
|
309
|
-
const block = serializeCodexServerBlock(serverName, serverEntry);
|
|
310
|
-
const normalized = rawConfig.replace(/\r\n/g, "\n");
|
|
311
|
-
const legacyPattern = new RegExp(String.raw`\n?\[mcp\.servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`, "g");
|
|
312
|
-
const currentPattern = new RegExp(
|
|
313
|
-
String.raw`\n?\[mcp_servers\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n[\s\S]*?(?=\n\[[^\n]+\]\n|$)`,
|
|
314
|
-
"g"
|
|
315
|
-
);
|
|
316
|
-
const withoutLegacy = normalized.replace(legacyPattern, "");
|
|
317
|
-
const withoutExisting = withoutLegacy.replace(currentPattern, "");
|
|
318
|
-
const trimmed = trimTrailingBlankLines(withoutExisting);
|
|
319
|
-
if (trimmed.length === 0) {
|
|
320
|
-
return block;
|
|
321
|
-
}
|
|
322
|
-
return `${trimmed}
|
|
323
|
-
|
|
324
|
-
${block}`;
|
|
325
|
-
}
|
|
326
|
-
async function readTomlConfigText(configPath) {
|
|
327
|
-
try {
|
|
328
|
-
return await readFile2(configPath, "utf8");
|
|
329
|
-
} catch (error) {
|
|
330
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
331
|
-
return "";
|
|
332
|
-
}
|
|
333
|
-
throw error;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
var CodexTOMLConfigWriter = class {
|
|
337
|
-
clientKind = "CodexCLI";
|
|
338
|
-
configuredPath;
|
|
339
|
-
constructor(configuredPath) {
|
|
340
|
-
this.configuredPath = configuredPath;
|
|
341
|
-
}
|
|
342
|
-
async detect(_workspaceRoot, overridePath) {
|
|
343
|
-
const explicitPath = overridePath ?? this.configuredPath;
|
|
344
|
-
if (explicitPath !== void 0) {
|
|
345
|
-
return resolve4(expandHome2(explicitPath));
|
|
346
|
-
}
|
|
347
|
-
const codexDir = join4(homedir3(), ".codex");
|
|
348
|
-
return existsSync4(codexDir) ? resolve4(join4(codexDir, "config.toml")) : null;
|
|
349
|
-
}
|
|
350
|
-
async write(serverPath, workspaceRoot, overridePath) {
|
|
351
|
-
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
352
|
-
if (configPath === null) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
const rawConfig = await readTomlConfigText(configPath);
|
|
356
|
-
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
357
|
-
await mkdir2(dirname3(configPath), { recursive: true });
|
|
358
|
-
await atomicWriteText2(configPath, nextConfig);
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
// src/config/resolver.ts
|
|
363
|
-
import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
|
|
364
|
-
function hasExplicitPath(clientPaths, key) {
|
|
365
|
-
return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
|
|
366
|
-
}
|
|
367
|
-
function addIfDetected(writers, detected, createWriter, configuredPath) {
|
|
368
|
-
if (configuredPath !== void 0 || detected) {
|
|
369
|
-
writers.push(createWriter(configuredPath));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
373
|
-
const clientPaths = fabricConfig.clientPaths;
|
|
374
|
-
const writers = [];
|
|
375
|
-
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
376
|
-
addIfDetected(
|
|
377
|
-
writers,
|
|
378
|
-
existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude")),
|
|
379
|
-
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
380
|
-
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
381
|
-
);
|
|
382
|
-
addIfDetected(
|
|
383
|
-
writers,
|
|
384
|
-
existsSync5(getClaudeDesktopConfigPath()),
|
|
385
|
-
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
386
|
-
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
387
|
-
);
|
|
388
|
-
addIfDetected(
|
|
389
|
-
writers,
|
|
390
|
-
existsSync5(join5(workspaceRoot, ".cursor")),
|
|
391
|
-
(configuredPath) => new CursorWriter(configuredPath),
|
|
392
|
-
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
393
|
-
);
|
|
394
|
-
addIfDetected(
|
|
395
|
-
writers,
|
|
396
|
-
existsSync5(join5(homedir4(), ".codex")),
|
|
397
|
-
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
398
|
-
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
399
|
-
);
|
|
400
|
-
return writers;
|
|
401
|
-
}
|
|
402
|
-
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
403
|
-
const clientPaths = fabricConfig.clientPaths;
|
|
404
|
-
const claudeDetected = existsSync5(join5(homedir4(), ".claude")) || existsSync5(join5(workspaceRoot, ".claude"));
|
|
405
|
-
const claudeDesktopDetected = existsSync5(getClaudeDesktopConfigPath());
|
|
406
|
-
const cursorDetected = existsSync5(join5(workspaceRoot, ".cursor"));
|
|
407
|
-
const codexDetected = existsSync5(join5(homedir4(), ".codex"));
|
|
408
|
-
return [
|
|
409
|
-
{
|
|
410
|
-
clientKind: "ClaudeCodeCLI",
|
|
411
|
-
label: "Claude Code CLI",
|
|
412
|
-
detected: claudeDetected || hasExplicitPath(clientPaths, "claudeCodeCLI"),
|
|
413
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
414
|
-
configPath: "project .claude/settings.json",
|
|
415
|
-
capabilities: {
|
|
416
|
-
bootstrap: true,
|
|
417
|
-
mcp: true,
|
|
418
|
-
hook: true,
|
|
419
|
-
skill: true
|
|
420
|
-
},
|
|
421
|
-
installedCapabilities: {
|
|
422
|
-
hook: true,
|
|
423
|
-
skill: true
|
|
424
|
-
}
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
clientKind: "ClaudeCodeDesktop",
|
|
428
|
-
label: "Claude Code Desktop",
|
|
429
|
-
detected: claudeDesktopDetected || hasExplicitPath(clientPaths, "claudeCodeDesktop"),
|
|
430
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
431
|
-
configPath: "desktop Claude config",
|
|
432
|
-
capabilities: {
|
|
433
|
-
bootstrap: true,
|
|
434
|
-
mcp: true,
|
|
435
|
-
hook: false,
|
|
436
|
-
skill: false
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
clientKind: "Cursor",
|
|
441
|
-
label: "Cursor",
|
|
442
|
-
detected: cursorDetected || hasExplicitPath(clientPaths, "cursor"),
|
|
443
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
444
|
-
configPath: ".cursor/mcp.json",
|
|
445
|
-
capabilities: {
|
|
446
|
-
bootstrap: true,
|
|
447
|
-
mcp: true,
|
|
448
|
-
hook: false,
|
|
449
|
-
skill: false
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
clientKind: "CodexCLI",
|
|
454
|
-
label: "Codex CLI",
|
|
455
|
-
detected: codexDetected || hasExplicitPath(clientPaths, "codexCLI"),
|
|
456
|
-
bootstrapTargetPath: ".fabric/bootstrap/README.md",
|
|
457
|
-
configPath: "~/.codex/config.toml",
|
|
458
|
-
capabilities: {
|
|
459
|
-
bootstrap: true,
|
|
460
|
-
mcp: true,
|
|
461
|
-
hook: true,
|
|
462
|
-
skill: true
|
|
463
|
-
},
|
|
464
|
-
installedCapabilities: {
|
|
465
|
-
hook: existsSync5(join5(workspaceRoot, ".codex", "hooks.json")),
|
|
466
|
-
skill: existsSync5(join5(workspaceRoot, ".codex", "skills", "fabric-init", "SKILL.md"))
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
];
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// src/commands/bootstrap.ts
|
|
473
57
|
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
|
-
}
|
|
650
|
-
}
|
|
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
|
-
}
|
|
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
|
-
|
|
756
|
-
// src/commands/config.ts
|
|
757
|
-
var CLIENT_ALIASES2 = {
|
|
758
58
|
claude: "ClaudeCodeCLI",
|
|
759
59
|
claudecodecli: "ClaudeCodeCLI",
|
|
760
60
|
"claude-code-cli": "ClaudeCodeCLI",
|
|
@@ -767,14 +67,14 @@ var CLIENT_ALIASES2 = {
|
|
|
767
67
|
"codex-cli": "CodexCLI",
|
|
768
68
|
codex: "CodexCLI"
|
|
769
69
|
};
|
|
770
|
-
function
|
|
70
|
+
function parseClientFilter(value) {
|
|
771
71
|
if (value === void 0 || value.trim().length === 0) {
|
|
772
72
|
return null;
|
|
773
73
|
}
|
|
774
74
|
const clients = /* @__PURE__ */ new Set();
|
|
775
75
|
for (const rawClient of value.split(",")) {
|
|
776
76
|
const alias = rawClient.trim().toLowerCase();
|
|
777
|
-
const clientKind =
|
|
77
|
+
const clientKind = CLIENT_ALIASES[alias];
|
|
778
78
|
if (clientKind === void 0) {
|
|
779
79
|
throw new Error(t("cli.config.errors.unknown-client", { client: rawClient }));
|
|
780
80
|
}
|
|
@@ -783,11 +83,11 @@ function parseClientFilter2(value) {
|
|
|
783
83
|
return clients;
|
|
784
84
|
}
|
|
785
85
|
async function loadFabricConfig(workspaceRoot) {
|
|
786
|
-
const configPath =
|
|
787
|
-
if (!
|
|
86
|
+
const configPath = resolve(workspaceRoot, "fabric.config.json");
|
|
87
|
+
if (!existsSync(configPath)) {
|
|
788
88
|
return {};
|
|
789
89
|
}
|
|
790
|
-
const parsed = JSON.parse(await
|
|
90
|
+
const parsed = JSON.parse(await readFile(configPath, "utf8"));
|
|
791
91
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
792
92
|
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
793
93
|
}
|
|
@@ -795,21 +95,21 @@ async function loadFabricConfig(workspaceRoot) {
|
|
|
795
95
|
}
|
|
796
96
|
function resolveServerPath(override) {
|
|
797
97
|
if (override) return override;
|
|
798
|
-
if (process.env.FAB_SERVER_PATH) return
|
|
799
|
-
return
|
|
98
|
+
if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
|
|
99
|
+
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
800
100
|
}
|
|
801
|
-
function
|
|
101
|
+
function writeStderr(message) {
|
|
802
102
|
process.stderr.write(`${message}
|
|
803
103
|
`);
|
|
804
104
|
}
|
|
805
|
-
var configCmd =
|
|
105
|
+
var configCmd = defineCommand({
|
|
806
106
|
meta: {
|
|
807
107
|
name: "config",
|
|
808
108
|
description: t("cli.config.description")
|
|
809
109
|
},
|
|
810
110
|
subCommands: {
|
|
811
111
|
hooks: hooksCommand,
|
|
812
|
-
install:
|
|
112
|
+
install: defineCommand({
|
|
813
113
|
meta: {
|
|
814
114
|
name: "install",
|
|
815
115
|
description: t("cli.config.install.description")
|
|
@@ -826,26 +126,26 @@ var configCmd = defineCommand3({
|
|
|
826
126
|
}
|
|
827
127
|
},
|
|
828
128
|
async run({ args }) {
|
|
829
|
-
const selectedClients =
|
|
129
|
+
const selectedClients = parseClientFilter(args.clients);
|
|
830
130
|
const result = await installMcpClients(process.cwd(), {
|
|
831
131
|
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
832
132
|
dryRun: args["dry-run"]
|
|
833
133
|
});
|
|
834
134
|
if (result.details.length === 0) {
|
|
835
|
-
|
|
135
|
+
writeStderr(t("cli.config.install.no-configs"));
|
|
836
136
|
return;
|
|
837
137
|
}
|
|
838
138
|
for (const detail of result.details) {
|
|
839
139
|
if (detail.action === "skipped") {
|
|
840
|
-
|
|
140
|
+
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
841
141
|
continue;
|
|
842
142
|
}
|
|
843
143
|
if (detail.action === "dry-run" && detail.path !== null) {
|
|
844
|
-
|
|
144
|
+
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
845
145
|
continue;
|
|
846
146
|
}
|
|
847
147
|
if (detail.path !== null) {
|
|
848
|
-
|
|
148
|
+
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
849
149
|
}
|
|
850
150
|
}
|
|
851
151
|
}
|
|
@@ -853,7 +153,7 @@ var configCmd = defineCommand3({
|
|
|
853
153
|
}
|
|
854
154
|
});
|
|
855
155
|
async function installMcpClients(target, options = {}) {
|
|
856
|
-
const workspaceRoot =
|
|
156
|
+
const workspaceRoot = resolve(target);
|
|
857
157
|
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
858
158
|
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
859
159
|
const serverPath = resolveServerPath(options.localServerPath);
|
|
@@ -884,9 +184,9 @@ async function installMcpClients(target, options = {}) {
|
|
|
884
184
|
|
|
885
185
|
// src/scanner/forensic.ts
|
|
886
186
|
import { execFileSync } from "child_process";
|
|
887
|
-
import { existsSync as
|
|
187
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
|
|
888
188
|
import { createRequire } from "module";
|
|
889
|
-
import { basename, extname, isAbsolute
|
|
189
|
+
import { basename, extname, isAbsolute, join, posix, relative, resolve as resolve2, sep } from "path";
|
|
890
190
|
import {
|
|
891
191
|
forensicReportSchema
|
|
892
192
|
} from "@fenglimg/fabric-shared";
|
|
@@ -972,7 +272,7 @@ var parserInitPromise = null;
|
|
|
972
272
|
var languagePromiseByKind = {};
|
|
973
273
|
var parserBundlePromiseByKind = {};
|
|
974
274
|
async function buildForensicReport(targetInput) {
|
|
975
|
-
const target =
|
|
275
|
+
const target = normalizeTarget(targetInput);
|
|
976
276
|
const framework = detectFramework(target);
|
|
977
277
|
const topology = buildTopology(target);
|
|
978
278
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -1008,11 +308,11 @@ async function buildForensicReport(targetInput) {
|
|
|
1008
308
|
}
|
|
1009
309
|
return validation.data;
|
|
1010
310
|
}
|
|
1011
|
-
function
|
|
1012
|
-
return
|
|
311
|
+
function normalizeTarget(targetInput) {
|
|
312
|
+
return isAbsolute(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
|
|
1013
313
|
}
|
|
1014
314
|
function buildTopology(root) {
|
|
1015
|
-
|
|
315
|
+
assertExistingDirectory(root);
|
|
1016
316
|
const byExt = {};
|
|
1017
317
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
1018
318
|
const files = [];
|
|
@@ -1025,7 +325,7 @@ function buildTopology(root) {
|
|
|
1025
325
|
continue;
|
|
1026
326
|
}
|
|
1027
327
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1028
|
-
const absolutePath =
|
|
328
|
+
const absolutePath = join(current, entry.name);
|
|
1029
329
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
1030
330
|
if (relativePath.length === 0) {
|
|
1031
331
|
continue;
|
|
@@ -1045,7 +345,7 @@ function buildTopology(root) {
|
|
|
1045
345
|
if (!entry.isFile()) {
|
|
1046
346
|
continue;
|
|
1047
347
|
}
|
|
1048
|
-
const stats =
|
|
348
|
+
const stats = statSync(absolutePath);
|
|
1049
349
|
const extension = extname(entry.name) || "[none]";
|
|
1050
350
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
1051
351
|
totalFiles += 1;
|
|
@@ -1063,8 +363,8 @@ function buildTopology(root) {
|
|
|
1063
363
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
1064
364
|
};
|
|
1065
365
|
}
|
|
1066
|
-
function
|
|
1067
|
-
if (!
|
|
366
|
+
function assertExistingDirectory(target) {
|
|
367
|
+
if (!existsSync2(target) || !statSync(target).isDirectory()) {
|
|
1068
368
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
1069
369
|
}
|
|
1070
370
|
}
|
|
@@ -1113,7 +413,7 @@ function getEntryPointReason(relativePath) {
|
|
|
1113
413
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
1114
414
|
const samples = [];
|
|
1115
415
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
1116
|
-
const absolutePath =
|
|
416
|
+
const absolutePath = join(target, ...entryPoint.path.split("/"));
|
|
1117
417
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
1118
418
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
1119
419
|
frameworkKind,
|
|
@@ -1133,7 +433,7 @@ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, pa
|
|
|
1133
433
|
}
|
|
1134
434
|
function readFirstLines(path, lineLimit) {
|
|
1135
435
|
try {
|
|
1136
|
-
const lines =
|
|
436
|
+
const lines = readFileSync(path, "utf8").split(/\r?\n/);
|
|
1137
437
|
if (lines.at(-1) === "") {
|
|
1138
438
|
lines.pop();
|
|
1139
439
|
}
|
|
@@ -1150,12 +450,12 @@ function readFirstLines(path, lineLimit) {
|
|
|
1150
450
|
}
|
|
1151
451
|
}
|
|
1152
452
|
function readPackageDependencies(target) {
|
|
1153
|
-
const packageJsonPath =
|
|
1154
|
-
if (!
|
|
453
|
+
const packageJsonPath = join(target, "package.json");
|
|
454
|
+
if (!existsSync2(packageJsonPath)) {
|
|
1155
455
|
return /* @__PURE__ */ new Map();
|
|
1156
456
|
}
|
|
1157
457
|
try {
|
|
1158
|
-
const packageJson = JSON.parse(
|
|
458
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1159
459
|
return new Map([
|
|
1160
460
|
...Object.entries(packageJson.dependencies ?? {}),
|
|
1161
461
|
...Object.entries(packageJson.devDependencies ?? {}),
|
|
@@ -1491,16 +791,16 @@ function scoreFrameworkConfidence(input) {
|
|
|
1491
791
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1492
792
|
}
|
|
1493
793
|
function readReadmeInfo(target) {
|
|
1494
|
-
const readmePath =
|
|
1495
|
-
const hasContributing =
|
|
1496
|
-
if (!
|
|
794
|
+
const readmePath = join(target, "README.md");
|
|
795
|
+
const hasContributing = existsSync2(join(target, "CONTRIBUTING.md"));
|
|
796
|
+
if (!existsSync2(readmePath)) {
|
|
1497
797
|
return {
|
|
1498
798
|
quality: "missing",
|
|
1499
799
|
line_count: 0,
|
|
1500
800
|
has_contributing: hasContributing
|
|
1501
801
|
};
|
|
1502
802
|
}
|
|
1503
|
-
const readme =
|
|
803
|
+
const readme = readFileSync(readmePath, "utf8");
|
|
1504
804
|
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
1505
805
|
return {
|
|
1506
806
|
quality: wordCount >= 200 ? "ok" : "stub",
|
|
@@ -1733,7 +1033,7 @@ function buildDomainAssertion(codeSamples) {
|
|
|
1733
1033
|
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
1734
1034
|
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
1735
1035
|
]),
|
|
1736
|
-
proposedRule: "Preserve domain-specific module names when
|
|
1036
|
+
proposedRule: "Preserve domain-specific module names when authoring knowledge entries that reference these modules."
|
|
1737
1037
|
});
|
|
1738
1038
|
}
|
|
1739
1039
|
function createAssertion(input) {
|
|
@@ -1978,10 +1278,10 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1978
1278
|
return recommendations;
|
|
1979
1279
|
}
|
|
1980
1280
|
function readProjectName(target) {
|
|
1981
|
-
const packageJsonPath =
|
|
1982
|
-
if (
|
|
1281
|
+
const packageJsonPath = join(target, "package.json");
|
|
1282
|
+
if (existsSync2(packageJsonPath)) {
|
|
1983
1283
|
try {
|
|
1984
|
-
const packageJson = JSON.parse(
|
|
1284
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1985
1285
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1986
1286
|
return packageJson.name;
|
|
1987
1287
|
}
|
|
@@ -1992,7 +1292,7 @@ function readProjectName(target) {
|
|
|
1992
1292
|
return basename(target);
|
|
1993
1293
|
}
|
|
1994
1294
|
function getCliVersion() {
|
|
1995
|
-
return true ? "
|
|
1295
|
+
return true ? "2.0.0-rc.10" : "unknown";
|
|
1996
1296
|
}
|
|
1997
1297
|
function sortRecord(record) {
|
|
1998
1298
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -2002,18 +1302,19 @@ function toPosixPath(path) {
|
|
|
2002
1302
|
}
|
|
2003
1303
|
|
|
2004
1304
|
// src/commands/init.ts
|
|
2005
|
-
var
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1305
|
+
var AGENTS_MD_DEFAULT_CONTENT = `# Project Knowledge
|
|
1306
|
+
|
|
1307
|
+
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
1308
|
+
|
|
1309
|
+
Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
|
|
1310
|
+
Run \`fabric doctor\` to verify state.
|
|
1311
|
+
|
|
1312
|
+
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1313
|
+
`;
|
|
1314
|
+
var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
2014
1315
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
2015
1316
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
2016
|
-
var initCommand =
|
|
1317
|
+
var initCommand = defineCommand2({
|
|
2017
1318
|
meta: {
|
|
2018
1319
|
name: "init",
|
|
2019
1320
|
description: t("cli.init.description")
|
|
@@ -2095,13 +1396,13 @@ async function runInitCommand(args) {
|
|
|
2095
1396
|
logger(step);
|
|
2096
1397
|
}
|
|
2097
1398
|
if (intent.options.planOnly) {
|
|
2098
|
-
|
|
1399
|
+
writeStderr2(t("cli.init.compat.plan"));
|
|
2099
1400
|
}
|
|
2100
1401
|
if (args.interactive === false) {
|
|
2101
|
-
|
|
1402
|
+
writeStderr2(t("cli.init.compat.interactive"));
|
|
2102
1403
|
}
|
|
2103
1404
|
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
2104
|
-
|
|
1405
|
+
writeStderr2(t("cli.init.compat.legacy-stage-flags"));
|
|
2105
1406
|
}
|
|
2106
1407
|
const supports = detectClientSupports(intent.target);
|
|
2107
1408
|
const basePlan = await buildInitExecutionPlan({
|
|
@@ -2117,10 +1418,47 @@ async function runInitCommand(args) {
|
|
|
2117
1418
|
process.exitCode = 130;
|
|
2118
1419
|
return;
|
|
2119
1420
|
}
|
|
2120
|
-
|
|
1421
|
+
const result = await executeInitExecutionPlan(plan);
|
|
1422
|
+
if (!intent.options.planOnly) {
|
|
1423
|
+
console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
|
|
1424
|
+
}
|
|
1425
|
+
return result;
|
|
1426
|
+
}
|
|
1427
|
+
function writeDefaultFabricConfig(fabricDir) {
|
|
1428
|
+
const target = join2(fabricDir, "fabric-config.json");
|
|
1429
|
+
if (existsSync3(target)) return;
|
|
1430
|
+
const FABRIC_CONFIG_DEFAULTS = {
|
|
1431
|
+
// Scan/import language policy. `match-existing` lets init resolve the
|
|
1432
|
+
// effective language from project content; explicit `zh-CN` / `en`
|
|
1433
|
+
// lock the policy. See packages/shared/src/schemas/fabric-config.ts.
|
|
1434
|
+
knowledge_language: "match-existing",
|
|
1435
|
+
// fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
|
|
1436
|
+
// since last knowledge_proposed event.
|
|
1437
|
+
archive_hint_hours: 24,
|
|
1438
|
+
// fabric-hint Stop hook cooldown after ANY signal fires, in hours.
|
|
1439
|
+
archive_hint_cooldown_hours: 12,
|
|
1440
|
+
// fabric-hint Stop hook Signal B (review): pending-count cutoff.
|
|
1441
|
+
review_hint_pending_count: 10,
|
|
1442
|
+
// fabric-hint Stop hook Signal B (review): pending-age cutoff in days.
|
|
1443
|
+
review_hint_pending_age_days: 7,
|
|
1444
|
+
// fabric-hint Stop hook Signal D (maintenance): days since last doctor.
|
|
1445
|
+
maintenance_hint_days: 14,
|
|
1446
|
+
// fabric-hint Stop hook Signal D (maintenance): cooldown between
|
|
1447
|
+
// reminders, in days.
|
|
1448
|
+
maintenance_hint_cooldown_days: 7,
|
|
1449
|
+
// fabric-hint Stop hook Signal A (archive): edit-count branch threshold;
|
|
1450
|
+
// PreToolUse fires recorded in .fabric/.cache/edit-counter since the
|
|
1451
|
+
// last knowledge_proposed event.
|
|
1452
|
+
archive_edit_threshold: 20,
|
|
1453
|
+
// fabric-hint Stop hook Signal C (import) + doctor lint #22: canonical
|
|
1454
|
+
// knowledge node count below this value flags an underseeded workspace.
|
|
1455
|
+
underseed_node_threshold: 10
|
|
1456
|
+
};
|
|
1457
|
+
mkdirSync(fabricDir, { recursive: true });
|
|
1458
|
+
writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
|
|
2121
1459
|
}
|
|
2122
1460
|
function resolveInitCliIntent(args, targetInput) {
|
|
2123
|
-
const target =
|
|
1461
|
+
const target = normalizeTarget2(targetInput);
|
|
2124
1462
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
2125
1463
|
const claudeMcpScope = resolveClaudeMcpScope(args.scope);
|
|
2126
1464
|
const terminalInteractive = isInteractiveInit();
|
|
@@ -2150,7 +1488,7 @@ function resolveClaudeMcpScope(raw) {
|
|
|
2150
1488
|
if (raw === "user") {
|
|
2151
1489
|
return "user";
|
|
2152
1490
|
}
|
|
2153
|
-
|
|
1491
|
+
writeStderr2(t("cli.init.mcp.scope.invalid", { value: raw }));
|
|
2154
1492
|
return "project";
|
|
2155
1493
|
}
|
|
2156
1494
|
async function buildInitExecutionPlan(input) {
|
|
@@ -2190,10 +1528,10 @@ async function buildInitExecutionPlan(input) {
|
|
|
2190
1528
|
}
|
|
2191
1529
|
async function executeInitExecutionPlan(plan) {
|
|
2192
1530
|
if (plan.options.force) {
|
|
2193
|
-
|
|
1531
|
+
writeStderr2(t("cli.init.force.warning", { path: plan.target }));
|
|
2194
1532
|
}
|
|
2195
1533
|
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
2196
|
-
|
|
1534
|
+
writeStderr2(formatInitModeBanner(plan.options));
|
|
2197
1535
|
}
|
|
2198
1536
|
if (plan.interactive) {
|
|
2199
1537
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
@@ -2238,98 +1576,78 @@ async function executeInitExecutionPlan(plan) {
|
|
|
2238
1576
|
finalSupports
|
|
2239
1577
|
};
|
|
2240
1578
|
}
|
|
1579
|
+
var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
|
|
1580
|
+
function resolvePersonalFabricRoot() {
|
|
1581
|
+
return process.env.FABRIC_HOME ?? homedir();
|
|
1582
|
+
}
|
|
2241
1583
|
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");
|
|
1584
|
+
assertExistingDirectory2(target);
|
|
1585
|
+
const fabricDir = join2(target, ".fabric");
|
|
1586
|
+
const agentsMdPath = join2(target, "AGENTS.md");
|
|
1587
|
+
const agentsMdAction = existsSync3(agentsMdPath) ? "preserved" : "created";
|
|
1588
|
+
const knowledgeDir = join2(fabricDir, "knowledge");
|
|
1589
|
+
const personalKnowledgeDir = join2(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1590
|
+
const forensicPath = join2(fabricDir, "forensic.json");
|
|
1591
|
+
const eventsPath = join2(fabricDir, "events.jsonl");
|
|
1592
|
+
const metaPath = join2(fabricDir, "agents.meta.json");
|
|
2257
1593
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
2258
|
-
const
|
|
1594
|
+
const knowledgeDirAction = existsSync3(knowledgeDir) ? "overwritten" : "created";
|
|
2259
1595
|
const metaAction = planFreshPath(metaPath, options);
|
|
2260
|
-
const taxonomyAction = planFreshPath(taxonomyPath, options);
|
|
2261
1596
|
const eventsAction = planFreshPath(eventsPath, options);
|
|
2262
1597
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
2263
1598
|
const forensicReport = await buildForensicReport(target);
|
|
2264
|
-
const
|
|
2265
|
-
const taxonomyContent = buildInitialTaxonomyMarkdown(forensicReport);
|
|
2266
|
-
const bootstrapHash = sha256(bootstrapContent);
|
|
2267
|
-
const meta = createInitialMeta(bootstrapHash);
|
|
1599
|
+
const meta = createInitialMeta();
|
|
2268
1600
|
return {
|
|
2269
1601
|
target,
|
|
2270
1602
|
options,
|
|
2271
1603
|
fabricDir,
|
|
2272
1604
|
replaceFabricDir,
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
1605
|
+
agentsMdPath,
|
|
1606
|
+
agentsMdAction,
|
|
1607
|
+
knowledgeDir,
|
|
1608
|
+
knowledgeDirAction,
|
|
1609
|
+
personalKnowledgeDir,
|
|
2276
1610
|
metaPath,
|
|
2277
1611
|
metaAction,
|
|
2278
1612
|
meta,
|
|
2279
|
-
taxonomyPath,
|
|
2280
|
-
taxonomyAction,
|
|
2281
|
-
taxonomyContent,
|
|
2282
|
-
rulesDir,
|
|
2283
1613
|
eventsPath,
|
|
2284
1614
|
eventsAction,
|
|
2285
1615
|
forensicPath,
|
|
2286
1616
|
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)
|
|
1617
|
+
forensicReport
|
|
2310
1618
|
};
|
|
2311
1619
|
}
|
|
2312
1620
|
async function executeInitFabricPlan(plan) {
|
|
2313
1621
|
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
1622
|
if (plan.replaceFabricDir) {
|
|
2317
1623
|
rmSync(plan.fabricDir, { force: true });
|
|
2318
1624
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
1625
|
+
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1626
|
+
writeDefaultFabricConfig(plan.fabricDir);
|
|
1627
|
+
if (plan.agentsMdAction === "created" && !existsSync3(plan.agentsMdPath)) {
|
|
1628
|
+
await atomicWriteText(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1629
|
+
}
|
|
1630
|
+
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1631
|
+
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1632
|
+
const teamSubDir = join2(plan.knowledgeDir, sub);
|
|
1633
|
+
mkdirSync(teamSubDir, { recursive: true });
|
|
1634
|
+
const teamGitkeep = join2(teamSubDir, ".gitkeep");
|
|
1635
|
+
if (!existsSync3(teamGitkeep)) {
|
|
1636
|
+
writeFileSync(teamGitkeep, "", "utf8");
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
try {
|
|
1640
|
+
mkdirSync(plan.personalKnowledgeDir, { recursive: true });
|
|
1641
|
+
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1642
|
+
mkdirSync(join2(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1643
|
+
}
|
|
1644
|
+
} catch {
|
|
1645
|
+
}
|
|
1646
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1647
|
+
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
2330
1648
|
if (isReapply) {
|
|
2331
|
-
if (!
|
|
2332
|
-
|
|
1649
|
+
if (!existsSync3(plan.eventsPath)) {
|
|
1650
|
+
mkdirSync(dirname(plan.eventsPath), { recursive: true });
|
|
2333
1651
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2334
1652
|
}
|
|
2335
1653
|
} else {
|
|
@@ -2337,46 +1655,33 @@ async function executeInitFabricPlan(plan) {
|
|
|
2337
1655
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2338
1656
|
}
|
|
2339
1657
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
2340
|
-
await
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
1658
|
+
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
1659
|
+
if (!plan.options?.reapply) {
|
|
1660
|
+
try {
|
|
1661
|
+
await runInitScan(plan.target, { source: "init" });
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
writeStderr2(
|
|
1664
|
+
`[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
2348
1668
|
if (isReapply) {
|
|
2349
1669
|
appendReapplyLedgerEvent(plan.eventsPath, {
|
|
2350
|
-
preserved_ledger: true
|
|
2351
|
-
preserved_meta: preserveMeta,
|
|
2352
|
-
rules_count: existingRules.length
|
|
1670
|
+
preserved_ledger: true
|
|
2353
1671
|
});
|
|
2354
1672
|
}
|
|
2355
1673
|
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
1674
|
+
agentsMdPath: plan.agentsMdPath,
|
|
1675
|
+
agentsMdAction: plan.agentsMdAction,
|
|
1676
|
+
knowledgeDir: plan.knowledgeDir,
|
|
1677
|
+
knowledgeDirAction: plan.knowledgeDirAction,
|
|
1678
|
+
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2358
1679
|
metaPath: plan.metaPath,
|
|
2359
1680
|
metaAction: plan.metaAction,
|
|
2360
|
-
taxonomyPath: plan.taxonomyPath,
|
|
2361
|
-
taxonomyAction: plan.taxonomyAction,
|
|
2362
1681
|
eventsPath: plan.eventsPath,
|
|
2363
1682
|
eventsAction: plan.eventsAction,
|
|
2364
1683
|
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
|
|
1684
|
+
forensicAction: plan.forensicAction
|
|
2380
1685
|
};
|
|
2381
1686
|
}
|
|
2382
1687
|
async function initFabric(target, options) {
|
|
@@ -2421,22 +1726,11 @@ function exhaustiveInitStagePlan(value) {
|
|
|
2421
1726
|
throw new Error(`Unsupported init stage plan: ${JSON.stringify(value)}`);
|
|
2422
1727
|
}
|
|
2423
1728
|
function printInitScaffoldResult(created) {
|
|
2424
|
-
console.log(
|
|
1729
|
+
console.log(formatAgentsMdAction(created.agentsMdPath, created.agentsMdAction));
|
|
1730
|
+
console.log(formatInitPathAction(created.knowledgeDir, created.knowledgeDirAction));
|
|
2425
1731
|
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
2426
|
-
console.log(formatInitPathAction(created.taxonomyPath, created.taxonomyAction));
|
|
2427
1732
|
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
2428
1733
|
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
1734
|
}
|
|
2441
1735
|
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
2442
1736
|
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
@@ -2470,30 +1764,17 @@ function printInitPlanPreview(plan) {
|
|
|
2470
1764
|
}
|
|
2471
1765
|
function buildPlanOnlyScaffoldResult(plan) {
|
|
2472
1766
|
return {
|
|
2473
|
-
|
|
2474
|
-
|
|
1767
|
+
agentsMdPath: plan.agentsMdPath,
|
|
1768
|
+
agentsMdAction: plan.agentsMdAction,
|
|
1769
|
+
knowledgeDir: plan.knowledgeDir,
|
|
1770
|
+
knowledgeDirAction: plan.knowledgeDirAction,
|
|
1771
|
+
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2475
1772
|
metaPath: plan.metaPath,
|
|
2476
1773
|
metaAction: plan.metaAction,
|
|
2477
|
-
taxonomyPath: plan.taxonomyPath,
|
|
2478
|
-
taxonomyAction: plan.taxonomyAction,
|
|
2479
1774
|
eventsPath: plan.eventsPath,
|
|
2480
1775
|
eventsAction: plan.eventsAction,
|
|
2481
1776
|
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
|
|
1777
|
+
forensicAction: plan.forensicAction
|
|
2497
1778
|
};
|
|
2498
1779
|
}
|
|
2499
1780
|
async function executeInitStagePlan(plan, stageName) {
|
|
@@ -2508,25 +1789,38 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2508
1789
|
try {
|
|
2509
1790
|
switch (stage.name) {
|
|
2510
1791
|
case "bootstrap": {
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
1792
|
+
const installResults = [];
|
|
1793
|
+
installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
|
|
1794
|
+
installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
|
|
1795
|
+
installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
|
|
1796
|
+
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
1797
|
+
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
1798
|
+
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
1799
|
+
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
1800
|
+
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
1801
|
+
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
1802
|
+
installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
|
|
1803
|
+
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
1804
|
+
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
1805
|
+
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
1806
|
+
for (const result of installResults) {
|
|
1807
|
+
if (result.status === "error") {
|
|
1808
|
+
writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
1809
|
+
}
|
|
2515
1810
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
);
|
|
1811
|
+
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
1812
|
+
console.log(formatInitStageResult("bootstrap", "completed", installedCount, skippedCount, note2));
|
|
2519
1813
|
return { name: "bootstrap", disposition: "ran" };
|
|
2520
1814
|
}
|
|
2521
1815
|
case "mcp": {
|
|
2522
1816
|
if (stage.installMode === "local") {
|
|
2523
1817
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
2524
|
-
|
|
2525
|
-
|
|
1818
|
+
writeStderr2(t("cli.init.mcp.install.local"));
|
|
1819
|
+
writeStderr2(t("cli.init.mcp.local.installing", { manager }));
|
|
2526
1820
|
installLocalFabricServer(plan.target, manager);
|
|
2527
|
-
|
|
1821
|
+
writeStderr2(t("cli.init.mcp.local.installed"));
|
|
2528
1822
|
} else {
|
|
2529
|
-
|
|
1823
|
+
writeStderr2(t("cli.init.mcp.install.global"));
|
|
2530
1824
|
}
|
|
2531
1825
|
const result = await installMcpClients(plan.target, {
|
|
2532
1826
|
force: plan.options.force,
|
|
@@ -2549,15 +1843,15 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2549
1843
|
return exhaustiveInitStagePlan(stage);
|
|
2550
1844
|
}
|
|
2551
1845
|
} catch (error) {
|
|
2552
|
-
|
|
1846
|
+
writeStderr2(formatInitStageFailure(stageName, error));
|
|
2553
1847
|
return { name: stageName, disposition: "failed" };
|
|
2554
1848
|
}
|
|
2555
1849
|
}
|
|
2556
1850
|
function shouldReplaceWritableDirectory(path, options) {
|
|
2557
|
-
if (!
|
|
1851
|
+
if (!existsSync3(path)) {
|
|
2558
1852
|
return false;
|
|
2559
1853
|
}
|
|
2560
|
-
if (
|
|
1854
|
+
if (statSync2(path).isDirectory()) {
|
|
2561
1855
|
return false;
|
|
2562
1856
|
}
|
|
2563
1857
|
if (!options?.force) {
|
|
@@ -2566,7 +1860,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
2566
1860
|
return true;
|
|
2567
1861
|
}
|
|
2568
1862
|
function planFreshPath(path, options) {
|
|
2569
|
-
if (!
|
|
1863
|
+
if (!existsSync3(path)) {
|
|
2570
1864
|
return "created";
|
|
2571
1865
|
}
|
|
2572
1866
|
if (!options?.force) {
|
|
@@ -2575,131 +1869,11 @@ function planFreshPath(path, options) {
|
|
|
2575
1869
|
return "overwritten";
|
|
2576
1870
|
}
|
|
2577
1871
|
function preparePlannedPath(path, action) {
|
|
2578
|
-
|
|
2579
|
-
if (action === "overwritten" &&
|
|
1872
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1873
|
+
if (action === "overwritten" && existsSync3(path)) {
|
|
2580
1874
|
rmSync(path, { recursive: true, force: true });
|
|
2581
1875
|
}
|
|
2582
1876
|
}
|
|
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
1877
|
function createDefaultInitWizardAdapter() {
|
|
2704
1878
|
return {
|
|
2705
1879
|
async run(context) {
|
|
@@ -2871,23 +2045,23 @@ function formatInitModeBadge(options) {
|
|
|
2871
2045
|
}
|
|
2872
2046
|
return t("cli.init.mode.badge.default");
|
|
2873
2047
|
}
|
|
2874
|
-
function
|
|
2875
|
-
return
|
|
2048
|
+
function normalizeTarget2(targetInput) {
|
|
2049
|
+
return isAbsolute2(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
|
|
2876
2050
|
}
|
|
2877
|
-
function
|
|
2878
|
-
if (!
|
|
2051
|
+
function assertExistingDirectory2(target) {
|
|
2052
|
+
if (!existsSync3(target) || !statSync2(target).isDirectory()) {
|
|
2879
2053
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2880
2054
|
}
|
|
2881
2055
|
}
|
|
2882
2056
|
function detectPackageManager(cwd) {
|
|
2883
|
-
const workspaceRoot =
|
|
2884
|
-
if (
|
|
2057
|
+
const workspaceRoot = resolve3(cwd);
|
|
2058
|
+
if (existsSync3(join2(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2885
2059
|
return "pnpm";
|
|
2886
2060
|
}
|
|
2887
|
-
if (
|
|
2061
|
+
if (existsSync3(join2(workspaceRoot, "yarn.lock"))) {
|
|
2888
2062
|
return "yarn";
|
|
2889
2063
|
}
|
|
2890
|
-
if (
|
|
2064
|
+
if (existsSync3(join2(workspaceRoot, "package-lock.json"))) {
|
|
2891
2065
|
return "npm";
|
|
2892
2066
|
}
|
|
2893
2067
|
return "npm";
|
|
@@ -2896,7 +2070,7 @@ function resolveMcpInstallMode(rawMode) {
|
|
|
2896
2070
|
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
2897
2071
|
return rawMode ?? "global";
|
|
2898
2072
|
}
|
|
2899
|
-
|
|
2073
|
+
writeStderr2(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
2900
2074
|
return "global";
|
|
2901
2075
|
}
|
|
2902
2076
|
function installLocalFabricServer(target, manager) {
|
|
@@ -2907,20 +2081,11 @@ function installLocalFabricServer(target, manager) {
|
|
|
2907
2081
|
shell: process.platform === "win32"
|
|
2908
2082
|
});
|
|
2909
2083
|
}
|
|
2910
|
-
function createInitialMeta(
|
|
2084
|
+
function createInitialMeta() {
|
|
2911
2085
|
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
|
-
}
|
|
2086
|
+
revision: "sha256:initial",
|
|
2087
|
+
nodes: {},
|
|
2088
|
+
counters: defaultAgentsMetaCounters()
|
|
2924
2089
|
};
|
|
2925
2090
|
}
|
|
2926
2091
|
function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
@@ -2930,125 +2095,36 @@ function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
|
2930
2095
|
ts: Date.now(),
|
|
2931
2096
|
schema_version: 1,
|
|
2932
2097
|
event_type: "reapply_completed",
|
|
2933
|
-
preserved_ledger: payload.preserved_ledger
|
|
2934
|
-
preserved_meta: payload.preserved_meta,
|
|
2935
|
-
rules_count: payload.rules_count
|
|
2098
|
+
preserved_ledger: payload.preserved_ledger
|
|
2936
2099
|
};
|
|
2937
2100
|
const line = `${JSON.stringify(event)}
|
|
2938
2101
|
`;
|
|
2939
2102
|
appendFileSync(eventsPath, line, "utf8");
|
|
2940
2103
|
}
|
|
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;
|
|
2104
|
+
async function runBestEffort(step, fn) {
|
|
2105
|
+
try {
|
|
2106
|
+
return await fn();
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
return [
|
|
2109
|
+
{
|
|
2110
|
+
step,
|
|
2111
|
+
path: "",
|
|
2112
|
+
status: "error",
|
|
2113
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2114
|
+
}
|
|
2115
|
+
];
|
|
3030
2116
|
}
|
|
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
2117
|
}
|
|
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 });
|
|
2118
|
+
async function runBestEffortSingle(step, fn) {
|
|
2119
|
+
try {
|
|
2120
|
+
return await fn();
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
return {
|
|
2123
|
+
step,
|
|
2124
|
+
path: "",
|
|
2125
|
+
status: "error",
|
|
2126
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2127
|
+
};
|
|
3052
2128
|
}
|
|
3053
2129
|
}
|
|
3054
2130
|
function formatInitStageHeader(message) {
|
|
@@ -3101,11 +2177,11 @@ function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
|
3101
2177
|
})
|
|
3102
2178
|
);
|
|
3103
2179
|
console.log(t("cli.init.plan.writes"));
|
|
3104
|
-
console.log(` - ${target}/.fabric/
|
|
2180
|
+
console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
|
|
3105
2181
|
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
3106
|
-
console.log(` - ${target}/.fabric/INITIAL_TAXONOMY.md`);
|
|
3107
2182
|
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
3108
2183
|
console.log(` - ${target}/.fabric/forensic.json`);
|
|
2184
|
+
console.log(` - ${target}/.fabric/fabric-config.json`);
|
|
3109
2185
|
}
|
|
3110
2186
|
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
3111
2187
|
const detected = supports.filter((support) => support.detected);
|
|
@@ -3137,18 +2213,6 @@ function printInitCapabilitySummary(supports, stageResults, options) {
|
|
|
3137
2213
|
console.log(formatCapabilityTableRow(row, widths));
|
|
3138
2214
|
}
|
|
3139
2215
|
}
|
|
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
2216
|
function toCapabilityRow(support, stageResults, options) {
|
|
3153
2217
|
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
3154
2218
|
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.init.capabilities.status.na");
|
|
@@ -3209,18 +2273,6 @@ function formatCapabilityDivider(widths) {
|
|
|
3209
2273
|
}
|
|
3210
2274
|
function formatInitReasonMessage(supports) {
|
|
3211
2275
|
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
2276
|
if (detected.some((support) => support.capabilities.skill)) {
|
|
3225
2277
|
return t("cli.init.reason-message.installable-body");
|
|
3226
2278
|
}
|
|
@@ -3232,11 +2284,11 @@ function yesNoLabel(value) {
|
|
|
3232
2284
|
function formatInitPathAction(path, action) {
|
|
3233
2285
|
return t("cli.init.created-path", { label: labelForInitWriteAction(action), path });
|
|
3234
2286
|
}
|
|
3235
|
-
function
|
|
3236
|
-
if (action === "
|
|
2287
|
+
function formatAgentsMdAction(path, action) {
|
|
2288
|
+
if (action === "preserved") {
|
|
3237
2289
|
return t("cli.init.skipped-existing-path", { label: skippedLabel(), path });
|
|
3238
2290
|
}
|
|
3239
|
-
return
|
|
2291
|
+
return t("cli.init.created-path", { label: createdLabel(), path });
|
|
3240
2292
|
}
|
|
3241
2293
|
function labelForInitWriteAction(action) {
|
|
3242
2294
|
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
@@ -3253,9 +2305,6 @@ function nextLabel() {
|
|
|
3253
2305
|
function reasonLabel() {
|
|
3254
2306
|
return paint.human(t("cli.shared.reason"));
|
|
3255
2307
|
}
|
|
3256
|
-
function updatedLabel() {
|
|
3257
|
-
return paint.success(t("cli.shared.updated"));
|
|
3258
|
-
}
|
|
3259
2308
|
function overwrittenLabel() {
|
|
3260
2309
|
return paint.warn(t("cli.init.force.overwritten"));
|
|
3261
2310
|
}
|
|
@@ -3268,13 +2317,10 @@ function skippedStageLabel() {
|
|
|
3268
2317
|
function failedStageLabel() {
|
|
3269
2318
|
return paint.error(t("cli.init.stages.failed"));
|
|
3270
2319
|
}
|
|
3271
|
-
function
|
|
2320
|
+
function writeStderr2(message) {
|
|
3272
2321
|
process.stderr.write(`${message}
|
|
3273
2322
|
`);
|
|
3274
2323
|
}
|
|
3275
|
-
function sha256(content) {
|
|
3276
|
-
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
3277
|
-
}
|
|
3278
2324
|
export {
|
|
3279
2325
|
buildInitExecutionPlan,
|
|
3280
2326
|
buildInitFabricPlan,
|