@fenglimg/fabric-cli 2.0.0-rc.1 → 2.0.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/chunk-6ICJICVU.js +10 -0
- package/dist/chunk-74SZWYPH.js +658 -0
- package/dist/{chunk-UHNP7T7W.js → chunk-EYIDD2YS.js} +346 -86
- package/dist/{chunk-5LOYBXWD.js → chunk-OBQU6NHO.js} +2 -52
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/doctor-T7JWODKG.js +282 -0
- package/dist/hooks-Y74Y5LQS.js +12 -0
- package/dist/index.js +7 -5
- package/dist/{init-DRHUYHYA.js → init-55WZSUK6.js} +212 -271
- package/dist/plan-context-hint-QMUPAXIB.js +98 -0
- package/dist/{scan-HU2EGITF.js → scan-LMK3UCWL.js} +4 -2
- package/dist/{serve-3LXXSBFR.js → serve-H554BHLG.js} +8 -4
- package/package.json +3 -3
- package/templates/bootstrap/CLAUDE.md +1 -1
- package/templates/bootstrap/codex-AGENTS-header.md +1 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +1 -1
- package/templates/hooks/configs/README.md +73 -0
- package/templates/hooks/configs/claude-code.json +37 -0
- package/templates/hooks/configs/codex-hooks.json +20 -0
- package/templates/hooks/configs/cursor-hooks.json +20 -0
- package/templates/hooks/fabric-hint.cjs +1307 -0
- package/templates/hooks/knowledge-hint-broad.cjs +464 -0
- package/templates/hooks/knowledge-hint-narrow.cjs +826 -0
- package/templates/hooks/lib/session-digest-writer.cjs +172 -0
- package/templates/skills/fabric-archive/SKILL.md +486 -0
- package/templates/skills/fabric-import/SKILL.md +588 -0
- package/templates/skills/fabric-review/SKILL.md +382 -0
- package/dist/doctor-DUHWLAYD.js +0 -98
|
@@ -1,172 +1,77 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ClaudeCodeCLIWriter,
|
|
4
|
+
CursorWriter,
|
|
5
|
+
addArchiveSkillPointer,
|
|
6
|
+
createServerEntry,
|
|
7
|
+
hooksCommand,
|
|
8
|
+
installArchiveHintHook,
|
|
9
|
+
installFabricArchiveSkill,
|
|
10
|
+
installFabricImportSkill,
|
|
11
|
+
installFabricReviewSkill,
|
|
12
|
+
installHooks,
|
|
13
|
+
installKnowledgeHintBroadHook,
|
|
14
|
+
installKnowledgeHintNarrowHook,
|
|
15
|
+
mergeClaudeCodeHookConfig,
|
|
16
|
+
mergeCodexHookConfig,
|
|
17
|
+
mergeCursorHookConfig,
|
|
18
|
+
normalizeConfigPath,
|
|
19
|
+
writeJsonClientConfig
|
|
20
|
+
} from "./chunk-74SZWYPH.js";
|
|
2
21
|
import {
|
|
3
22
|
detectFramework,
|
|
4
23
|
runInitScan
|
|
5
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-EYIDD2YS.js";
|
|
6
25
|
import {
|
|
7
|
-
createDebugLogger,
|
|
8
26
|
displayWidth,
|
|
9
27
|
padEnd,
|
|
10
|
-
paint
|
|
11
|
-
|
|
28
|
+
paint
|
|
29
|
+
} from "./chunk-WWNXR34K.js";
|
|
30
|
+
import {
|
|
12
31
|
t
|
|
13
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-6ICJICVU.js";
|
|
33
|
+
import {
|
|
34
|
+
createDebugLogger,
|
|
35
|
+
resolveDevMode
|
|
36
|
+
} from "./chunk-OBQU6NHO.js";
|
|
14
37
|
|
|
15
38
|
// src/commands/init.ts
|
|
16
39
|
import { randomUUID } from "crypto";
|
|
17
|
-
import { homedir as
|
|
40
|
+
import { homedir as homedir4 } from "os";
|
|
18
41
|
import * as childProcess from "child_process";
|
|
19
|
-
import { appendFileSync, existsSync as
|
|
20
|
-
import { dirname as
|
|
42
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
43
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "path";
|
|
21
44
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
22
45
|
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
23
|
-
import { atomicWriteJson
|
|
24
|
-
import { defineCommand as
|
|
46
|
+
import { atomicWriteJson, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
47
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
25
48
|
import { checkLockOrThrow } from "@fenglimg/fabric-server";
|
|
26
49
|
|
|
27
50
|
// src/commands/config.ts
|
|
28
|
-
import { existsSync as
|
|
29
|
-
import { readFile as
|
|
30
|
-
import { resolve as
|
|
51
|
+
import { existsSync as existsSync4 } from "fs";
|
|
52
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
53
|
+
import { resolve as resolve3 } from "path";
|
|
31
54
|
import { fileURLToPath } from "url";
|
|
32
|
-
import { defineCommand
|
|
55
|
+
import { defineCommand } from "citty";
|
|
33
56
|
|
|
34
57
|
// src/config/resolver.ts
|
|
35
|
-
import { existsSync as
|
|
36
|
-
import { join as
|
|
37
|
-
import { homedir as
|
|
58
|
+
import { existsSync as existsSync3 } from "fs";
|
|
59
|
+
import { join as join3 } from "path";
|
|
60
|
+
import { homedir as homedir3 } from "os";
|
|
38
61
|
|
|
39
62
|
// src/config/claude-code.ts
|
|
40
|
-
import { existsSync as existsSync2 } from "fs";
|
|
41
|
-
import { join as join2, resolve as resolve2 } from "path";
|
|
42
|
-
import { homedir as homedir2, platform } from "os";
|
|
43
|
-
|
|
44
|
-
// src/config/json.ts
|
|
45
63
|
import { existsSync } from "fs";
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import { homedir } from "os";
|
|
49
|
-
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
50
|
-
|
|
51
|
-
// src/config/writer.ts
|
|
52
|
-
function createServerEntry(serverPath) {
|
|
53
|
-
return {
|
|
54
|
-
command: process.execPath,
|
|
55
|
-
args: [serverPath]
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/config/json.ts
|
|
60
|
-
function deepMerge(target, source) {
|
|
61
|
-
if (target === null || typeof target !== "object" || Array.isArray(target) || source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
62
|
-
return source;
|
|
63
|
-
}
|
|
64
|
-
const out = { ...target };
|
|
65
|
-
for (const key of Object.keys(source)) {
|
|
66
|
-
out[key] = deepMerge(
|
|
67
|
-
target[key],
|
|
68
|
-
source[key]
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
function expandHome(filePath) {
|
|
74
|
-
if (filePath === "~") {
|
|
75
|
-
return homedir();
|
|
76
|
-
}
|
|
77
|
-
if (filePath.startsWith("~/")) {
|
|
78
|
-
return join(homedir(), filePath.slice(2));
|
|
79
|
-
}
|
|
80
|
-
return filePath;
|
|
81
|
-
}
|
|
82
|
-
function normalizeConfigPath(filePath) {
|
|
83
|
-
return resolve(expandHome(filePath));
|
|
84
|
-
}
|
|
85
|
-
async function readJsonConfig(configPath) {
|
|
86
|
-
try {
|
|
87
|
-
const raw = await readFile(configPath, "utf8");
|
|
88
|
-
if (raw.trim().length === 0) {
|
|
89
|
-
return {};
|
|
90
|
-
}
|
|
91
|
-
const parsed = JSON.parse(raw);
|
|
92
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
93
|
-
throw new Error(`Expected JSON object in ${configPath}`);
|
|
94
|
-
}
|
|
95
|
-
return parsed;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
98
|
-
return {};
|
|
99
|
-
}
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function writeJsonClientConfig(configPath, serverEntry) {
|
|
104
|
-
const existing = await readJsonConfig(configPath);
|
|
105
|
-
const merged = deepMerge(existing, { mcpServers: { fabric: serverEntry } });
|
|
106
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
107
|
-
await atomicWriteJson(configPath, merged, { indent: 2 });
|
|
108
|
-
}
|
|
109
|
-
var JsonClientConfigWriter = class {
|
|
110
|
-
configuredPath;
|
|
111
|
-
constructor(configuredPath) {
|
|
112
|
-
this.configuredPath = configuredPath;
|
|
113
|
-
}
|
|
114
|
-
async detect(workspaceRoot, overridePath) {
|
|
115
|
-
const explicitPath = overridePath ?? this.configuredPath;
|
|
116
|
-
if (explicitPath !== void 0) {
|
|
117
|
-
return normalizeConfigPath(explicitPath);
|
|
118
|
-
}
|
|
119
|
-
const configPath = this.defaultPath(workspaceRoot);
|
|
120
|
-
return configPath === null ? null : normalizeConfigPath(configPath);
|
|
121
|
-
}
|
|
122
|
-
async write(serverPath, workspaceRoot, overridePath) {
|
|
123
|
-
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
124
|
-
if (configPath === null) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
await writeJsonClientConfig(configPath, createServerEntry(serverPath));
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
var ClaudeCodeCLIWriter = class extends JsonClientConfigWriter {
|
|
131
|
-
clientKind = "ClaudeCodeCLI";
|
|
132
|
-
scope;
|
|
133
|
-
constructor(configuredPath, scope = "project") {
|
|
134
|
-
super(configuredPath);
|
|
135
|
-
this.scope = scope;
|
|
136
|
-
}
|
|
137
|
-
// Writes to project-level .mcp.json (per Claude Code MCP spec) by default,
|
|
138
|
-
// or ~/.claude.json for user scope.
|
|
139
|
-
// Detection still checks ~/.claude to confirm Claude Code is installed.
|
|
140
|
-
defaultPath(workspaceRoot) {
|
|
141
|
-
const globalClaudeDir = join(homedir(), ".claude");
|
|
142
|
-
const projectClaudeDir = join(workspaceRoot, ".claude");
|
|
143
|
-
if (!existsSync(globalClaudeDir) && !existsSync(projectClaudeDir)) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
return this.scope === "user" ? join(homedir(), ".claude.json") : join(workspaceRoot, ".mcp.json");
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
var CursorWriter = class extends JsonClientConfigWriter {
|
|
150
|
-
clientKind = "Cursor";
|
|
151
|
-
constructor(configuredPath) {
|
|
152
|
-
super(configuredPath);
|
|
153
|
-
}
|
|
154
|
-
defaultPath(workspaceRoot) {
|
|
155
|
-
const cursorDir = join(workspaceRoot, ".cursor");
|
|
156
|
-
return existsSync(cursorDir) ? join(cursorDir, "mcp.json") : null;
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// src/config/claude-code.ts
|
|
64
|
+
import { join, resolve } from "path";
|
|
65
|
+
import { homedir, platform } from "os";
|
|
161
66
|
function getClaudeDesktopConfigPath() {
|
|
162
67
|
const os = platform();
|
|
163
68
|
if (os === "darwin") {
|
|
164
|
-
return
|
|
69
|
+
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
165
70
|
}
|
|
166
71
|
if (os === "win32") {
|
|
167
|
-
return
|
|
72
|
+
return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
168
73
|
}
|
|
169
|
-
return
|
|
74
|
+
return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
170
75
|
}
|
|
171
76
|
var ClaudeCodeDesktopWriter = class {
|
|
172
77
|
clientKind = "ClaudeCodeDesktop";
|
|
@@ -176,7 +81,7 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
176
81
|
}
|
|
177
82
|
async detect(_workspaceRoot, overridePath) {
|
|
178
83
|
const configPath = normalizeConfigPath(overridePath ?? this.configuredPath ?? getClaudeDesktopConfigPath());
|
|
179
|
-
return
|
|
84
|
+
return existsSync(configPath) || overridePath !== void 0 || this.configuredPath !== void 0 ? configPath : null;
|
|
180
85
|
}
|
|
181
86
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
182
87
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -191,17 +96,17 @@ var ClaudeCodeDesktopWriter = class {
|
|
|
191
96
|
};
|
|
192
97
|
|
|
193
98
|
// src/config/toml.ts
|
|
194
|
-
import { existsSync as
|
|
195
|
-
import { mkdir
|
|
196
|
-
import { dirname
|
|
197
|
-
import { homedir as
|
|
99
|
+
import { existsSync as existsSync2 } from "fs";
|
|
100
|
+
import { mkdir, readFile } from "fs/promises";
|
|
101
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
102
|
+
import { homedir as homedir2 } from "os";
|
|
198
103
|
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
199
|
-
function
|
|
104
|
+
function expandHome(filePath) {
|
|
200
105
|
if (filePath === "~") {
|
|
201
|
-
return
|
|
106
|
+
return homedir2();
|
|
202
107
|
}
|
|
203
108
|
if (filePath.startsWith("~/")) {
|
|
204
|
-
return
|
|
109
|
+
return join2(homedir2(), filePath.slice(2));
|
|
205
110
|
}
|
|
206
111
|
return filePath;
|
|
207
112
|
}
|
|
@@ -250,7 +155,7 @@ ${block}`;
|
|
|
250
155
|
}
|
|
251
156
|
async function readTomlConfigText(configPath) {
|
|
252
157
|
try {
|
|
253
|
-
return await
|
|
158
|
+
return await readFile(configPath, "utf8");
|
|
254
159
|
} catch (error) {
|
|
255
160
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
256
161
|
return "";
|
|
@@ -267,10 +172,10 @@ var CodexTOMLConfigWriter = class {
|
|
|
267
172
|
async detect(_workspaceRoot, overridePath) {
|
|
268
173
|
const explicitPath = overridePath ?? this.configuredPath;
|
|
269
174
|
if (explicitPath !== void 0) {
|
|
270
|
-
return
|
|
175
|
+
return resolve2(expandHome(explicitPath));
|
|
271
176
|
}
|
|
272
|
-
const codexDir =
|
|
273
|
-
return
|
|
177
|
+
const codexDir = join2(homedir2(), ".codex");
|
|
178
|
+
return existsSync2(codexDir) ? resolve2(join2(codexDir, "config.toml")) : null;
|
|
274
179
|
}
|
|
275
180
|
async write(serverPath, workspaceRoot, overridePath) {
|
|
276
181
|
const configPath = await this.detect(workspaceRoot, overridePath);
|
|
@@ -279,7 +184,7 @@ var CodexTOMLConfigWriter = class {
|
|
|
279
184
|
}
|
|
280
185
|
const rawConfig = await readTomlConfigText(configPath);
|
|
281
186
|
const nextConfig = upsertCodexServerBlock(rawConfig, "fabric", createServerEntry(serverPath));
|
|
282
|
-
await
|
|
187
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
283
188
|
await atomicWriteText(configPath, nextConfig);
|
|
284
189
|
}
|
|
285
190
|
};
|
|
@@ -300,25 +205,25 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
300
205
|
const claudeMcpScope = opts.claudeMcpScope ?? "project";
|
|
301
206
|
addIfDetected(
|
|
302
207
|
writers,
|
|
303
|
-
|
|
208
|
+
existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude")),
|
|
304
209
|
(configuredPath) => new ClaudeCodeCLIWriter(configuredPath, claudeMcpScope),
|
|
305
210
|
hasExplicitPath(clientPaths, "claudeCodeCLI") ? clientPaths.claudeCodeCLI : void 0
|
|
306
211
|
);
|
|
307
212
|
addIfDetected(
|
|
308
213
|
writers,
|
|
309
|
-
|
|
214
|
+
existsSync3(getClaudeDesktopConfigPath()),
|
|
310
215
|
(configuredPath) => new ClaudeCodeDesktopWriter(configuredPath),
|
|
311
216
|
hasExplicitPath(clientPaths, "claudeCodeDesktop") ? clientPaths.claudeCodeDesktop : void 0
|
|
312
217
|
);
|
|
313
218
|
addIfDetected(
|
|
314
219
|
writers,
|
|
315
|
-
|
|
220
|
+
existsSync3(join3(workspaceRoot, ".cursor")),
|
|
316
221
|
(configuredPath) => new CursorWriter(configuredPath),
|
|
317
222
|
hasExplicitPath(clientPaths, "cursor") ? clientPaths.cursor : void 0
|
|
318
223
|
);
|
|
319
224
|
addIfDetected(
|
|
320
225
|
writers,
|
|
321
|
-
|
|
226
|
+
existsSync3(join3(homedir3(), ".codex")),
|
|
322
227
|
(configuredPath) => new CodexTOMLConfigWriter(configuredPath),
|
|
323
228
|
hasExplicitPath(clientPaths, "codexCLI") ? clientPaths.codexCLI : void 0
|
|
324
229
|
);
|
|
@@ -326,10 +231,10 @@ function resolveClients(workspaceRoot, fabricConfig = {}, opts = {}) {
|
|
|
326
231
|
}
|
|
327
232
|
function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
328
233
|
const clientPaths = fabricConfig.clientPaths;
|
|
329
|
-
const claudeDetected =
|
|
330
|
-
const claudeDesktopDetected =
|
|
331
|
-
const cursorDetected =
|
|
332
|
-
const codexDetected =
|
|
234
|
+
const claudeDetected = existsSync3(join3(homedir3(), ".claude")) || existsSync3(join3(workspaceRoot, ".claude"));
|
|
235
|
+
const claudeDesktopDetected = existsSync3(getClaudeDesktopConfigPath());
|
|
236
|
+
const cursorDetected = existsSync3(join3(workspaceRoot, ".cursor"));
|
|
237
|
+
const codexDetected = existsSync3(join3(homedir3(), ".codex"));
|
|
333
238
|
return [
|
|
334
239
|
{
|
|
335
240
|
clientKind: "ClaudeCodeCLI",
|
|
@@ -387,7 +292,7 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
387
292
|
skill: true
|
|
388
293
|
},
|
|
389
294
|
installedCapabilities: {
|
|
390
|
-
hook:
|
|
295
|
+
hook: existsSync3(join3(workspaceRoot, ".codex", "hooks.json")),
|
|
391
296
|
// v2/rc.2: v1 client-side init skill removed; skill-installation probes
|
|
392
297
|
// will return once rc.2/3/4 introduce the v2 skills (fabric-archive,
|
|
393
298
|
// fabric-review, fabric-import). Until then there is nothing to probe.
|
|
@@ -397,50 +302,6 @@ function detectClientSupports(workspaceRoot, fabricConfig = {}) {
|
|
|
397
302
|
];
|
|
398
303
|
}
|
|
399
304
|
|
|
400
|
-
// src/commands/hooks.ts
|
|
401
|
-
import { existsSync as existsSync5, statSync } from "fs";
|
|
402
|
-
import { isAbsolute, resolve as resolve4 } from "path";
|
|
403
|
-
import { defineCommand } from "citty";
|
|
404
|
-
var hooksCommand = defineCommand({
|
|
405
|
-
meta: {
|
|
406
|
-
name: "hooks",
|
|
407
|
-
description: t("cli.hooks.description")
|
|
408
|
-
},
|
|
409
|
-
subCommands: {
|
|
410
|
-
install: defineCommand({
|
|
411
|
-
meta: {
|
|
412
|
-
name: "install",
|
|
413
|
-
description: t("cli.hooks.install.description")
|
|
414
|
-
},
|
|
415
|
-
args: {
|
|
416
|
-
target: {
|
|
417
|
-
type: "string",
|
|
418
|
-
description: t("cli.hooks.install.args.target.description"),
|
|
419
|
-
default: process.cwd()
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
async run({ args }) {
|
|
423
|
-
await installHooks(args.target);
|
|
424
|
-
}
|
|
425
|
-
})
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
async function installHooks(target, _options = {}) {
|
|
429
|
-
const normalizedTarget = normalizeTarget(target);
|
|
430
|
-
assertExistingDirectory(normalizedTarget);
|
|
431
|
-
throw new Error(
|
|
432
|
-
`fab hooks install is not available in v2 (husky pre-commit template removed). Use per-client hooks under .claude/ and .codex/ instead. Target: ${normalizedTarget}`
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
function normalizeTarget(targetInput) {
|
|
436
|
-
return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
|
|
437
|
-
}
|
|
438
|
-
function assertExistingDirectory(target) {
|
|
439
|
-
if (!existsSync5(target) || !statSync(target).isDirectory()) {
|
|
440
|
-
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
305
|
// src/commands/config.ts
|
|
445
306
|
var CLIENT_ALIASES = {
|
|
446
307
|
claude: "ClaudeCodeCLI",
|
|
@@ -471,11 +332,11 @@ function parseClientFilter(value) {
|
|
|
471
332
|
return clients;
|
|
472
333
|
}
|
|
473
334
|
async function loadFabricConfig(workspaceRoot) {
|
|
474
|
-
const configPath =
|
|
475
|
-
if (!
|
|
335
|
+
const configPath = resolve3(workspaceRoot, "fabric.config.json");
|
|
336
|
+
if (!existsSync4(configPath)) {
|
|
476
337
|
return {};
|
|
477
338
|
}
|
|
478
|
-
const parsed = JSON.parse(await
|
|
339
|
+
const parsed = JSON.parse(await readFile2(configPath, "utf8"));
|
|
479
340
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
480
341
|
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
481
342
|
}
|
|
@@ -483,21 +344,21 @@ async function loadFabricConfig(workspaceRoot) {
|
|
|
483
344
|
}
|
|
484
345
|
function resolveServerPath(override) {
|
|
485
346
|
if (override) return override;
|
|
486
|
-
if (process.env.FAB_SERVER_PATH) return
|
|
347
|
+
if (process.env.FAB_SERVER_PATH) return resolve3(process.env.FAB_SERVER_PATH);
|
|
487
348
|
return fileURLToPath(import.meta.resolve("@fenglimg/fabric-server"));
|
|
488
349
|
}
|
|
489
350
|
function writeStderr(message) {
|
|
490
351
|
process.stderr.write(`${message}
|
|
491
352
|
`);
|
|
492
353
|
}
|
|
493
|
-
var configCmd =
|
|
354
|
+
var configCmd = defineCommand({
|
|
494
355
|
meta: {
|
|
495
356
|
name: "config",
|
|
496
357
|
description: t("cli.config.description")
|
|
497
358
|
},
|
|
498
359
|
subCommands: {
|
|
499
360
|
hooks: hooksCommand,
|
|
500
|
-
install:
|
|
361
|
+
install: defineCommand({
|
|
501
362
|
meta: {
|
|
502
363
|
name: "install",
|
|
503
364
|
description: t("cli.config.install.description")
|
|
@@ -541,7 +402,7 @@ var configCmd = defineCommand2({
|
|
|
541
402
|
}
|
|
542
403
|
});
|
|
543
404
|
async function installMcpClients(target, options = {}) {
|
|
544
|
-
const workspaceRoot =
|
|
405
|
+
const workspaceRoot = resolve3(target);
|
|
545
406
|
const fabricConfig = await loadFabricConfig(workspaceRoot);
|
|
546
407
|
const selectedClients = options.clients === void 0 ? null : new Set(options.clients);
|
|
547
408
|
const serverPath = resolveServerPath(options.localServerPath);
|
|
@@ -572,9 +433,9 @@ async function installMcpClients(target, options = {}) {
|
|
|
572
433
|
|
|
573
434
|
// src/scanner/forensic.ts
|
|
574
435
|
import { execFileSync } from "child_process";
|
|
575
|
-
import { existsSync as
|
|
436
|
+
import { existsSync as existsSync5, readdirSync, readFileSync, statSync } from "fs";
|
|
576
437
|
import { createRequire } from "module";
|
|
577
|
-
import { basename, extname, isAbsolute
|
|
438
|
+
import { basename, extname, isAbsolute, join as join4, posix, relative, resolve as resolve4, sep } from "path";
|
|
578
439
|
import {
|
|
579
440
|
forensicReportSchema
|
|
580
441
|
} from "@fenglimg/fabric-shared";
|
|
@@ -660,7 +521,7 @@ var parserInitPromise = null;
|
|
|
660
521
|
var languagePromiseByKind = {};
|
|
661
522
|
var parserBundlePromiseByKind = {};
|
|
662
523
|
async function buildForensicReport(targetInput) {
|
|
663
|
-
const target =
|
|
524
|
+
const target = normalizeTarget(targetInput);
|
|
664
525
|
const framework = detectFramework(target);
|
|
665
526
|
const topology = buildTopology(target);
|
|
666
527
|
const entryPoints = collectEntryPoints(target, topology.files);
|
|
@@ -696,11 +557,11 @@ async function buildForensicReport(targetInput) {
|
|
|
696
557
|
}
|
|
697
558
|
return validation.data;
|
|
698
559
|
}
|
|
699
|
-
function
|
|
700
|
-
return
|
|
560
|
+
function normalizeTarget(targetInput) {
|
|
561
|
+
return isAbsolute(targetInput) ? targetInput : resolve4(process.cwd(), targetInput);
|
|
701
562
|
}
|
|
702
563
|
function buildTopology(root) {
|
|
703
|
-
|
|
564
|
+
assertExistingDirectory(root);
|
|
704
565
|
const byExt = {};
|
|
705
566
|
const keyDirs = /* @__PURE__ */ new Set();
|
|
706
567
|
const files = [];
|
|
@@ -713,7 +574,7 @@ function buildTopology(root) {
|
|
|
713
574
|
continue;
|
|
714
575
|
}
|
|
715
576
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
716
|
-
const absolutePath =
|
|
577
|
+
const absolutePath = join4(current, entry.name);
|
|
717
578
|
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
718
579
|
if (relativePath.length === 0) {
|
|
719
580
|
continue;
|
|
@@ -733,7 +594,7 @@ function buildTopology(root) {
|
|
|
733
594
|
if (!entry.isFile()) {
|
|
734
595
|
continue;
|
|
735
596
|
}
|
|
736
|
-
const stats =
|
|
597
|
+
const stats = statSync(absolutePath);
|
|
737
598
|
const extension = extname(entry.name) || "[none]";
|
|
738
599
|
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
739
600
|
totalFiles += 1;
|
|
@@ -751,8 +612,8 @@ function buildTopology(root) {
|
|
|
751
612
|
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
752
613
|
};
|
|
753
614
|
}
|
|
754
|
-
function
|
|
755
|
-
if (!
|
|
615
|
+
function assertExistingDirectory(target) {
|
|
616
|
+
if (!existsSync5(target) || !statSync(target).isDirectory()) {
|
|
756
617
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
757
618
|
}
|
|
758
619
|
}
|
|
@@ -801,7 +662,7 @@ function getEntryPointReason(relativePath) {
|
|
|
801
662
|
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
802
663
|
const samples = [];
|
|
803
664
|
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
804
|
-
const absolutePath =
|
|
665
|
+
const absolutePath = join4(target, ...entryPoint.path.split("/"));
|
|
805
666
|
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
806
667
|
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
807
668
|
frameworkKind,
|
|
@@ -838,8 +699,8 @@ function readFirstLines(path, lineLimit) {
|
|
|
838
699
|
}
|
|
839
700
|
}
|
|
840
701
|
function readPackageDependencies(target) {
|
|
841
|
-
const packageJsonPath =
|
|
842
|
-
if (!
|
|
702
|
+
const packageJsonPath = join4(target, "package.json");
|
|
703
|
+
if (!existsSync5(packageJsonPath)) {
|
|
843
704
|
return /* @__PURE__ */ new Map();
|
|
844
705
|
}
|
|
845
706
|
try {
|
|
@@ -1179,9 +1040,9 @@ function scoreFrameworkConfidence(input) {
|
|
|
1179
1040
|
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1180
1041
|
}
|
|
1181
1042
|
function readReadmeInfo(target) {
|
|
1182
|
-
const readmePath =
|
|
1183
|
-
const hasContributing =
|
|
1184
|
-
if (!
|
|
1043
|
+
const readmePath = join4(target, "README.md");
|
|
1044
|
+
const hasContributing = existsSync5(join4(target, "CONTRIBUTING.md"));
|
|
1045
|
+
if (!existsSync5(readmePath)) {
|
|
1185
1046
|
return {
|
|
1186
1047
|
quality: "missing",
|
|
1187
1048
|
line_count: 0,
|
|
@@ -1666,8 +1527,8 @@ function buildSkillRecommendations(frameworkKind, topology, readme) {
|
|
|
1666
1527
|
return recommendations;
|
|
1667
1528
|
}
|
|
1668
1529
|
function readProjectName(target) {
|
|
1669
|
-
const packageJsonPath =
|
|
1670
|
-
if (
|
|
1530
|
+
const packageJsonPath = join4(target, "package.json");
|
|
1531
|
+
if (existsSync5(packageJsonPath)) {
|
|
1671
1532
|
try {
|
|
1672
1533
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1673
1534
|
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
@@ -1680,7 +1541,7 @@ function readProjectName(target) {
|
|
|
1680
1541
|
return basename(target);
|
|
1681
1542
|
}
|
|
1682
1543
|
function getCliVersion() {
|
|
1683
|
-
return true ? "2.0.0-rc.
|
|
1544
|
+
return true ? "2.0.0-rc.8" : "unknown";
|
|
1684
1545
|
}
|
|
1685
1546
|
function sortRecord(record) {
|
|
1686
1547
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -1699,10 +1560,10 @@ Run \`fabric doctor\` to verify state.
|
|
|
1699
1560
|
|
|
1700
1561
|
See \`.fabric/knowledge/\` for project decisions, pitfalls, guidelines, models, and processes.
|
|
1701
1562
|
`;
|
|
1702
|
-
var LOCAL_FABRIC_SERVER_PATH =
|
|
1563
|
+
var LOCAL_FABRIC_SERVER_PATH = join5("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1703
1564
|
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1704
1565
|
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1705
|
-
var initCommand =
|
|
1566
|
+
var initCommand = defineCommand2({
|
|
1706
1567
|
meta: {
|
|
1707
1568
|
name: "init",
|
|
1708
1569
|
description: t("cli.init.description")
|
|
@@ -1806,10 +1667,44 @@ async function runInitCommand(args) {
|
|
|
1806
1667
|
process.exitCode = 130;
|
|
1807
1668
|
return;
|
|
1808
1669
|
}
|
|
1809
|
-
|
|
1670
|
+
const result = await executeInitExecutionPlan(plan);
|
|
1671
|
+
try {
|
|
1672
|
+
await maybeWriteImportSentinel({
|
|
1673
|
+
target: intent.target,
|
|
1674
|
+
planOnly: intent.options.planOnly === true,
|
|
1675
|
+
wizardEnabled: intent.wizardEnabled,
|
|
1676
|
+
terminalInteractive: intent.interactiveSummary
|
|
1677
|
+
});
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
if (!intent.options.planOnly) {
|
|
1681
|
+
console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
|
|
1682
|
+
}
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1685
|
+
async function maybeWriteImportSentinel(opts) {
|
|
1686
|
+
if (opts.planOnly) return;
|
|
1687
|
+
if (process.env.FABRIC_NONINTERACTIVE === "1") return;
|
|
1688
|
+
if (!opts.wizardEnabled || !opts.terminalInteractive) return;
|
|
1689
|
+
if (!Boolean(process.stdin.isTTY)) return;
|
|
1690
|
+
const answer = await confirm({
|
|
1691
|
+
message: "\u4E0B\u6B21\u5F00 AI \u65F6\u8BA9\u6211\u4ECE git log \u62BD\u66F4\u591A\u77E5\u8BC6\u5417?",
|
|
1692
|
+
initialValue: true
|
|
1693
|
+
});
|
|
1694
|
+
if (isCancel(answer) || answer !== true) return;
|
|
1695
|
+
const fabricDir = join5(opts.target, ".fabric");
|
|
1696
|
+
const sentinelPath = join5(fabricDir, ".import-requested");
|
|
1697
|
+
try {
|
|
1698
|
+
if (!existsSync6(fabricDir)) {
|
|
1699
|
+
mkdirSync(fabricDir, { recursive: true });
|
|
1700
|
+
}
|
|
1701
|
+
writeFileSync(sentinelPath, "", "utf8");
|
|
1702
|
+
log.success("\u4E0B\u6B21\u5F00 AI \u4F1A\u770B\u5230\u63D0\u793A");
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
1810
1705
|
}
|
|
1811
1706
|
function resolveInitCliIntent(args, targetInput) {
|
|
1812
|
-
const target =
|
|
1707
|
+
const target = normalizeTarget2(targetInput);
|
|
1813
1708
|
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
1814
1709
|
const claudeMcpScope = resolveClaudeMcpScope(args.scope);
|
|
1815
1710
|
const terminalInteractive = isInteractiveInit();
|
|
@@ -1929,20 +1824,20 @@ async function executeInitExecutionPlan(plan) {
|
|
|
1929
1824
|
}
|
|
1930
1825
|
var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
|
|
1931
1826
|
function resolvePersonalFabricRoot() {
|
|
1932
|
-
return process.env.FABRIC_HOME ??
|
|
1827
|
+
return process.env.FABRIC_HOME ?? homedir4();
|
|
1933
1828
|
}
|
|
1934
1829
|
async function buildInitFabricPlan(target, options) {
|
|
1935
|
-
|
|
1936
|
-
const fabricDir =
|
|
1937
|
-
const agentsMdPath =
|
|
1938
|
-
const agentsMdAction =
|
|
1939
|
-
const knowledgeDir =
|
|
1940
|
-
const personalKnowledgeDir =
|
|
1941
|
-
const forensicPath =
|
|
1942
|
-
const eventsPath =
|
|
1943
|
-
const metaPath =
|
|
1830
|
+
assertExistingDirectory2(target);
|
|
1831
|
+
const fabricDir = join5(target, ".fabric");
|
|
1832
|
+
const agentsMdPath = join5(target, "AGENTS.md");
|
|
1833
|
+
const agentsMdAction = existsSync6(agentsMdPath) ? "preserved" : "created";
|
|
1834
|
+
const knowledgeDir = join5(fabricDir, "knowledge");
|
|
1835
|
+
const personalKnowledgeDir = join5(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1836
|
+
const forensicPath = join5(fabricDir, "forensic.json");
|
|
1837
|
+
const eventsPath = join5(fabricDir, "events.jsonl");
|
|
1838
|
+
const metaPath = join5(fabricDir, "agents.meta.json");
|
|
1944
1839
|
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1945
|
-
const knowledgeDirAction =
|
|
1840
|
+
const knowledgeDirAction = existsSync6(knowledgeDir) ? "overwritten" : "created";
|
|
1946
1841
|
const metaAction = planFreshPath(metaPath, options);
|
|
1947
1842
|
const eventsAction = planFreshPath(eventsPath, options);
|
|
1948
1843
|
const forensicAction = planFreshPath(forensicPath, options);
|
|
@@ -1974,30 +1869,30 @@ async function executeInitFabricPlan(plan) {
|
|
|
1974
1869
|
rmSync(plan.fabricDir, { force: true });
|
|
1975
1870
|
}
|
|
1976
1871
|
mkdirSync(plan.fabricDir, { recursive: true });
|
|
1977
|
-
if (plan.agentsMdAction === "created" && !
|
|
1872
|
+
if (plan.agentsMdAction === "created" && !existsSync6(plan.agentsMdPath)) {
|
|
1978
1873
|
await atomicWriteText2(plan.agentsMdPath, AGENTS_MD_DEFAULT_CONTENT);
|
|
1979
1874
|
}
|
|
1980
1875
|
mkdirSync(plan.knowledgeDir, { recursive: true });
|
|
1981
1876
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1982
|
-
const teamSubDir =
|
|
1877
|
+
const teamSubDir = join5(plan.knowledgeDir, sub);
|
|
1983
1878
|
mkdirSync(teamSubDir, { recursive: true });
|
|
1984
|
-
const teamGitkeep =
|
|
1985
|
-
if (!
|
|
1879
|
+
const teamGitkeep = join5(teamSubDir, ".gitkeep");
|
|
1880
|
+
if (!existsSync6(teamGitkeep)) {
|
|
1986
1881
|
writeFileSync(teamGitkeep, "", "utf8");
|
|
1987
1882
|
}
|
|
1988
1883
|
}
|
|
1989
1884
|
try {
|
|
1990
1885
|
mkdirSync(plan.personalKnowledgeDir, { recursive: true });
|
|
1991
1886
|
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
1992
|
-
mkdirSync(
|
|
1887
|
+
mkdirSync(join5(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
1993
1888
|
}
|
|
1994
1889
|
} catch {
|
|
1995
1890
|
}
|
|
1996
1891
|
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
1997
|
-
await
|
|
1892
|
+
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
1998
1893
|
if (isReapply) {
|
|
1999
|
-
if (!
|
|
2000
|
-
mkdirSync(
|
|
1894
|
+
if (!existsSync6(plan.eventsPath)) {
|
|
1895
|
+
mkdirSync(dirname2(plan.eventsPath), { recursive: true });
|
|
2001
1896
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2002
1897
|
}
|
|
2003
1898
|
} else {
|
|
@@ -2005,7 +1900,7 @@ async function executeInitFabricPlan(plan) {
|
|
|
2005
1900
|
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2006
1901
|
}
|
|
2007
1902
|
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
2008
|
-
await
|
|
1903
|
+
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
2009
1904
|
if (!plan.options?.reapply) {
|
|
2010
1905
|
try {
|
|
2011
1906
|
await runInitScan(plan.target, { source: "init" });
|
|
@@ -2139,8 +2034,28 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2139
2034
|
try {
|
|
2140
2035
|
switch (stage.name) {
|
|
2141
2036
|
case "bootstrap": {
|
|
2142
|
-
|
|
2143
|
-
|
|
2037
|
+
const installResults = [];
|
|
2038
|
+
installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
|
|
2039
|
+
installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
|
|
2040
|
+
installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
|
|
2041
|
+
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
2042
|
+
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
2043
|
+
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
2044
|
+
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
2045
|
+
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
2046
|
+
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
2047
|
+
installResults.push(...await runBestEffort("pointer", () => addArchiveSkillPointer(plan.target)));
|
|
2048
|
+
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
2049
|
+
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
2050
|
+
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
2051
|
+
for (const result of installResults) {
|
|
2052
|
+
if (result.status === "error") {
|
|
2053
|
+
writeStderr2(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
2057
|
+
console.log(formatInitStageResult("bootstrap", "completed", installedCount, skippedCount, note2));
|
|
2058
|
+
return { name: "bootstrap", disposition: "ran" };
|
|
2144
2059
|
}
|
|
2145
2060
|
case "mcp": {
|
|
2146
2061
|
if (stage.installMode === "local") {
|
|
@@ -2178,10 +2093,10 @@ async function executeInitStagePlan(plan, stageName) {
|
|
|
2178
2093
|
}
|
|
2179
2094
|
}
|
|
2180
2095
|
function shouldReplaceWritableDirectory(path, options) {
|
|
2181
|
-
if (!
|
|
2096
|
+
if (!existsSync6(path)) {
|
|
2182
2097
|
return false;
|
|
2183
2098
|
}
|
|
2184
|
-
if (
|
|
2099
|
+
if (statSync2(path).isDirectory()) {
|
|
2185
2100
|
return false;
|
|
2186
2101
|
}
|
|
2187
2102
|
if (!options?.force) {
|
|
@@ -2190,7 +2105,7 @@ function shouldReplaceWritableDirectory(path, options) {
|
|
|
2190
2105
|
return true;
|
|
2191
2106
|
}
|
|
2192
2107
|
function planFreshPath(path, options) {
|
|
2193
|
-
if (!
|
|
2108
|
+
if (!existsSync6(path)) {
|
|
2194
2109
|
return "created";
|
|
2195
2110
|
}
|
|
2196
2111
|
if (!options?.force) {
|
|
@@ -2199,8 +2114,8 @@ function planFreshPath(path, options) {
|
|
|
2199
2114
|
return "overwritten";
|
|
2200
2115
|
}
|
|
2201
2116
|
function preparePlannedPath(path, action) {
|
|
2202
|
-
mkdirSync(
|
|
2203
|
-
if (action === "overwritten" &&
|
|
2117
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
2118
|
+
if (action === "overwritten" && existsSync6(path)) {
|
|
2204
2119
|
rmSync(path, { recursive: true, force: true });
|
|
2205
2120
|
}
|
|
2206
2121
|
}
|
|
@@ -2375,23 +2290,23 @@ function formatInitModeBadge(options) {
|
|
|
2375
2290
|
}
|
|
2376
2291
|
return t("cli.init.mode.badge.default");
|
|
2377
2292
|
}
|
|
2378
|
-
function
|
|
2379
|
-
return
|
|
2293
|
+
function normalizeTarget2(targetInput) {
|
|
2294
|
+
return isAbsolute2(targetInput) ? targetInput : resolve5(process.cwd(), targetInput);
|
|
2380
2295
|
}
|
|
2381
|
-
function
|
|
2382
|
-
if (!
|
|
2296
|
+
function assertExistingDirectory2(target) {
|
|
2297
|
+
if (!existsSync6(target) || !statSync2(target).isDirectory()) {
|
|
2383
2298
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2384
2299
|
}
|
|
2385
2300
|
}
|
|
2386
2301
|
function detectPackageManager(cwd) {
|
|
2387
|
-
const workspaceRoot =
|
|
2388
|
-
if (
|
|
2302
|
+
const workspaceRoot = resolve5(cwd);
|
|
2303
|
+
if (existsSync6(join5(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2389
2304
|
return "pnpm";
|
|
2390
2305
|
}
|
|
2391
|
-
if (
|
|
2306
|
+
if (existsSync6(join5(workspaceRoot, "yarn.lock"))) {
|
|
2392
2307
|
return "yarn";
|
|
2393
2308
|
}
|
|
2394
|
-
if (
|
|
2309
|
+
if (existsSync6(join5(workspaceRoot, "package-lock.json"))) {
|
|
2395
2310
|
return "npm";
|
|
2396
2311
|
}
|
|
2397
2312
|
return "npm";
|
|
@@ -2431,6 +2346,32 @@ function appendReapplyLedgerEvent(eventsPath, payload) {
|
|
|
2431
2346
|
`;
|
|
2432
2347
|
appendFileSync(eventsPath, line, "utf8");
|
|
2433
2348
|
}
|
|
2349
|
+
async function runBestEffort(step, fn) {
|
|
2350
|
+
try {
|
|
2351
|
+
return await fn();
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
return [
|
|
2354
|
+
{
|
|
2355
|
+
step,
|
|
2356
|
+
path: "",
|
|
2357
|
+
status: "error",
|
|
2358
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2359
|
+
}
|
|
2360
|
+
];
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
async function runBestEffortSingle(step, fn) {
|
|
2364
|
+
try {
|
|
2365
|
+
return await fn();
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
return {
|
|
2368
|
+
step,
|
|
2369
|
+
path: "",
|
|
2370
|
+
status: "error",
|
|
2371
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2434
2375
|
function formatInitStageHeader(message) {
|
|
2435
2376
|
return `${nextLabel()} ${paint.muted(message)}`;
|
|
2436
2377
|
}
|