@fenglimg/fabric-cli 2.0.0-rc.13 → 2.0.0-rc.21
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 +4 -2
- package/dist/{chunk-X7QPY5KH.js → chunk-4HC5ZK7H.js} +296 -301
- package/dist/{chunk-FDRLV5PL.js → chunk-FNO7CQDG.js} +5 -213
- package/dist/{chunk-WWNXR34K.js → chunk-G2CIOLD4.js} +16 -1
- package/dist/chunk-KZ2YITOS.js +225 -0
- package/dist/{chunk-OHWQNSLH.js → chunk-MF3OTILQ.js} +267 -44
- package/dist/{chunk-OBQU6NHO.js → chunk-ZSESMG6L.js} +0 -6
- package/dist/config-AYP5F72E.js +13 -0
- package/dist/doctor-L6TIXXIX.js +425 -0
- package/dist/index.js +11 -9
- package/dist/{install-SLS5W27W.js → install-DNZXGFHJ.js} +344 -359
- package/dist/{plan-context-hint-QMUPAXIB.js → plan-context-hint-CFDGXHCA.js} +10 -5
- package/dist/{serve-NGLXHDYC.js → serve-6PPQX7AW.js} +16 -11
- package/dist/{uninstall-JHUSFENL.js → uninstall-L2HEEOU3.js} +200 -215
- package/package.json +3 -3
- package/templates/hooks/configs/README.md +9 -5
- package/templates/hooks/configs/cursor-hooks.json +7 -10
- package/templates/hooks/fabric-hint.cjs +350 -21
- package/templates/hooks/knowledge-hint-broad.cjs +39 -14
- package/templates/hooks/knowledge-hint-narrow.cjs +31 -7
- package/templates/hooks/lib/banner-i18n.cjs +252 -0
- package/dist/chunk-Q72D24BG.js +0 -186
- package/dist/doctor-RILCO5OG.js +0 -282
- package/dist/hooks-HIWYI3VG.js +0 -13
- package/dist/scan-VHKZPT2W.js +0 -24
- package/templates/agents-md/AGENTS.md.template +0 -59
- package/templates/bootstrap/CLAUDE.md +0 -8
- package/templates/bootstrap/codex-AGENTS-header.md +0 -6
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +0 -10
|
@@ -1,197 +1,202 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-Q72D24BG.js";
|
|
3
|
+
installMcpClients
|
|
4
|
+
} from "./chunk-KZ2YITOS.js";
|
|
6
5
|
import {
|
|
7
6
|
detectExistingLanguage,
|
|
8
|
-
detectFramework,
|
|
9
7
|
runInitScan
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FNO7CQDG.js";
|
|
11
9
|
import {
|
|
12
|
-
detectClientSupports,
|
|
13
|
-
resolveClients
|
|
14
|
-
} from "./chunk-OHWQNSLH.js";
|
|
15
|
-
import {
|
|
16
|
-
addFabricKnowledgeBaseSection,
|
|
17
10
|
installArchiveHintHook,
|
|
18
11
|
installFabricArchiveSkill,
|
|
19
12
|
installFabricImportSkill,
|
|
20
13
|
installFabricReviewSkill,
|
|
14
|
+
installHookLibs,
|
|
21
15
|
installKnowledgeHintBroadHook,
|
|
22
16
|
installKnowledgeHintNarrowHook,
|
|
23
17
|
mergeClaudeCodeHookConfig,
|
|
24
18
|
mergeCodexHookConfig,
|
|
25
19
|
mergeCursorHookConfig,
|
|
26
|
-
readFabricLanguagePreference
|
|
27
|
-
|
|
20
|
+
readFabricLanguagePreference,
|
|
21
|
+
writeClaudeBootstrapThinShell,
|
|
22
|
+
writeCodexBootstrapManagedBlock,
|
|
23
|
+
writeCursorBootstrapManagedBlock,
|
|
24
|
+
writeFabricAgentsSnapshot
|
|
25
|
+
} from "./chunk-4HC5ZK7H.js";
|
|
26
|
+
import {
|
|
27
|
+
detectClientSupports
|
|
28
|
+
} from "./chunk-MF3OTILQ.js";
|
|
28
29
|
import {
|
|
29
30
|
displayWidth,
|
|
31
|
+
hasActionHint,
|
|
30
32
|
padEnd,
|
|
31
|
-
paint
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
createDebugLogger,
|
|
35
|
-
resolveDevMode
|
|
36
|
-
} from "./chunk-OBQU6NHO.js";
|
|
33
|
+
paint,
|
|
34
|
+
renderFabricError
|
|
35
|
+
} from "./chunk-G2CIOLD4.js";
|
|
37
36
|
import {
|
|
38
37
|
t
|
|
39
38
|
} from "./chunk-6ICJICVU.js";
|
|
39
|
+
import {
|
|
40
|
+
createDebugLogger,
|
|
41
|
+
resolveDevMode
|
|
42
|
+
} from "./chunk-ZSESMG6L.js";
|
|
40
43
|
|
|
41
44
|
// src/commands/install.ts
|
|
42
45
|
import { randomUUID } from "crypto";
|
|
43
46
|
import { homedir } from "os";
|
|
44
47
|
import * as childProcess from "child_process";
|
|
45
|
-
import { appendFileSync, existsSync as existsSync3, mkdirSync, rmSync, statSync as
|
|
46
|
-
import { dirname, isAbsolute as
|
|
48
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync, statSync as statSync3, writeFileSync } from "fs";
|
|
49
|
+
import { dirname, isAbsolute as isAbsolute3, join as join3, resolve as resolve3 } from "path";
|
|
47
50
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
48
51
|
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
49
|
-
import { atomicWriteJson
|
|
50
|
-
import { defineCommand
|
|
52
|
+
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
53
|
+
import { defineCommand } from "citty";
|
|
51
54
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
52
55
|
|
|
53
|
-
// src/
|
|
54
|
-
import { existsSync } from "fs";
|
|
55
|
-
import {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
56
|
+
// src/install/hooks-orchestrator.ts
|
|
57
|
+
import { existsSync, statSync } from "fs";
|
|
58
|
+
import { isAbsolute, join, resolve } from "path";
|
|
59
|
+
async function installHooks(target, _options = {}) {
|
|
60
|
+
const normalizedTarget = normalizeTarget(target);
|
|
61
|
+
assertExistingDirectory(normalizedTarget);
|
|
62
|
+
const results = [];
|
|
63
|
+
results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
|
|
64
|
+
results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
|
|
65
|
+
results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
|
|
66
|
+
results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
|
|
67
|
+
results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
|
|
68
|
+
results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
|
|
69
|
+
results.push(...await runStep(() => installHookLibs(normalizedTarget)));
|
|
70
|
+
results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
|
|
71
|
+
results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
|
|
72
|
+
results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
|
|
73
|
+
results.push(await runSingleStep("bootstrap-snapshot", () => writeFabricAgentsSnapshot(normalizedTarget)));
|
|
74
|
+
results.push(await runSingleStep("bootstrap-claude", () => writeClaudeBootstrapThinShell(normalizedTarget)));
|
|
75
|
+
results.push(await runSingleStep("bootstrap-codex", () => writeCodexBootstrapManagedBlock(normalizedTarget)));
|
|
76
|
+
results.push(await runSingleStep("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(normalizedTarget)));
|
|
77
|
+
results.push(...validateHookPaths(normalizedTarget));
|
|
78
|
+
return summarizeResults(results);
|
|
79
|
+
}
|
|
80
|
+
function validateHookPaths(projectRoot) {
|
|
81
|
+
const scripts = [
|
|
82
|
+
{ stepSuffix: "", hookFile: "fabric-hint.cjs" },
|
|
83
|
+
{ stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
|
|
84
|
+
{ stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
|
|
85
|
+
];
|
|
86
|
+
const clients = [
|
|
87
|
+
{
|
|
88
|
+
client: "claude",
|
|
89
|
+
configRel: join(".claude", "settings.json"),
|
|
90
|
+
hookDir: join(".claude", "hooks")
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
client: "codex",
|
|
94
|
+
configRel: join(".codex", "hooks.json"),
|
|
95
|
+
hookDir: join(".codex", "hooks")
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
client: "cursor",
|
|
99
|
+
configRel: join(".cursor", "hooks.json"),
|
|
100
|
+
hookDir: join(".cursor", "hooks")
|
|
101
|
+
}
|
|
102
|
+
];
|
|
103
|
+
const results = [];
|
|
104
|
+
for (const { client, configRel, hookDir } of clients) {
|
|
105
|
+
const configPath = resolve(projectRoot, configRel);
|
|
106
|
+
if (!existsSync(configPath)) {
|
|
107
|
+
results.push({
|
|
108
|
+
step: `hook-validate-${client}`,
|
|
109
|
+
path: configPath,
|
|
110
|
+
status: "skipped",
|
|
111
|
+
message: "missing-config"
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const { stepSuffix, hookFile } of scripts) {
|
|
116
|
+
const expectedHookPath = resolve(projectRoot, hookDir, hookFile);
|
|
117
|
+
const expectedHookRel = join(hookDir, hookFile);
|
|
118
|
+
const step = `hook-validate-${client}${stepSuffix}`;
|
|
119
|
+
if (!existsSync(expectedHookPath)) {
|
|
120
|
+
results.push({
|
|
121
|
+
step,
|
|
122
|
+
path: expectedHookPath,
|
|
123
|
+
status: "error",
|
|
124
|
+
message: `hook script missing: ${expectedHookRel}`
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
results.push({ step, path: expectedHookPath, status: "skipped", message: "ok" });
|
|
82
129
|
}
|
|
83
|
-
clients.add(clientKind);
|
|
84
130
|
}
|
|
85
|
-
return
|
|
131
|
+
return results;
|
|
86
132
|
}
|
|
87
|
-
async function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
async function runStep(fn) {
|
|
134
|
+
try {
|
|
135
|
+
return await fn();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
step: "hook-install",
|
|
140
|
+
path: "",
|
|
141
|
+
status: "error",
|
|
142
|
+
message: error instanceof Error ? error.message : String(error)
|
|
143
|
+
}
|
|
144
|
+
];
|
|
95
145
|
}
|
|
96
|
-
return parsed;
|
|
97
|
-
}
|
|
98
|
-
function resolveServerPath(override) {
|
|
99
|
-
if (override) return override;
|
|
100
|
-
if (process.env.FAB_SERVER_PATH) return resolve(process.env.FAB_SERVER_PATH);
|
|
101
|
-
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
102
146
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
hooks: hooksCommand,
|
|
114
|
-
install: defineCommand({
|
|
115
|
-
meta: {
|
|
116
|
-
name: "install",
|
|
117
|
-
description: t("cli.config.install.description")
|
|
118
|
-
},
|
|
119
|
-
args: {
|
|
120
|
-
clients: {
|
|
121
|
-
type: "string",
|
|
122
|
-
description: t("cli.config.install.args.clients.description")
|
|
123
|
-
},
|
|
124
|
-
"dry-run": {
|
|
125
|
-
type: "boolean",
|
|
126
|
-
description: t("cli.config.install.args.dry-run.description"),
|
|
127
|
-
default: false
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
async run({ args }) {
|
|
131
|
-
const selectedClients = parseClientFilter(args.clients);
|
|
132
|
-
const result = await installMcpClients(process.cwd(), {
|
|
133
|
-
clients: selectedClients === null ? void 0 : Array.from(selectedClients),
|
|
134
|
-
dryRun: args["dry-run"]
|
|
135
|
-
});
|
|
136
|
-
if (result.details.length === 0) {
|
|
137
|
-
writeStderr(t("cli.config.install.no-configs"));
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
for (const detail of result.details) {
|
|
141
|
-
if (detail.action === "skipped") {
|
|
142
|
-
writeStderr(t("cli.config.install.no-config-path", { client: detail.client }));
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (detail.action === "dry-run" && detail.path !== null) {
|
|
146
|
-
writeStderr(t("cli.config.install.dry-run", { client: detail.client, path: detail.path }));
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (detail.path !== null) {
|
|
150
|
-
writeStderr(t("cli.config.install.wrote", { client: detail.client, path: detail.path }));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
})
|
|
147
|
+
async function runSingleStep(step, fn) {
|
|
148
|
+
try {
|
|
149
|
+
return await fn();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
step,
|
|
153
|
+
path: "",
|
|
154
|
+
status: "error",
|
|
155
|
+
message: error instanceof Error ? error.message : String(error)
|
|
156
|
+
};
|
|
155
157
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const workspaceRoot = resolve(target);
|
|
159
|
-
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
160
|
-
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
161
|
-
const serverPath = resolveServerPath(options.localServerPath);
|
|
162
|
-
const writers = resolveClients(workspaceRoot, fabricConfig, { claudeMcpScope: options.claudeMcpScope }).filter(
|
|
163
|
-
(writer) => selectedClients === null ? true : selectedClients.has(writer.clientKind)
|
|
164
|
-
);
|
|
158
|
+
}
|
|
159
|
+
function summarizeResults(results) {
|
|
165
160
|
const installed = [];
|
|
166
161
|
const skipped = [];
|
|
167
|
-
const
|
|
168
|
-
for (const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
162
|
+
const errors = [];
|
|
163
|
+
for (const r of results) {
|
|
164
|
+
switch (r.status) {
|
|
165
|
+
case "written":
|
|
166
|
+
installed.push(r.path);
|
|
167
|
+
break;
|
|
168
|
+
case "skipped":
|
|
169
|
+
skipped.push(r.path);
|
|
170
|
+
break;
|
|
171
|
+
case "error":
|
|
172
|
+
errors.push(`${r.step} ${r.path}: ${r.message ?? "unknown error"}`);
|
|
173
|
+
break;
|
|
179
174
|
}
|
|
180
|
-
await writer.write(serverPath, workspaceRoot);
|
|
181
|
-
installed.push(writer.clientKind);
|
|
182
|
-
details.push({ client: writer.clientKind, path: configPath, action: "wrote" });
|
|
183
175
|
}
|
|
184
|
-
return { installed, skipped,
|
|
176
|
+
return { installed, skipped, errors };
|
|
177
|
+
}
|
|
178
|
+
function normalizeTarget(targetInput) {
|
|
179
|
+
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
180
|
+
}
|
|
181
|
+
function assertExistingDirectory(target) {
|
|
182
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
183
|
+
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
184
|
+
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// src/scanner/forensic.ts
|
|
188
188
|
import { execFileSync } from "child_process";
|
|
189
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
|
|
189
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
190
190
|
import { createRequire } from "module";
|
|
191
|
-
import { basename, extname, isAbsolute, join, posix, relative, resolve as resolve2, sep } from "path";
|
|
191
|
+
import { basename, extname, isAbsolute as isAbsolute2, join as join2, posix, relative, resolve as resolve2, sep } from "path";
|
|
192
192
|
import {
|
|
193
193
|
forensicReportSchema
|
|
194
194
|
} from "@fenglimg/fabric-shared";
|
|
195
|
+
|
|
196
|
+
// src/scanner/detector.ts
|
|
197
|
+
import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
198
|
+
|
|
199
|
+
// src/scanner/forensic.ts
|
|
195
200
|
var require2 = createRequire(import.meta.url);
|
|
196
201
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
197
202
|
".fabric",
|
|
@@ -274,7 +279,7 @@ var parserInitPromise = null;
|
|
|
274
279
|
var languagePromiseByKind = {};
|
|
275
280
|
var parserBundlePromiseByKind = {};
|
|
276
281
|
async function buildForensicReport(targetInput) {
|
|
277
|
-
const target =
|
|
282
|
+
const target = normalizeTarget2(targetInput);
|
|
278
283
|
const framework = detectFramework(target);
|
|
279
284
|
const topology = buildTopology(target);
|
|
280
285
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -310,11 +315,11 @@ async function buildForensicReport(targetInput) {
|
|
|
310
315
|
}
|
|
311
316
|
return validation.data;
|
|
312
317
|
}
|
|
313
|
-
function
|
|
314
|
-
return
|
|
318
|
+
function normalizeTarget2(targetInput) {
|
|
319
|
+
return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
|
|
315
320
|
}
|
|
316
321
|
function buildTopology(root) {
|
|
317
|
-
|
|
322
|
+
assertExistingDirectory2(root);
|
|
318
323
|
const byExt = {};
|
|
319
324
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
320
325
|
const files = [];
|
|
@@ -327,7 +332,7 @@ function buildTopology(root) {
|
|
|
327
332
|
continue;
|
|
328
333
|
}
|
|
329
334
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
330
|
-
const absolutePath =
|
|
335
|
+
const absolutePath = join2(current, entry.name);
|
|
331
336
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
332
337
|
if (relativePath.length === 0) {
|
|
333
338
|
continue;
|
|
@@ -347,7 +352,7 @@ function buildTopology(root) {
|
|
|
347
352
|
if (!entry.isFile()) {
|
|
348
353
|
continue;
|
|
349
354
|
}
|
|
350
|
-
const stats =
|
|
355
|
+
const stats = statSync2(absolutePath);
|
|
351
356
|
const extension = extname(entry.name) || "[none]";
|
|
352
357
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
353
358
|
totalFiles += 1;
|
|
@@ -365,8 +370,8 @@ function buildTopology(root) {
|
|
|
365
370
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
366
371
|
};
|
|
367
372
|
}
|
|
368
|
-
function
|
|
369
|
-
if (!existsSync2(target) || !
|
|
373
|
+
function assertExistingDirectory2(target) {
|
|
374
|
+
if (!existsSync2(target) || !statSync2(target).isDirectory()) {
|
|
370
375
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
371
376
|
}
|
|
372
377
|
}
|
|
@@ -415,7 +420,7 @@ function getEntryPointReason(relativePath) {
|
|
|
415
420
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
416
421
|
const samples = [];
|
|
417
422
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
418
|
-
const absolutePath =
|
|
423
|
+
const absolutePath = join2(target, ...entryPoint.path.split("/"));
|
|
419
424
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
420
425
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
421
426
|
frameworkKind,
|
|
@@ -452,7 +457,7 @@ function readFirstLines(path, lineLimit) {
|
|
|
452
457
|
}
|
|
453
458
|
}
|
|
454
459
|
function readPackageDependencies(target) {
|
|
455
|
-
const packageJsonPath =
|
|
460
|
+
const packageJsonPath = join2(target, "package.json");
|
|
456
461
|
if (!existsSync2(packageJsonPath)) {
|
|
457
462
|
return /* @__PURE__ */ new Map();
|
|
458
463
|
}
|
|
@@ -793,8 +798,8 @@ function scoreFrameworkConfidence(input) {
|
|
|
793
798
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
794
799
|
}
|
|
795
800
|
function readReadmeInfo(target) {
|
|
796
|
-
const readmePath =
|
|
797
|
-
const hasContributing = existsSync2(
|
|
801
|
+
const readmePath = join2(target, "README.md");
|
|
802
|
+
const hasContributing = existsSync2(join2(target, "CONTRIBUTING.md"));
|
|
798
803
|
if (!existsSync2(readmePath)) {
|
|
799
804
|
return {
|
|
800
805
|
quality: "missing",
|
|
@@ -1280,7 +1285,7 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1280
1285
|
return recommendations;
|
|
1281
1286
|
}
|
|
1282
1287
|
function readProjectName(target) {
|
|
1283
|
-
const packageJsonPath =
|
|
1288
|
+
const packageJsonPath = join2(target, "package.json");
|
|
1284
1289
|
if (existsSync2(packageJsonPath)) {
|
|
1285
1290
|
try {
|
|
1286
1291
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
@@ -1294,7 +1299,7 @@ function readProjectName(target) {
|
|
|
1294
1299
|
return basename(target);
|
|
1295
1300
|
}
|
|
1296
1301
|
function getCliVersion() {
|
|
1297
|
-
return true ? "2.0.0-rc.
|
|
1302
|
+
return true ? "2.0.0-rc.21" : "unknown";
|
|
1298
1303
|
}
|
|
1299
1304
|
function sortRecord(record) {
|
|
1300
1305
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1304,81 +1309,33 @@ function toPosixPath(path) {
|
|
|
1304
1309
|
}
|
|
1305
1310
|
|
|
1306
1311
|
// src/commands/install.ts
|
|
1307
|
-
var
|
|
1308
|
-
|
|
1309
|
-
This project uses [Fabric](https://github.com/fenglimg/fabric) for cross-client AI knowledge management.
|
|
1310
|
-
|
|
1311
|
-
Knowledge entries live in \`.fabric/knowledge/\` (team) and \`~/.fabric/knowledge/\` (personal).
|
|
1312
|
-
Run \`fabric doctor\` to verify state.
|
|
1313
|
-
|
|
1314
|
-
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1315
|
-
`;
|
|
1316
|
-
var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1312
|
+
var LOCAL_FABRIC_SERVER_PATH = join3("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1317
1313
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1318
1314
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1319
|
-
var installCommand =
|
|
1315
|
+
var installCommand = defineCommand({
|
|
1320
1316
|
meta: {
|
|
1321
1317
|
name: "install",
|
|
1322
1318
|
description: t("cli.install.description")
|
|
1323
1319
|
},
|
|
1324
1320
|
args: {
|
|
1325
|
-
target: {
|
|
1326
|
-
type: "string",
|
|
1327
|
-
description: t("cli.install.args.target.description")
|
|
1328
|
-
},
|
|
1329
1321
|
debug: {
|
|
1330
1322
|
type: "boolean",
|
|
1331
1323
|
description: t("cli.install.args.debug.description"),
|
|
1332
1324
|
default: false
|
|
1333
1325
|
},
|
|
1334
|
-
|
|
1326
|
+
"dry-run": {
|
|
1335
1327
|
type: "boolean",
|
|
1336
|
-
description: t("cli.install.args.
|
|
1328
|
+
description: t("cli.install.args.dry-run.description"),
|
|
1337
1329
|
default: false
|
|
1338
1330
|
},
|
|
1331
|
+
target: {
|
|
1332
|
+
type: "string",
|
|
1333
|
+
description: t("cli.install.args.target.description")
|
|
1334
|
+
},
|
|
1339
1335
|
yes: {
|
|
1340
1336
|
type: "boolean",
|
|
1341
1337
|
description: t("cli.install.args.yes.description"),
|
|
1342
1338
|
default: false
|
|
1343
|
-
},
|
|
1344
|
-
plan: {
|
|
1345
|
-
type: "boolean",
|
|
1346
|
-
description: t("cli.install.args.plan.description"),
|
|
1347
|
-
default: false
|
|
1348
|
-
},
|
|
1349
|
-
reapply: {
|
|
1350
|
-
type: "boolean",
|
|
1351
|
-
description: t("cli.install.args.reapply.description"),
|
|
1352
|
-
default: false
|
|
1353
|
-
},
|
|
1354
|
-
bootstrap: {
|
|
1355
|
-
type: "boolean",
|
|
1356
|
-
default: true,
|
|
1357
|
-
negativeDescription: t("cli.install.args.no-bootstrap.description")
|
|
1358
|
-
},
|
|
1359
|
-
mcp: {
|
|
1360
|
-
type: "boolean",
|
|
1361
|
-
default: true,
|
|
1362
|
-
negativeDescription: t("cli.install.args.no-mcp.description")
|
|
1363
|
-
},
|
|
1364
|
-
hooks: {
|
|
1365
|
-
type: "boolean",
|
|
1366
|
-
default: true,
|
|
1367
|
-
negativeDescription: t("cli.install.args.no-hooks.description")
|
|
1368
|
-
},
|
|
1369
|
-
interactive: {
|
|
1370
|
-
type: "boolean",
|
|
1371
|
-
description: t("cli.install.args.interactive.description"),
|
|
1372
|
-
default: true
|
|
1373
|
-
},
|
|
1374
|
-
"mcp-install": {
|
|
1375
|
-
type: "string",
|
|
1376
|
-
default: "global",
|
|
1377
|
-
description: t("cli.install.mcp.install.prompt")
|
|
1378
|
-
},
|
|
1379
|
-
scope: {
|
|
1380
|
-
type: "string",
|
|
1381
|
-
description: t("cli.install.mcp.scope.description")
|
|
1382
1339
|
}
|
|
1383
1340
|
},
|
|
1384
1341
|
async run({ args }) {
|
|
@@ -1390,22 +1347,22 @@ async function runInitCommand(args) {
|
|
|
1390
1347
|
const logger = createDebugLogger(args.debug);
|
|
1391
1348
|
const resolution = resolveDevMode(args.target, process.cwd());
|
|
1392
1349
|
const intent = resolveInitCliIntent(args, resolution.target);
|
|
1393
|
-
|
|
1394
|
-
|
|
1350
|
+
const fabricInitialized = existsSync3(join3(intent.target, ".fabric", "events.jsonl"));
|
|
1351
|
+
if (fabricInitialized) {
|
|
1352
|
+
try {
|
|
1353
|
+
checkLockOrThrow(intent.target);
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
if (hasActionHint(err)) {
|
|
1356
|
+
renderFabricError(err);
|
|
1357
|
+
process.exit(1);
|
|
1358
|
+
}
|
|
1359
|
+
throw err;
|
|
1360
|
+
}
|
|
1395
1361
|
}
|
|
1396
1362
|
logger(`init target source: ${resolution.source}`);
|
|
1397
1363
|
for (const step of resolution.chain) {
|
|
1398
1364
|
logger(step);
|
|
1399
1365
|
}
|
|
1400
|
-
if (intent.options.planOnly) {
|
|
1401
|
-
writeStderr2(t("cli.install.compat.plan"));
|
|
1402
|
-
}
|
|
1403
|
-
if (args.interactive === false) {
|
|
1404
|
-
writeStderr2(t("cli.install.compat.interactive"));
|
|
1405
|
-
}
|
|
1406
|
-
if (args.bootstrap === false || args.mcp === false || args.hooks === false) {
|
|
1407
|
-
writeStderr2(t("cli.install.compat.legacy-stage-flags"));
|
|
1408
|
-
}
|
|
1409
1366
|
const supports = detectClientSupports(intent.target);
|
|
1410
1367
|
const basePlan = await buildInitExecutionPlan({
|
|
1411
1368
|
target: intent.target,
|
|
@@ -1415,7 +1372,7 @@ async function runInitCommand(args) {
|
|
|
1415
1372
|
interactive: intent.interactiveSummary && !intent.wizardEnabled,
|
|
1416
1373
|
supports
|
|
1417
1374
|
});
|
|
1418
|
-
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan,
|
|
1375
|
+
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, createDefaultInitWizardAdapter()) : basePlan;
|
|
1419
1376
|
if (plan === null) {
|
|
1420
1377
|
process.exitCode = 130;
|
|
1421
1378
|
return;
|
|
@@ -1427,7 +1384,7 @@ async function runInitCommand(args) {
|
|
|
1427
1384
|
return result;
|
|
1428
1385
|
}
|
|
1429
1386
|
function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
1430
|
-
const target =
|
|
1387
|
+
const target = join3(fabricDir, "fabric-config.json");
|
|
1431
1388
|
if (existsSync3(target)) return;
|
|
1432
1389
|
const detectedLanguage = detectExistingLanguage(targetRoot);
|
|
1433
1390
|
const FABRIC_CONFIG_DEFAULTS = {
|
|
@@ -1496,39 +1453,23 @@ function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
|
1496
1453
|
);
|
|
1497
1454
|
}
|
|
1498
1455
|
function resolveInitCliIntent(args, targetInput) {
|
|
1499
|
-
const target =
|
|
1500
|
-
const mcpInstallMode =
|
|
1501
|
-
const claudeMcpScope =
|
|
1456
|
+
const target = normalizeTarget3(targetInput);
|
|
1457
|
+
const mcpInstallMode = "global";
|
|
1458
|
+
const claudeMcpScope = "project";
|
|
1502
1459
|
const terminalInteractive = isInteractiveInit();
|
|
1503
|
-
const planOnly = args
|
|
1504
|
-
const reapply = args.reapply === true;
|
|
1460
|
+
const planOnly = args["dry-run"] === true;
|
|
1505
1461
|
const options = {
|
|
1506
|
-
|
|
1507
|
-
skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
|
|
1508
|
-
skipMcp: args.mcp === false ? true : args.skipMcp,
|
|
1509
|
-
skipHooks: args.hooks === false ? true : args.skipHooks,
|
|
1510
|
-
planOnly,
|
|
1511
|
-
reapply
|
|
1462
|
+
planOnly
|
|
1512
1463
|
};
|
|
1513
1464
|
return {
|
|
1514
1465
|
target,
|
|
1515
1466
|
options,
|
|
1516
1467
|
mcpInstallMode,
|
|
1517
1468
|
claudeMcpScope,
|
|
1518
|
-
interactiveSummary:
|
|
1469
|
+
interactiveSummary: terminalInteractive,
|
|
1519
1470
|
wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
|
|
1520
1471
|
};
|
|
1521
1472
|
}
|
|
1522
|
-
function resolveClaudeMcpScope(raw) {
|
|
1523
|
-
if (raw === void 0 || raw === "project") {
|
|
1524
|
-
return "project";
|
|
1525
|
-
}
|
|
1526
|
-
if (raw === "user") {
|
|
1527
|
-
return "user";
|
|
1528
|
-
}
|
|
1529
|
-
writeStderr2(t("cli.install.mcp.scope.invalid", { value: raw }));
|
|
1530
|
-
return "project";
|
|
1531
|
-
}
|
|
1532
1473
|
async function buildInitExecutionPlan(input) {
|
|
1533
1474
|
const options = input.options ?? {};
|
|
1534
1475
|
const scaffold = await buildInitFabricPlan(input.target, options);
|
|
@@ -1565,17 +1506,17 @@ async function buildInitExecutionPlan(input) {
|
|
|
1565
1506
|
};
|
|
1566
1507
|
}
|
|
1567
1508
|
async function executeInitExecutionPlan(plan) {
|
|
1568
|
-
if (plan.options.force) {
|
|
1569
|
-
writeStderr2(t("cli.install.force.warning", { path: plan.target }));
|
|
1570
|
-
}
|
|
1571
|
-
if (plan.options.reapply && !plan.options.planOnly && !plan.interactive) {
|
|
1572
|
-
writeStderr2(formatInitModeBanner(plan.options));
|
|
1573
|
-
}
|
|
1574
1509
|
if (plan.interactive) {
|
|
1575
1510
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1576
1511
|
}
|
|
1512
|
+
const scaffoldStates = [
|
|
1513
|
+
{ path: plan.scaffold.metaPath, state: plan.scaffold.metaState },
|
|
1514
|
+
{ path: plan.scaffold.eventsPath, state: plan.scaffold.eventsState },
|
|
1515
|
+
{ path: plan.scaffold.forensicPath, state: plan.scaffold.forensicState }
|
|
1516
|
+
];
|
|
1577
1517
|
if (plan.options.planOnly) {
|
|
1578
1518
|
printInitPlanPreview(plan);
|
|
1519
|
+
printInitDiffStateTable(scaffoldStates);
|
|
1579
1520
|
return {
|
|
1580
1521
|
plan,
|
|
1581
1522
|
created: buildPlanOnlyScaffoldResult(plan.scaffold),
|
|
@@ -1583,6 +1524,17 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1583
1524
|
finalSupports: plan.supports
|
|
1584
1525
|
};
|
|
1585
1526
|
}
|
|
1527
|
+
if (existsSync3(plan.scaffold.fabricDir) && !statSync3(plan.scaffold.fabricDir).isDirectory()) {
|
|
1528
|
+
throw new Error(
|
|
1529
|
+
t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
const drifted = scaffoldStates.find(
|
|
1533
|
+
(entry) => entry.state === "drifted" || entry.state === "user-modified"
|
|
1534
|
+
);
|
|
1535
|
+
if (drifted !== void 0) {
|
|
1536
|
+
throw new Error(t("cli.install.diff.drift-abort", { path: drifted.path }));
|
|
1537
|
+
}
|
|
1586
1538
|
let created = null;
|
|
1587
1539
|
const stageResults = [];
|
|
1588
1540
|
let finalSupports = plan.supports;
|
|
@@ -1607,6 +1559,11 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1607
1559
|
exhaustiveInitExecutionStep(step);
|
|
1608
1560
|
}
|
|
1609
1561
|
}
|
|
1562
|
+
if (scaffoldStates.every((entry) => entry.state === "present-canonical")) {
|
|
1563
|
+
console.log(
|
|
1564
|
+
t("cli.install.diff.canonical", { count: String(scaffoldStates.length) })
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1610
1567
|
return {
|
|
1611
1568
|
plan,
|
|
1612
1569
|
created: created ?? unreachableInitScaffold(),
|
|
@@ -1619,20 +1576,23 @@ function resolvePersonalFabricRoot() {
|
|
|
1619
1576
|
return process.env.FABRIC_HOME ?? homedir();
|
|
1620
1577
|
}
|
|
1621
1578
|
async function buildInitFabricPlan(target, options) {
|
|
1622
|
-
|
|
1623
|
-
const fabricDir =
|
|
1624
|
-
const agentsMdPath =
|
|
1579
|
+
assertExistingDirectory3(target);
|
|
1580
|
+
const fabricDir = join3(target, ".fabric");
|
|
1581
|
+
const agentsMdPath = join3(target, "AGENTS.md");
|
|
1625
1582
|
const agentsMdAction = existsSync3(agentsMdPath) ? "preserved" : "created";
|
|
1626
|
-
const knowledgeDir =
|
|
1627
|
-
const personalKnowledgeDir =
|
|
1628
|
-
const forensicPath =
|
|
1629
|
-
const eventsPath =
|
|
1630
|
-
const metaPath =
|
|
1583
|
+
const knowledgeDir = join3(fabricDir, "knowledge");
|
|
1584
|
+
const personalKnowledgeDir = join3(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1585
|
+
const forensicPath = join3(fabricDir, "forensic.json");
|
|
1586
|
+
const eventsPath = join3(fabricDir, "events.jsonl");
|
|
1587
|
+
const metaPath = join3(fabricDir, "agents.meta.json");
|
|
1631
1588
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1632
1589
|
const knowledgeDirAction = existsSync3(knowledgeDir) ? "overwritten" : "created";
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
const
|
|
1590
|
+
const metaClassification = classifyFreshPath(metaPath, "structural");
|
|
1591
|
+
const eventsClassification = classifyFreshPath(eventsPath, "presence");
|
|
1592
|
+
const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
|
|
1593
|
+
const metaAction = diffStateToWriteAction(metaClassification.state);
|
|
1594
|
+
const eventsAction = diffStateToWriteAction(eventsClassification.state);
|
|
1595
|
+
const forensicAction = diffStateToWriteAction(forensicClassification.state);
|
|
1636
1596
|
const forensicReport = await buildForensicReport(target);
|
|
1637
1597
|
const meta = createInitialMeta();
|
|
1638
1598
|
return {
|
|
@@ -1652,24 +1612,23 @@ async function buildInitFabricPlan(target, options) {
|
|
|
1652
1612
|
eventsAction,
|
|
1653
1613
|
forensicPath,
|
|
1654
1614
|
forensicAction,
|
|
1655
|
-
forensicReport
|
|
1615
|
+
forensicReport,
|
|
1616
|
+
metaState: metaClassification.state,
|
|
1617
|
+
eventsState: eventsClassification.state,
|
|
1618
|
+
forensicState: forensicClassification.state
|
|
1656
1619
|
};
|
|
1657
1620
|
}
|
|
1658
1621
|
async function executeInitFabricPlan(plan) {
|
|
1659
|
-
const isReapply = plan.options?.reapply === true;
|
|
1660
1622
|
if (plan.replaceFabricDir) {
|
|
1661
1623
|
rmSync(plan.fabricDir, { force: true });
|
|
1662
1624
|
}
|
|
1663
1625
|
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1664
1626
|
writeDefaultFabricConfig(plan.fabricDir, plan.target);
|
|
1665
|
-
if (plan.agentsMdAction === "created" && !existsSync3(plan.agentsMdPath)) {
|
|
1666
|
-
await atomicWriteText(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1667
|
-
}
|
|
1668
1627
|
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1669
1628
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1670
|
-
const teamSubDir =
|
|
1629
|
+
const teamSubDir = join3(plan.knowledgeDir, sub);
|
|
1671
1630
|
mkdirSync(teamSubDir, { recursive: true });
|
|
1672
|
-
const teamGitkeep =
|
|
1631
|
+
const teamGitkeep = join3(teamSubDir, ".gitkeep");
|
|
1673
1632
|
if (!existsSync3(teamGitkeep)) {
|
|
1674
1633
|
writeFileSync(teamGitkeep, "", "utf8");
|
|
1675
1634
|
}
|
|
@@ -1677,36 +1636,49 @@ async function executeInitFabricPlan(plan) {
|
|
|
1677
1636
|
try {
|
|
1678
1637
|
mkdirSync(plan.personalKnowledgeDir, { recursive: true });
|
|
1679
1638
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1680
|
-
mkdirSync(
|
|
1639
|
+
mkdirSync(join3(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1681
1640
|
}
|
|
1682
1641
|
} catch {
|
|
1683
1642
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
writeFileSync(plan.eventsPath, "", "utf8");
|
|
1690
|
-
}
|
|
1691
|
-
} else {
|
|
1643
|
+
if (plan.metaState === "missing") {
|
|
1644
|
+
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1645
|
+
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
1646
|
+
}
|
|
1647
|
+
if (plan.eventsState === "missing") {
|
|
1692
1648
|
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
1649
|
+
mkdirSync(dirname(plan.eventsPath), { recursive: true });
|
|
1693
1650
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
1694
1651
|
}
|
|
1695
1652
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
1696
1653
|
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
1697
|
-
|
|
1654
|
+
const wasCanonicalReRun = plan.metaState === "present-canonical" && plan.eventsState === "present-canonical";
|
|
1655
|
+
if (!wasCanonicalReRun) {
|
|
1698
1656
|
try {
|
|
1699
1657
|
await runInitScan(plan.target, { source: "init" });
|
|
1700
1658
|
} catch (error) {
|
|
1701
|
-
|
|
1659
|
+
writeStderr(
|
|
1702
1660
|
`[warn] init-scan failed: ${error instanceof Error ? error.message : String(error)} \u2014 re-run \`fab scan\` to populate baseline knowledge entries.`
|
|
1703
1661
|
);
|
|
1704
1662
|
}
|
|
1705
1663
|
}
|
|
1706
|
-
if (
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1664
|
+
if (existsSync3(plan.eventsPath)) {
|
|
1665
|
+
const applied = [];
|
|
1666
|
+
const canonical = [];
|
|
1667
|
+
const drifted = [];
|
|
1668
|
+
for (const entry of [
|
|
1669
|
+
{ path: plan.metaPath, state: plan.metaState },
|
|
1670
|
+
{ path: plan.eventsPath, state: plan.eventsState },
|
|
1671
|
+
{ path: plan.forensicPath, state: plan.forensicState }
|
|
1672
|
+
]) {
|
|
1673
|
+
if (entry.state === "missing") {
|
|
1674
|
+
applied.push(entry.path);
|
|
1675
|
+
} else if (entry.state === "present-canonical") {
|
|
1676
|
+
canonical.push(entry.path);
|
|
1677
|
+
} else {
|
|
1678
|
+
drifted.push(entry.path);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
appendInstallDiffLedgerEvent(plan.eventsPath, { applied, canonical, drifted });
|
|
1710
1682
|
}
|
|
1711
1683
|
return {
|
|
1712
1684
|
agentsMdPath: plan.agentsMdPath,
|
|
@@ -1726,16 +1698,16 @@ async function initFabric(target, options) {
|
|
|
1726
1698
|
return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
1727
1699
|
}
|
|
1728
1700
|
function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
|
|
1729
|
-
return terminalInteractive && args.
|
|
1701
|
+
return terminalInteractive && args.yes !== true;
|
|
1730
1702
|
}
|
|
1731
|
-
async function resolveInitExecutionPlanWithWizard(basePlan,
|
|
1703
|
+
async function resolveInitExecutionPlanWithWizard(basePlan, wizardAdapter) {
|
|
1732
1704
|
const selection = await wizardAdapter.run({
|
|
1733
1705
|
target: basePlan.target,
|
|
1734
1706
|
options: basePlan.options,
|
|
1735
1707
|
supports: basePlan.supports,
|
|
1736
1708
|
mcpInstallMode: basePlan.mcpInstallMode,
|
|
1737
1709
|
claudeMcpScope: basePlan.claudeMcpScope,
|
|
1738
|
-
lockedStages:
|
|
1710
|
+
lockedStages: []
|
|
1739
1711
|
});
|
|
1740
1712
|
if (selection === null) {
|
|
1741
1713
|
return null;
|
|
@@ -1792,12 +1764,17 @@ function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
|
1792
1764
|
paint.muted(t("cli.install.language_preference_hint", { value: fabricLanguage }))
|
|
1793
1765
|
);
|
|
1794
1766
|
}
|
|
1767
|
+
function printInitDiffStateTable(entries) {
|
|
1768
|
+
for (const entry of entries) {
|
|
1769
|
+
console.log(` ${formatDiffFileState(entry.state)} ${entry.path}`);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1795
1772
|
function printInitPlanPreview(plan) {
|
|
1796
1773
|
console.log(t("cli.install.plan.preview-title"));
|
|
1797
1774
|
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1798
1775
|
console.log(
|
|
1799
1776
|
t("cli.install.plan.preview-result", {
|
|
1800
|
-
mode:
|
|
1777
|
+
mode: t("cli.install.mode.default"),
|
|
1801
1778
|
bootstrap: yesNoLabel(!plan.options.skipBootstrap),
|
|
1802
1779
|
mcp: yesNoLabel(!plan.options.skipMcp),
|
|
1803
1780
|
hooks: yesNoLabel(!plan.options.skipHooks)
|
|
@@ -1838,17 +1815,20 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1838
1815
|
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
1839
1816
|
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
1840
1817
|
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
1818
|
+
installResults.push(...await runBestEffort("hook-lib", () => installHookLibs(plan.target)));
|
|
1841
1819
|
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
1842
1820
|
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
1843
1821
|
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
1844
|
-
|
|
1845
|
-
installResults.push(
|
|
1822
|
+
installResults.push(await runBestEffortSingle("bootstrap-snapshot", () => writeFabricAgentsSnapshot(plan.target)));
|
|
1823
|
+
installResults.push(await runBestEffortSingle("bootstrap-claude", () => writeClaudeBootstrapThinShell(plan.target)));
|
|
1824
|
+
installResults.push(await runBestEffortSingle("bootstrap-codex", () => writeCodexBootstrapManagedBlock(plan.target)));
|
|
1825
|
+
installResults.push(await runBestEffortSingle("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(plan.target)));
|
|
1846
1826
|
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
1847
1827
|
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
1848
1828
|
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
1849
1829
|
for (const result of installResults) {
|
|
1850
1830
|
if (result.status === "error") {
|
|
1851
|
-
|
|
1831
|
+
writeStderr(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
1852
1832
|
}
|
|
1853
1833
|
}
|
|
1854
1834
|
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
@@ -1858,15 +1838,14 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1858
1838
|
case "mcp": {
|
|
1859
1839
|
if (stage.installMode === "local") {
|
|
1860
1840
|
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
1861
|
-
|
|
1862
|
-
|
|
1841
|
+
writeStderr(t("cli.install.mcp.install.local"));
|
|
1842
|
+
writeStderr(t("cli.install.mcp.local.installing", { manager }));
|
|
1863
1843
|
installLocalFabricServer(plan.target, manager);
|
|
1864
|
-
|
|
1844
|
+
writeStderr(t("cli.install.mcp.local.installed"));
|
|
1865
1845
|
} else {
|
|
1866
|
-
|
|
1846
|
+
writeStderr(t("cli.install.mcp.install.global"));
|
|
1867
1847
|
}
|
|
1868
1848
|
const result = await installMcpClients(plan.target, {
|
|
1869
|
-
force: plan.options.force,
|
|
1870
1849
|
localServerPath: stage.localServerPath,
|
|
1871
1850
|
claudeMcpScope: stage.claudeMcpScope
|
|
1872
1851
|
});
|
|
@@ -1878,7 +1857,7 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1878
1857
|
return { name: "mcp", disposition: "ran" };
|
|
1879
1858
|
}
|
|
1880
1859
|
case "hooks": {
|
|
1881
|
-
const result = await installHooks(plan.target
|
|
1860
|
+
const result = await installHooks(plan.target);
|
|
1882
1861
|
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
1883
1862
|
return { name: "hooks", disposition: "ran" };
|
|
1884
1863
|
}
|
|
@@ -1886,30 +1865,66 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
1886
1865
|
return exhaustiveInitStagePlan(stage);
|
|
1887
1866
|
}
|
|
1888
1867
|
} catch (error) {
|
|
1889
|
-
|
|
1868
|
+
writeStderr(formatInitStageFailure(stageName, error));
|
|
1890
1869
|
return { name: stageName, disposition: "failed" };
|
|
1891
1870
|
}
|
|
1892
1871
|
}
|
|
1893
|
-
function shouldReplaceWritableDirectory(path,
|
|
1872
|
+
function shouldReplaceWritableDirectory(path, _options) {
|
|
1894
1873
|
if (!existsSync3(path)) {
|
|
1895
1874
|
return false;
|
|
1896
1875
|
}
|
|
1897
|
-
if (
|
|
1876
|
+
if (statSync3(path).isDirectory()) {
|
|
1898
1877
|
return false;
|
|
1899
1878
|
}
|
|
1900
|
-
|
|
1901
|
-
throw new Error(t("cli.install.errors.abort-existing", { path }));
|
|
1902
|
-
}
|
|
1903
|
-
return true;
|
|
1879
|
+
return false;
|
|
1904
1880
|
}
|
|
1905
|
-
function
|
|
1881
|
+
function classifyFreshPath(path, strategy) {
|
|
1906
1882
|
if (!existsSync3(path)) {
|
|
1907
|
-
return "
|
|
1883
|
+
return { path, state: "missing" };
|
|
1908
1884
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1885
|
+
let stat;
|
|
1886
|
+
try {
|
|
1887
|
+
stat = statSync3(path);
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
return {
|
|
1890
|
+
path,
|
|
1891
|
+
state: "user-modified",
|
|
1892
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
if (!stat.isFile()) {
|
|
1896
|
+
return { path, state: "user-modified", reason: "expected a file" };
|
|
1897
|
+
}
|
|
1898
|
+
if (strategy === "presence" || strategy === "always-rewrite") {
|
|
1899
|
+
return { path, state: "present-canonical" };
|
|
1900
|
+
}
|
|
1901
|
+
try {
|
|
1902
|
+
const raw = readFileSync2(path, "utf8");
|
|
1903
|
+
const parsed = JSON.parse(raw);
|
|
1904
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1905
|
+
return { path, state: "user-modified", reason: "not a JSON object" };
|
|
1906
|
+
}
|
|
1907
|
+
const record = parsed;
|
|
1908
|
+
const hasRevision = typeof record["revision"] === "string";
|
|
1909
|
+
const hasNodes = record["nodes"] !== void 0 && record["nodes"] !== null && typeof record["nodes"] === "object" && !Array.isArray(record["nodes"]);
|
|
1910
|
+
const hasCounters = record["counters"] !== void 0 && record["counters"] !== null && typeof record["counters"] === "object" && !Array.isArray(record["counters"]);
|
|
1911
|
+
if (!hasRevision || !hasNodes || !hasCounters) {
|
|
1912
|
+
return { path, state: "drifted", reason: "missing required AgentsMeta fields" };
|
|
1913
|
+
}
|
|
1914
|
+
return { path, state: "present-canonical" };
|
|
1915
|
+
} catch (error) {
|
|
1916
|
+
return {
|
|
1917
|
+
path,
|
|
1918
|
+
state: "user-modified",
|
|
1919
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1920
|
+
};
|
|
1911
1921
|
}
|
|
1912
|
-
|
|
1922
|
+
}
|
|
1923
|
+
function diffStateToWriteAction(_state) {
|
|
1924
|
+
return "created";
|
|
1925
|
+
}
|
|
1926
|
+
function formatDiffFileState(state) {
|
|
1927
|
+
return t(`cli.install.diff.state.${state}`);
|
|
1913
1928
|
}
|
|
1914
1929
|
function preparePlannedPath(path, action) {
|
|
1915
1930
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -2048,74 +2063,42 @@ async function selectClaudeMcpScopeInGroup(options) {
|
|
|
2048
2063
|
}
|
|
2049
2064
|
return result;
|
|
2050
2065
|
}
|
|
2051
|
-
function collectLockedWizardStages(args) {
|
|
2052
|
-
const lockedStages = [];
|
|
2053
|
-
if (args.bootstrap === false) {
|
|
2054
|
-
lockedStages.push("bootstrap");
|
|
2055
|
-
}
|
|
2056
|
-
if (args.mcp === false) {
|
|
2057
|
-
lockedStages.push("mcp");
|
|
2058
|
-
}
|
|
2059
|
-
if (args.hooks === false) {
|
|
2060
|
-
lockedStages.push("hooks");
|
|
2061
|
-
}
|
|
2062
|
-
return lockedStages;
|
|
2063
|
-
}
|
|
2064
2066
|
function formatPromptDefault(value) {
|
|
2065
2067
|
return value ? "Y/n" : "y/N";
|
|
2066
2068
|
}
|
|
2067
2069
|
function formatInitModeBanner(options) {
|
|
2068
|
-
if (options.planOnly && options.reapply) {
|
|
2069
|
-
return t("cli.install.plan.mode-banner.plan-reapply");
|
|
2070
|
-
}
|
|
2071
2070
|
if (options.planOnly) {
|
|
2072
2071
|
return t("cli.install.plan.mode-banner.plan");
|
|
2073
2072
|
}
|
|
2074
|
-
if (options.reapply) {
|
|
2075
|
-
return t("cli.install.plan.mode-banner.reapply");
|
|
2076
|
-
}
|
|
2077
2073
|
return t("cli.install.plan.mode-banner.default");
|
|
2078
2074
|
}
|
|
2079
2075
|
function formatInitModeBadge(options) {
|
|
2080
|
-
if (options.planOnly && options.reapply) {
|
|
2081
|
-
return t("cli.install.mode.badge.plan-reapply");
|
|
2082
|
-
}
|
|
2083
2076
|
if (options.planOnly) {
|
|
2084
2077
|
return t("cli.install.mode.badge.plan");
|
|
2085
2078
|
}
|
|
2086
|
-
if (options.reapply) {
|
|
2087
|
-
return t("cli.install.mode.badge.reapply");
|
|
2088
|
-
}
|
|
2089
2079
|
return t("cli.install.mode.badge.default");
|
|
2090
2080
|
}
|
|
2091
|
-
function
|
|
2092
|
-
return
|
|
2081
|
+
function normalizeTarget3(targetInput) {
|
|
2082
|
+
return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
|
|
2093
2083
|
}
|
|
2094
|
-
function
|
|
2095
|
-
if (!existsSync3(target) || !
|
|
2084
|
+
function assertExistingDirectory3(target) {
|
|
2085
|
+
if (!existsSync3(target) || !statSync3(target).isDirectory()) {
|
|
2096
2086
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2097
2087
|
}
|
|
2098
2088
|
}
|
|
2099
2089
|
function detectPackageManager(cwd) {
|
|
2100
2090
|
const workspaceRoot = resolve3(cwd);
|
|
2101
|
-
if (existsSync3(
|
|
2091
|
+
if (existsSync3(join3(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2102
2092
|
return "pnpm";
|
|
2103
2093
|
}
|
|
2104
|
-
if (existsSync3(
|
|
2094
|
+
if (existsSync3(join3(workspaceRoot, "yarn.lock"))) {
|
|
2105
2095
|
return "yarn";
|
|
2106
2096
|
}
|
|
2107
|
-
if (existsSync3(
|
|
2097
|
+
if (existsSync3(join3(workspaceRoot, "package-lock.json"))) {
|
|
2108
2098
|
return "npm";
|
|
2109
2099
|
}
|
|
2110
2100
|
return "npm";
|
|
2111
2101
|
}
|
|
2112
|
-
function resolveMcpInstallMode(rawMode) {
|
|
2113
|
-
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
2114
|
-
return rawMode ?? "global";
|
|
2115
|
-
}
|
|
2116
|
-
writeStderr2(t("cli.install.mcp.install.invalid", { value: rawMode }));
|
|
2117
|
-
return "global";
|
|
2118
|
-
}
|
|
2119
2102
|
function installLocalFabricServer(target, manager) {
|
|
2120
2103
|
const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
|
|
2121
2104
|
childProcess.execFileSync(manager, installArgs, {
|
|
@@ -2131,14 +2114,16 @@ function createInitialMeta() {
|
|
|
2131
2114
|
counters: defaultAgentsMetaCounters()
|
|
2132
2115
|
};
|
|
2133
2116
|
}
|
|
2134
|
-
function
|
|
2117
|
+
function appendInstallDiffLedgerEvent(eventsPath, payload) {
|
|
2135
2118
|
const event = {
|
|
2136
2119
|
kind: "fabric-event",
|
|
2137
2120
|
id: `event:${randomUUID()}`,
|
|
2138
2121
|
ts: Date.now(),
|
|
2139
2122
|
schema_version: 1,
|
|
2140
|
-
event_type: "
|
|
2141
|
-
|
|
2123
|
+
event_type: "install_diff_applied",
|
|
2124
|
+
applied: payload.applied,
|
|
2125
|
+
canonical: payload.canonical,
|
|
2126
|
+
drifted: payload.drifted
|
|
2142
2127
|
};
|
|
2143
2128
|
const line = `${JSON.stringify(event)}
|
|
2144
2129
|
`;
|
|
@@ -2349,7 +2334,7 @@ function reasonLabel() {
|
|
|
2349
2334
|
return paint.human(t("cli.shared.reason"));
|
|
2350
2335
|
}
|
|
2351
2336
|
function overwrittenLabel() {
|
|
2352
|
-
return paint.warn(t("cli.install.
|
|
2337
|
+
return paint.warn(t("cli.install.label.overwritten"));
|
|
2353
2338
|
}
|
|
2354
2339
|
function completedStageLabel() {
|
|
2355
2340
|
return paint.success(t("cli.install.stages.completed"));
|
|
@@ -2360,7 +2345,7 @@ function skippedStageLabel() {
|
|
|
2360
2345
|
function failedStageLabel() {
|
|
2361
2346
|
return paint.error(t("cli.install.stages.failed"));
|
|
2362
2347
|
}
|
|
2363
|
-
function
|
|
2348
|
+
function writeStderr(message) {
|
|
2364
2349
|
process.stderr.write(`${message}
|
|
2365
2350
|
`);
|
|
2366
2351
|
}
|