@fenglimg/fabric-cli 2.2.0-rc.1 → 2.2.0-rc.11
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 +8 -5
- package/dist/chunk-27HK6H5Y.js +69 -0
- package/dist/{chunk-AOE6AYI7.js → chunk-2KBCTMID.js} +31 -8
- package/dist/chunk-3D7B2UAZ.js +149 -0
- package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
- package/dist/{plan-context-hint-FC6P3WFE.js → chunk-722JU5BP.js} +52 -12
- package/dist/{chunk-2R55HNVD.js → chunk-7ZDXBOOU.js} +234 -206
- package/dist/{doctor-YONYXDX6.js → chunk-E7HJUU34.js} +215 -52
- package/dist/chunk-EOT63RDH.js +36 -0
- package/dist/chunk-FNHDQTPC.js +16 -0
- package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
- package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
- package/dist/{chunk-WU6GAPKH.js → chunk-PTGQAZEW.js} +12 -4
- package/dist/chunk-QFIVFZRH.js +13 -0
- package/dist/chunk-QPAW6IYT.js +387 -0
- package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
- package/dist/{config-XYRBZJDU.js → config-A3LTECAY.js} +4 -3
- package/dist/context-UJCGYOT6.js +117 -0
- package/dist/doctor-MDTZWKBK.js +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +133 -22
- package/dist/info-7FKBTMVO.js +139 -0
- package/dist/install-v2-WLEJ5XHT.js +3279 -0
- package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
- package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
- package/dist/plan-context-hint-5TNGH3R4.js +12 -0
- package/dist/{scope-explain-CDIZESP5.js → scope-explain-HLJZ2M33.js} +17 -6
- package/dist/status-4R3TM4FJ.js +37 -0
- package/dist/store-HOCORVL3.js +563 -0
- package/dist/{sync-UJ4BBCZJ.js → sync-DT5UJMMR.js} +197 -30
- package/dist/{uninstall-C3QXKOO6.js → uninstall-IFN2KYBK.js} +97 -140
- package/dist/whoami-ITGEFWH4.js +49 -0
- package/package.json +7 -5
- package/templates/hooks/cite-policy-evict.cjs +412 -160
- package/templates/hooks/configs/README.md +14 -27
- package/templates/hooks/configs/claude-code.json +17 -2
- package/templates/hooks/configs/codex-hooks.json +15 -3
- package/templates/hooks/fabric-hint.cjs +742 -259
- package/templates/hooks/knowledge-hint-broad.cjs +577 -274
- package/templates/hooks/knowledge-hint-narrow.cjs +113 -73
- package/templates/hooks/lib/banner-i18n.cjs +50 -1
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +118 -7
- package/templates/hooks/lib/cite-line-parser.cjs +12 -20
- package/templates/hooks/lib/client-adapter.cjs +66 -7
- package/templates/hooks/lib/nudge-policy.cjs +117 -0
- package/templates/hooks/lib/state-store.cjs +60 -0
- package/templates/hooks/post-tooluse-mutation.cjs +386 -0
- package/templates/hooks/session-end-marker.cjs +140 -0
- package/templates/skills/fabric/SKILL.md +100 -0
- package/templates/skills/fabric-archive/SKILL.md +47 -24
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
- package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
- package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
- package/templates/skills/fabric-audit/SKILL.md +13 -3
- package/templates/skills/fabric-connect/SKILL.md +3 -3
- package/templates/skills/fabric-import/SKILL.md +7 -7
- package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
- package/templates/skills/fabric-review/SKILL.md +14 -5
- package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
- package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-review/ref/output-contract.md +1 -1
- package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
- package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
- package/templates/skills/fabric-store/SKILL.md +1 -1
- package/templates/skills/fabric-sync/SKILL.md +1 -1
- package/templates/skills/lib/shared-policy.md +2 -2
- package/dist/chunk-4R2CYEA4.js +0 -116
- package/dist/chunk-L4Q55UC4.js +0 -52
- package/dist/chunk-LFIKMVY7.js +0 -27
- package/dist/chunk-RYAFBNES.js +0 -33
- package/dist/chunk-T5RPGCCM.js +0 -40
- package/dist/install-74ANPCCP.js +0 -2737
- package/dist/status-GLQWLWH6.js +0 -23
- package/dist/store-XB3ADT65.js +0 -144
- package/dist/whoami-2MLO4Y37.js +0 -36
- package/templates/hooks/configs/cursor-hooks.json +0 -18
- package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
- package/templates/hooks/lib/summary-fallback.cjs +0 -210
package/dist/install-74ANPCCP.js
DELETED
|
@@ -1,2737 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
cleanupDeprecatedSkills,
|
|
4
|
-
installArchiveHintHook,
|
|
5
|
-
installCitePolicyEvictHook,
|
|
6
|
-
installFabricArchiveSkill,
|
|
7
|
-
installFabricAuditSkill,
|
|
8
|
-
installFabricConnectSkill,
|
|
9
|
-
installFabricImportSkill,
|
|
10
|
-
installFabricReviewSkill,
|
|
11
|
-
installFabricStoreSkill,
|
|
12
|
-
installFabricSyncSkill,
|
|
13
|
-
installHookLibs,
|
|
14
|
-
installKnowledgeHintBroadHook,
|
|
15
|
-
installKnowledgeHintNarrowHook,
|
|
16
|
-
installSharedSkillLib,
|
|
17
|
-
mergeClaudeCodeHookConfig,
|
|
18
|
-
mergeCodexHookConfig,
|
|
19
|
-
mergeCursorHookConfig,
|
|
20
|
-
readFabricLanguagePreference,
|
|
21
|
-
writeClaudeBootstrapThinShell,
|
|
22
|
-
writeCodexBootstrapManagedBlock,
|
|
23
|
-
writeCursorBootstrapManagedBlock,
|
|
24
|
-
writeFabricAgentsSnapshot
|
|
25
|
-
} from "./chunk-2R55HNVD.js";
|
|
26
|
-
import {
|
|
27
|
-
displayWidth,
|
|
28
|
-
padEnd,
|
|
29
|
-
paint
|
|
30
|
-
} from "./chunk-BO4XIZWZ.js";
|
|
31
|
-
import {
|
|
32
|
-
createDebugLogger,
|
|
33
|
-
resolveDevMode
|
|
34
|
-
} from "./chunk-COI5VDFU.js";
|
|
35
|
-
import {
|
|
36
|
-
installMcpClients
|
|
37
|
-
} from "./chunk-AOE6AYI7.js";
|
|
38
|
-
import {
|
|
39
|
-
detectClientSupports
|
|
40
|
-
} from "./chunk-XC5RUHLK.js";
|
|
41
|
-
import {
|
|
42
|
-
getProjectTranslator,
|
|
43
|
-
t
|
|
44
|
-
} from "./chunk-2CY4BMTH.js";
|
|
45
|
-
import {
|
|
46
|
-
globalConfigPath,
|
|
47
|
-
loadGlobalConfig,
|
|
48
|
-
resolveGlobalRoot,
|
|
49
|
-
saveGlobalConfig
|
|
50
|
-
} from "./chunk-RYAFBNES.js";
|
|
51
|
-
|
|
52
|
-
// src/commands/install.ts
|
|
53
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
54
|
-
import { homedir } from "os";
|
|
55
|
-
import * as childProcess from "child_process";
|
|
56
|
-
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync4, writeFileSync } from "fs";
|
|
57
|
-
import { dirname, isAbsolute as isAbsolute3, join as join6, resolve as resolve3 } from "path";
|
|
58
|
-
import { cancel, confirm, group, intro, isCancel, log, note, outro, select } from "@clack/prompts";
|
|
59
|
-
import { defaultAgentsMetaCounters } from "@fenglimg/fabric-shared";
|
|
60
|
-
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
61
|
-
import { defineCommand } from "citty";
|
|
62
|
-
|
|
63
|
-
// src/install/hooks-orchestrator.ts
|
|
64
|
-
import { existsSync, statSync } from "fs";
|
|
65
|
-
import { isAbsolute, join, resolve } from "path";
|
|
66
|
-
async function installHooks(target, _options = {}) {
|
|
67
|
-
const normalizedTarget = normalizeTarget(target);
|
|
68
|
-
assertExistingDirectory(normalizedTarget);
|
|
69
|
-
const results = [];
|
|
70
|
-
results.push(...await runStep(() => installFabricArchiveSkill(normalizedTarget)));
|
|
71
|
-
results.push(...await runStep(() => installFabricReviewSkill(normalizedTarget)));
|
|
72
|
-
results.push(...await runStep(() => installFabricImportSkill(normalizedTarget)));
|
|
73
|
-
results.push(...await runStep(() => installFabricSyncSkill(normalizedTarget)));
|
|
74
|
-
results.push(...await runStep(() => installFabricStoreSkill(normalizedTarget)));
|
|
75
|
-
results.push(...await runStep(() => installFabricAuditSkill(normalizedTarget)));
|
|
76
|
-
results.push(...await runStep(() => installFabricConnectSkill(normalizedTarget)));
|
|
77
|
-
results.push(...await runStep(() => installSharedSkillLib(normalizedTarget)));
|
|
78
|
-
results.push(...await runStep(() => installArchiveHintHook(normalizedTarget)));
|
|
79
|
-
results.push(...await runStep(() => installKnowledgeHintBroadHook(normalizedTarget)));
|
|
80
|
-
results.push(...await runStep(() => installKnowledgeHintNarrowHook(normalizedTarget)));
|
|
81
|
-
results.push(...await runStep(() => installCitePolicyEvictHook(normalizedTarget)));
|
|
82
|
-
results.push(...await runStep(() => installHookLibs(normalizedTarget)));
|
|
83
|
-
results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
|
|
84
|
-
results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
|
|
85
|
-
results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
|
|
86
|
-
results.push(await runSingleStep("bootstrap-snapshot", () => writeFabricAgentsSnapshot(normalizedTarget)));
|
|
87
|
-
results.push(await runSingleStep("bootstrap-claude", () => writeClaudeBootstrapThinShell(normalizedTarget)));
|
|
88
|
-
results.push(await runSingleStep("bootstrap-codex", () => writeCodexBootstrapManagedBlock(normalizedTarget)));
|
|
89
|
-
results.push(await runSingleStep("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(normalizedTarget)));
|
|
90
|
-
results.push(...validateHookPaths(normalizedTarget));
|
|
91
|
-
return summarizeResults(results);
|
|
92
|
-
}
|
|
93
|
-
function validateHookPaths(projectRoot) {
|
|
94
|
-
const scripts = [
|
|
95
|
-
{ stepSuffix: "", hookFile: "fabric-hint.cjs" },
|
|
96
|
-
{ stepSuffix: "-broad", hookFile: "knowledge-hint-broad.cjs" },
|
|
97
|
-
{ stepSuffix: "-narrow", hookFile: "knowledge-hint-narrow.cjs" }
|
|
98
|
-
];
|
|
99
|
-
const clients = [
|
|
100
|
-
{
|
|
101
|
-
client: "claude",
|
|
102
|
-
configRel: join(".claude", "settings.json"),
|
|
103
|
-
hookDir: join(".claude", "hooks")
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
client: "codex",
|
|
107
|
-
configRel: join(".codex", "hooks.json"),
|
|
108
|
-
hookDir: join(".codex", "hooks")
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
client: "cursor",
|
|
112
|
-
configRel: join(".cursor", "hooks.json"),
|
|
113
|
-
hookDir: join(".cursor", "hooks")
|
|
114
|
-
}
|
|
115
|
-
];
|
|
116
|
-
const results = [];
|
|
117
|
-
for (const { client, configRel, hookDir } of clients) {
|
|
118
|
-
const configPath = resolve(projectRoot, configRel);
|
|
119
|
-
if (!existsSync(configPath)) {
|
|
120
|
-
results.push({
|
|
121
|
-
step: `hook-validate-${client}`,
|
|
122
|
-
path: configPath,
|
|
123
|
-
status: "skipped",
|
|
124
|
-
message: "missing-config"
|
|
125
|
-
});
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
for (const { stepSuffix, hookFile } of scripts) {
|
|
129
|
-
const expectedHookPath = resolve(projectRoot, hookDir, hookFile);
|
|
130
|
-
const expectedHookRel = join(hookDir, hookFile);
|
|
131
|
-
const step = `hook-validate-${client}${stepSuffix}`;
|
|
132
|
-
if (!existsSync(expectedHookPath)) {
|
|
133
|
-
results.push({
|
|
134
|
-
step,
|
|
135
|
-
path: expectedHookPath,
|
|
136
|
-
status: "error",
|
|
137
|
-
message: `hook script missing: ${expectedHookRel}`
|
|
138
|
-
});
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
results.push({ step, path: expectedHookPath, status: "skipped", message: "ok" });
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return results;
|
|
145
|
-
}
|
|
146
|
-
async function runStep(fn) {
|
|
147
|
-
try {
|
|
148
|
-
return await fn();
|
|
149
|
-
} catch (error) {
|
|
150
|
-
return [
|
|
151
|
-
{
|
|
152
|
-
step: "hook-install",
|
|
153
|
-
path: "",
|
|
154
|
-
status: "error",
|
|
155
|
-
message: error instanceof Error ? error.message : String(error)
|
|
156
|
-
}
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async function runSingleStep(step, fn) {
|
|
161
|
-
try {
|
|
162
|
-
return await fn();
|
|
163
|
-
} catch (error) {
|
|
164
|
-
return {
|
|
165
|
-
step,
|
|
166
|
-
path: "",
|
|
167
|
-
status: "error",
|
|
168
|
-
message: error instanceof Error ? error.message : String(error)
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
function summarizeResults(results) {
|
|
173
|
-
const installed = [];
|
|
174
|
-
const skipped = [];
|
|
175
|
-
const errors = [];
|
|
176
|
-
for (const r of results) {
|
|
177
|
-
switch (r.status) {
|
|
178
|
-
case "written":
|
|
179
|
-
installed.push(r.path);
|
|
180
|
-
break;
|
|
181
|
-
case "skipped":
|
|
182
|
-
skipped.push(r.path);
|
|
183
|
-
break;
|
|
184
|
-
case "error":
|
|
185
|
-
errors.push(`${r.step} ${r.path}: ${r.message ?? "unknown error"}`);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return { installed, skipped, errors };
|
|
190
|
-
}
|
|
191
|
-
function normalizeTarget(targetInput) {
|
|
192
|
-
return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
|
|
193
|
-
}
|
|
194
|
-
function assertExistingDirectory(target) {
|
|
195
|
-
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
196
|
-
throw new Error(t("cli.shared.target-invalid", { target }));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// src/install/run-global-install.ts
|
|
201
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
202
|
-
import { randomUUID } from "crypto";
|
|
203
|
-
import { mkdirSync, mkdtempSync, renameSync } from "fs";
|
|
204
|
-
import { tmpdir } from "os";
|
|
205
|
-
import { join as join3 } from "path";
|
|
206
|
-
import { STORES_ROOT_DIR as STORES_ROOT_DIR2, addMountedStore, readStoreIdentity } from "@fenglimg/fabric-shared";
|
|
207
|
-
import { GenericIOError } from "@fenglimg/fabric-shared/errors";
|
|
208
|
-
|
|
209
|
-
// src/store/uid.ts
|
|
210
|
-
import { execFileSync } from "child_process";
|
|
211
|
-
import { createHash } from "crypto";
|
|
212
|
-
function deriveUid(opts = {}) {
|
|
213
|
-
let email = "";
|
|
214
|
-
try {
|
|
215
|
-
email = execFileSync("git", ["config", "user.email"], {
|
|
216
|
-
encoding: "utf8",
|
|
217
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
218
|
-
}).trim();
|
|
219
|
-
} catch {
|
|
220
|
-
email = "";
|
|
221
|
-
}
|
|
222
|
-
if (email === "") {
|
|
223
|
-
return "u-anon";
|
|
224
|
-
}
|
|
225
|
-
const material = opts.salt !== void 0 && opts.salt.length > 0 ? `${opts.salt}:${email.toLowerCase()}` : email.toLowerCase();
|
|
226
|
-
const hash = createHash("sha256").update(material).digest("hex").slice(0, 12);
|
|
227
|
-
return `u-${hash}`;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// src/install/install-global.ts
|
|
231
|
-
import { rmSync } from "fs";
|
|
232
|
-
import { join as join2 } from "path";
|
|
233
|
-
import {
|
|
234
|
-
STORES_ROOT_DIR,
|
|
235
|
-
globalConfigSchema,
|
|
236
|
-
initStore
|
|
237
|
-
} from "@fenglimg/fabric-shared";
|
|
238
|
-
|
|
239
|
-
// src/install/transaction.ts
|
|
240
|
-
function errorMessage(error) {
|
|
241
|
-
return error instanceof Error ? error.message : String(error);
|
|
242
|
-
}
|
|
243
|
-
async function runInstallTransaction(steps) {
|
|
244
|
-
const receipt = { ok: true, steps: [] };
|
|
245
|
-
const applied = [];
|
|
246
|
-
for (let i = 0; i < steps.length; i++) {
|
|
247
|
-
const step = steps[i];
|
|
248
|
-
try {
|
|
249
|
-
await step.apply();
|
|
250
|
-
applied.push(step);
|
|
251
|
-
receipt.steps.push({ name: step.name, status: "applied" });
|
|
252
|
-
} catch (error) {
|
|
253
|
-
receipt.ok = false;
|
|
254
|
-
receipt.failedStep = step.name;
|
|
255
|
-
receipt.error = errorMessage(error);
|
|
256
|
-
receipt.steps.push({ name: step.name, status: "failed", error: errorMessage(error) });
|
|
257
|
-
for (let j = i + 1; j < steps.length; j++) {
|
|
258
|
-
receipt.steps.push({ name: steps[j].name, status: "skipped" });
|
|
259
|
-
}
|
|
260
|
-
for (const done of [...applied].reverse()) {
|
|
261
|
-
const entry = receipt.steps.find((s) => s.name === done.name);
|
|
262
|
-
try {
|
|
263
|
-
await done.rollback();
|
|
264
|
-
if (entry !== void 0) {
|
|
265
|
-
entry.status = "rolled_back";
|
|
266
|
-
}
|
|
267
|
-
} catch (rollbackError) {
|
|
268
|
-
if (entry !== void 0) {
|
|
269
|
-
entry.status = "rollback_failed";
|
|
270
|
-
entry.error = errorMessage(rollbackError);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return receipt;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return receipt;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// src/install/install-global.ts
|
|
281
|
-
async function installGlobalCore(options) {
|
|
282
|
-
const existing = loadGlobalConfig(options.globalRoot);
|
|
283
|
-
if (existing !== null) {
|
|
284
|
-
return {
|
|
285
|
-
receipt: { ok: true, steps: [{ name: "already-installed", status: "applied" }] },
|
|
286
|
-
config: existing,
|
|
287
|
-
alreadyInstalled: true
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
const alias = options.personalAlias ?? "personal";
|
|
291
|
-
const personalDir = join2(options.globalRoot, STORES_ROOT_DIR, options.personalStoreUuid);
|
|
292
|
-
let config = null;
|
|
293
|
-
const receipt = await runInstallTransaction([
|
|
294
|
-
{
|
|
295
|
-
name: "init-personal-store",
|
|
296
|
-
apply: () => {
|
|
297
|
-
initStore(
|
|
298
|
-
personalDir,
|
|
299
|
-
{
|
|
300
|
-
store_uuid: options.personalStoreUuid,
|
|
301
|
-
created_at: options.now,
|
|
302
|
-
canonical_alias: alias
|
|
303
|
-
},
|
|
304
|
-
{ git: options.git }
|
|
305
|
-
);
|
|
306
|
-
},
|
|
307
|
-
rollback: () => {
|
|
308
|
-
rmSync(personalDir, { recursive: true, force: true });
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
name: "write-global-config",
|
|
313
|
-
apply: () => {
|
|
314
|
-
const next = globalConfigSchema.parse({
|
|
315
|
-
uid: options.uid,
|
|
316
|
-
stores: [{ store_uuid: options.personalStoreUuid, alias, personal: true }]
|
|
317
|
-
});
|
|
318
|
-
saveGlobalConfig(next, options.globalRoot);
|
|
319
|
-
config = next;
|
|
320
|
-
},
|
|
321
|
-
rollback: () => {
|
|
322
|
-
rmSync(globalConfigPath(options.globalRoot), { force: true });
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
]);
|
|
326
|
-
return { receipt, config, alreadyInstalled: false };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// src/install/run-global-install.ts
|
|
330
|
-
function gitClone(url, dest) {
|
|
331
|
-
console.log(`cloning store from ${url} (this may take a while)\u2026`);
|
|
332
|
-
try {
|
|
333
|
-
execFileSync2("git", ["clone", "--", url, dest], { stdio: ["ignore", "ignore", "inherit"] });
|
|
334
|
-
} catch (error) {
|
|
335
|
-
throw new GenericIOError(`git clone of ${url} failed`, {
|
|
336
|
-
actionHint: "check the url is reachable and points to a Fabric store git repo (the git error above shows the cause), then re-run `fabric install --global <url>`",
|
|
337
|
-
details: error
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function mountStoreFromRemote(url, globalRoot) {
|
|
342
|
-
const storesRoot = join3(globalRoot, STORES_ROOT_DIR2);
|
|
343
|
-
mkdirSync(storesRoot, { recursive: true });
|
|
344
|
-
const tmp = mkdtempSync(join3(tmpdir(), "fabric-clone-"));
|
|
345
|
-
const cloneDest = join3(tmp, "store");
|
|
346
|
-
gitClone(url, cloneDest);
|
|
347
|
-
const identity = readStoreIdentity(cloneDest);
|
|
348
|
-
if (identity === null) {
|
|
349
|
-
throw new GenericIOError(`cloned store at ${url} has no valid store.json (not a Fabric store)`, {
|
|
350
|
-
actionHint: "verify the url points to a repository created by `fabric` (it must contain a store.json at its root); if you meant to mount a different store, re-run with the correct url"
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
const finalDir = join3(storesRoot, identity.store_uuid);
|
|
354
|
-
renameSync(cloneDest, finalDir);
|
|
355
|
-
const config = loadGlobalConfig(globalRoot);
|
|
356
|
-
if (config === null) {
|
|
357
|
-
throw new GenericIOError("global config missing after install", {
|
|
358
|
-
actionHint: "re-run `fabric install --global` to (re)create the global config, then retry mounting the store; if it persists, inspect ~/.fabric for a partial install"
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
const alias = identity.canonical_alias ?? "team";
|
|
362
|
-
saveGlobalConfig(
|
|
363
|
-
addMountedStore(config, { store_uuid: identity.store_uuid, alias, remote: url }),
|
|
364
|
-
globalRoot
|
|
365
|
-
);
|
|
366
|
-
console.log(`mounted store '${alias}' (${identity.store_uuid}) from ${url}`);
|
|
367
|
-
}
|
|
368
|
-
async function runGlobalInstall(options = {}, globalRoot = resolveGlobalRoot()) {
|
|
369
|
-
const uid = options.uid ?? deriveUid();
|
|
370
|
-
const personalStoreUuid = options.personalStoreUuid ?? randomUUID();
|
|
371
|
-
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
372
|
-
const result = await installGlobalCore({ globalRoot, uid, personalStoreUuid, now });
|
|
373
|
-
if (!result.receipt.ok) {
|
|
374
|
-
throw new GenericIOError(
|
|
375
|
-
`global install failed at step '${result.receipt.failedStep}': ${result.receipt.error}`,
|
|
376
|
-
{
|
|
377
|
-
actionHint: "check write permissions and free space under ~/.fabric, then re-run `fabric install --global` (the install is transactional and rolls back partial state)"
|
|
378
|
-
}
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
console.log(
|
|
382
|
-
result.alreadyInstalled ? "global Fabric already installed" : `installed global Fabric (uid ${uid})`
|
|
383
|
-
);
|
|
384
|
-
if (options.url !== void 0) {
|
|
385
|
-
mountStoreFromRemote(options.url, globalRoot);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// src/lib/detect-language.ts
|
|
390
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
|
|
391
|
-
import { join as join4 } from "path";
|
|
392
|
-
function detectExistingLanguage(target) {
|
|
393
|
-
const ZH_CN_RATIO_THRESHOLD = 0.3;
|
|
394
|
-
const samples = [];
|
|
395
|
-
const readmePath = join4(target, "README.md");
|
|
396
|
-
if (existsSync2(readmePath)) {
|
|
397
|
-
try {
|
|
398
|
-
samples.push(readFileSync(readmePath, "utf8"));
|
|
399
|
-
} catch {
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const docsDir = join4(target, "docs");
|
|
403
|
-
if (existsSync2(docsDir)) {
|
|
404
|
-
try {
|
|
405
|
-
const stat = statSync2(docsDir);
|
|
406
|
-
if (stat.isDirectory()) {
|
|
407
|
-
for (const entry of readdirSync(docsDir, { withFileTypes: true })) {
|
|
408
|
-
if (!entry.isFile()) continue;
|
|
409
|
-
if (!/\.(md|mdx|txt)$/iu.test(entry.name)) continue;
|
|
410
|
-
try {
|
|
411
|
-
samples.push(readFileSync(join4(docsDir, entry.name), "utf8"));
|
|
412
|
-
} catch {
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
} catch {
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (samples.length === 0) {
|
|
420
|
-
return "en";
|
|
421
|
-
}
|
|
422
|
-
let cjkCount = 0;
|
|
423
|
-
let asciiLetterCount = 0;
|
|
424
|
-
for (const sample of samples) {
|
|
425
|
-
for (const ch of sample) {
|
|
426
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
427
|
-
if (code >= 19968 && code <= 40959) {
|
|
428
|
-
cjkCount += 1;
|
|
429
|
-
} else if (code >= 65 && code <= 90 || code >= 97 && code <= 122) {
|
|
430
|
-
asciiLetterCount += 1;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const denominator = cjkCount + asciiLetterCount;
|
|
435
|
-
if (denominator === 0) {
|
|
436
|
-
return "en";
|
|
437
|
-
}
|
|
438
|
-
const ratio = cjkCount / denominator;
|
|
439
|
-
return ratio > ZH_CN_RATIO_THRESHOLD ? "zh-CN-hybrid" : "en";
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// src/scanner/forensic.ts
|
|
443
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
444
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
|
|
445
|
-
import { createRequire } from "module";
|
|
446
|
-
import { basename, extname, isAbsolute as isAbsolute2, join as join5, posix, relative, resolve as resolve2, sep } from "path";
|
|
447
|
-
import {
|
|
448
|
-
buildScanRecommendations,
|
|
449
|
-
forensicReportSchema
|
|
450
|
-
} from "@fenglimg/fabric-shared";
|
|
451
|
-
|
|
452
|
-
// src/scanner/detector.ts
|
|
453
|
-
import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
454
|
-
|
|
455
|
-
// src/scanner/forensic.ts
|
|
456
|
-
var require2 = createRequire(import.meta.url);
|
|
457
|
-
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
458
|
-
".fabric",
|
|
459
|
-
".git",
|
|
460
|
-
".next",
|
|
461
|
-
".turbo",
|
|
462
|
-
"Library",
|
|
463
|
-
"Temp",
|
|
464
|
-
"build",
|
|
465
|
-
"coverage",
|
|
466
|
-
"dist",
|
|
467
|
-
"node_modules"
|
|
468
|
-
]);
|
|
469
|
-
var KEY_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
|
|
470
|
-
"app",
|
|
471
|
-
"components",
|
|
472
|
-
"pages",
|
|
473
|
-
"prefabs",
|
|
474
|
-
"scenes",
|
|
475
|
-
"scripts",
|
|
476
|
-
"src"
|
|
477
|
-
]);
|
|
478
|
-
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
|
|
479
|
-
var DOMAIN_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".json", ".md"]);
|
|
480
|
-
var EXPECTED_CONFIG_FILES_BY_FRAMEWORK = {
|
|
481
|
-
"cocos-creator": ["package.json", "project.config.json", "tsconfig.json"],
|
|
482
|
-
react: ["package.json", "tsconfig.json"],
|
|
483
|
-
next: ["package.json", "tsconfig.json"],
|
|
484
|
-
vite: ["package.json", "tsconfig.json"]
|
|
485
|
-
};
|
|
486
|
-
var FRAMEWORK_IMPORT_PROFILES = {
|
|
487
|
-
"cocos-creator": {
|
|
488
|
-
pattern: "cocos-component-class",
|
|
489
|
-
family: "component",
|
|
490
|
-
statement: "Sampled entry files use Cocos Creator component classes.",
|
|
491
|
-
proposedRule: "Treat assets/scripts/*.ts and adjacent .meta files as framework-owned structure unless the user says otherwise.",
|
|
492
|
-
alternatives: ["Generic TypeScript utility module"],
|
|
493
|
-
rationale: "Cocos framework imports and component markers co-occur in sampled entry files.",
|
|
494
|
-
packages: ["cc"]
|
|
495
|
-
},
|
|
496
|
-
react: {
|
|
497
|
-
pattern: "react-root",
|
|
498
|
-
family: "entry",
|
|
499
|
-
statement: "Sampled entry files import React framework packages.",
|
|
500
|
-
proposedRule: "Keep root rendering and component composition aligned with React entry conventions.",
|
|
501
|
-
alternatives: ["Server-rendered route module"],
|
|
502
|
-
rationale: "AST import declarations reference React packages rather than comments or strings.",
|
|
503
|
-
packages: ["react", "react-dom", "react/jsx-runtime", "react-dom/client"]
|
|
504
|
-
},
|
|
505
|
-
vite: {
|
|
506
|
-
pattern: "vite-main-entry",
|
|
507
|
-
family: "entry",
|
|
508
|
-
statement: "Sampled entry files use the conventional Vite main entrypoint.",
|
|
509
|
-
proposedRule: "Keep primary bootstrapping logic inside src/main.*.",
|
|
510
|
-
alternatives: ["Alternative bundler entrypoint"],
|
|
511
|
-
rationale: "Entry path and framework imports align with a Vite bootstrap surface.",
|
|
512
|
-
packages: ["@vitejs/plugin-react", "@vitejs/plugin-vue", "vite", "react", "vue"]
|
|
513
|
-
},
|
|
514
|
-
next: {
|
|
515
|
-
pattern: "next-route-component",
|
|
516
|
-
family: "entry",
|
|
517
|
-
statement: "Sampled entry files align with Next.js route modules.",
|
|
518
|
-
proposedRule: "Preserve route-segment boundaries when editing app/ or pages/ files.",
|
|
519
|
-
alternatives: ["Generic source module"],
|
|
520
|
-
rationale: "Route placement and Next/React imports anchor these files to the request surface.",
|
|
521
|
-
packages: ["next", "next/link", "next/navigation", "react"]
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
var SAMPLE_LIMIT = 5;
|
|
525
|
-
var SAMPLE_LINE_LIMIT = 30;
|
|
526
|
-
var ENTRY_FAMILY_LIMIT = 1;
|
|
527
|
-
var FAMILY_LIMIT = 3;
|
|
528
|
-
var CANDIDATE_FILE_LIMIT = 12;
|
|
529
|
-
var DEFAULT_SAMPLING_BUDGET = {
|
|
530
|
-
max_files: 15,
|
|
531
|
-
max_lines_per_file: 100
|
|
532
|
-
};
|
|
533
|
-
var treeSitterModulePromise = null;
|
|
534
|
-
var parserInitPromise = null;
|
|
535
|
-
var languagePromiseByKind = {};
|
|
536
|
-
var parserBundlePromiseByKind = {};
|
|
537
|
-
async function buildForensicReport(targetInput) {
|
|
538
|
-
const target = normalizeTarget2(targetInput);
|
|
539
|
-
const framework = detectFramework(target);
|
|
540
|
-
const topology = buildTopology(target);
|
|
541
|
-
const entryPoints = collectEntryPoints(target, topology.files);
|
|
542
|
-
const packageDependencies = readPackageDependencies(target);
|
|
543
|
-
const codeSamples = await buildCodeSamples(target, entryPoints, framework.kind, topology, packageDependencies);
|
|
544
|
-
const assertions = buildAssertions(framework.kind, topology, codeSamples);
|
|
545
|
-
const candidateFiles = buildCandidateFiles(topology, codeSamples, entryPoints);
|
|
546
|
-
const readme = readReadmeInfo(target);
|
|
547
|
-
const report = {
|
|
548
|
-
version: "1.0",
|
|
549
|
-
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
550
|
-
generated_by: `fabric-cli@${getCliVersion()}`,
|
|
551
|
-
target,
|
|
552
|
-
project_name: readProjectName(target),
|
|
553
|
-
framework,
|
|
554
|
-
topology: {
|
|
555
|
-
total_files: topology.total_files,
|
|
556
|
-
by_ext: topology.by_ext,
|
|
557
|
-
key_dirs: topology.key_dirs,
|
|
558
|
-
max_depth: topology.max_depth
|
|
559
|
-
},
|
|
560
|
-
entry_points: entryPoints,
|
|
561
|
-
code_samples: codeSamples.map(({ pattern_analysis: _patternAnalysis, evidence: _evidence, ...sample }) => sample),
|
|
562
|
-
assertions,
|
|
563
|
-
candidate_files: candidateFiles,
|
|
564
|
-
sampling_budget: DEFAULT_SAMPLING_BUDGET,
|
|
565
|
-
readme,
|
|
566
|
-
recommendations_for_skill: buildSkillRecommendations(framework.kind, topology, readme, target)
|
|
567
|
-
};
|
|
568
|
-
const validation = forensicReportSchema.safeParse(report);
|
|
569
|
-
if (!validation.success) {
|
|
570
|
-
throw new Error(`ForensicReport schema validation failed: ${validation.error.message}`);
|
|
571
|
-
}
|
|
572
|
-
return validation.data;
|
|
573
|
-
}
|
|
574
|
-
function normalizeTarget2(targetInput) {
|
|
575
|
-
return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
|
|
576
|
-
}
|
|
577
|
-
function buildTopology(root) {
|
|
578
|
-
assertExistingDirectory2(root);
|
|
579
|
-
const byExt = {};
|
|
580
|
-
const keyDirs = /* @__PURE__ */ new Set();
|
|
581
|
-
const files = [];
|
|
582
|
-
let totalFiles = 0;
|
|
583
|
-
let maxDepth = 0;
|
|
584
|
-
const stack = [root];
|
|
585
|
-
while (stack.length > 0) {
|
|
586
|
-
const current = stack.pop();
|
|
587
|
-
if (current === void 0) {
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
for (const entry of readdirSync2(current, { withFileTypes: true })) {
|
|
591
|
-
const absolutePath = join5(current, entry.name);
|
|
592
|
-
const relativePath = toPosixPath(relative(root, absolutePath));
|
|
593
|
-
if (relativePath.length === 0) {
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
const depth = relativePath.split("/").length;
|
|
597
|
-
maxDepth = Math.max(maxDepth, depth);
|
|
598
|
-
if (entry.isDirectory()) {
|
|
599
|
-
if (IGNORED_DIRECTORIES.has(entry.name)) {
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
if (isKeyDirectory(relativePath)) {
|
|
603
|
-
keyDirs.add(relativePath);
|
|
604
|
-
}
|
|
605
|
-
stack.push(absolutePath);
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
if (!entry.isFile()) {
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
const stats = statSync3(absolutePath);
|
|
612
|
-
const extension = extname(entry.name) || "[none]";
|
|
613
|
-
byExt[extension] = (byExt[extension] ?? 0) + 1;
|
|
614
|
-
totalFiles += 1;
|
|
615
|
-
files.push({
|
|
616
|
-
relativePath,
|
|
617
|
-
sizeBytes: stats.size
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
return {
|
|
622
|
-
total_files: totalFiles,
|
|
623
|
-
by_ext: sortRecord(byExt),
|
|
624
|
-
key_dirs: [...keyDirs].sort(),
|
|
625
|
-
max_depth: maxDepth,
|
|
626
|
-
files: files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
function assertExistingDirectory2(target) {
|
|
630
|
-
if (!existsSync3(target) || !statSync3(target).isDirectory()) {
|
|
631
|
-
throw new Error(`Target must be an existing directory: ${target}`);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
function isKeyDirectory(relativePath) {
|
|
635
|
-
const name = basename(relativePath);
|
|
636
|
-
return KEY_DIRECTORY_NAMES.has(name);
|
|
637
|
-
}
|
|
638
|
-
function collectEntryPoints(target, files) {
|
|
639
|
-
const entryPoints = [];
|
|
640
|
-
for (const file of files) {
|
|
641
|
-
const reason = getEntryPointReason(file.relativePath);
|
|
642
|
-
if (reason === null) {
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
entryPoints.push({
|
|
646
|
-
path: file.relativePath,
|
|
647
|
-
reason,
|
|
648
|
-
size_bytes: file.sizeBytes
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
return entryPoints.sort(
|
|
652
|
-
(left, right) => compareCandidateScore(readGitChurnWeight(target, right.path), readGitChurnWeight(target, left.path))
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
function getEntryPointReason(relativePath) {
|
|
656
|
-
if (!SCRIPT_EXTENSIONS.has(extname(relativePath))) {
|
|
657
|
-
return null;
|
|
658
|
-
}
|
|
659
|
-
const directory = posix.dirname(relativePath);
|
|
660
|
-
const fileName = basename(relativePath);
|
|
661
|
-
const fileBase = basename(relativePath, extname(relativePath));
|
|
662
|
-
if (directory === "assets/scripts" || directory === "scripts") {
|
|
663
|
-
return "top-level script";
|
|
664
|
-
}
|
|
665
|
-
if (directory === "src" && /^(App|app|index|main)$/.test(fileBase)) {
|
|
666
|
-
return "application entry";
|
|
667
|
-
}
|
|
668
|
-
if ((directory === "app" || directory.startsWith("app/")) && /^(layout|page|route)$/.test(fileBase)) {
|
|
669
|
-
return "next app route";
|
|
670
|
-
}
|
|
671
|
-
if ((directory === "pages" || directory.startsWith("pages/")) && fileName !== "_app.d.ts") {
|
|
672
|
-
return "next page route";
|
|
673
|
-
}
|
|
674
|
-
return null;
|
|
675
|
-
}
|
|
676
|
-
async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
|
|
677
|
-
const samples = [];
|
|
678
|
-
for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
|
|
679
|
-
const absolutePath = join5(target, ...entryPoint.path.split("/"));
|
|
680
|
-
const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
|
|
681
|
-
const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
|
|
682
|
-
frameworkKind,
|
|
683
|
-
topology,
|
|
684
|
-
packageDependencies
|
|
685
|
-
});
|
|
686
|
-
samples.push({
|
|
687
|
-
path: entryPoint.path,
|
|
688
|
-
lines: `1-${sample.lineCount}`,
|
|
689
|
-
snippet: sample.snippet,
|
|
690
|
-
pattern_hint: patternAnalysis.pattern,
|
|
691
|
-
pattern_analysis: patternAnalysis,
|
|
692
|
-
evidence: buildEvidenceAnchors(entryPoint.path, sample.snippet, patternAnalysis.evidence_lines)
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
return samples;
|
|
696
|
-
}
|
|
697
|
-
function readFirstLines(path, lineLimit) {
|
|
698
|
-
try {
|
|
699
|
-
const lines = readFileSync2(path, "utf8").split(/\r?\n/);
|
|
700
|
-
if (lines.at(-1) === "") {
|
|
701
|
-
lines.pop();
|
|
702
|
-
}
|
|
703
|
-
const sampledLines = lines.slice(0, lineLimit);
|
|
704
|
-
return {
|
|
705
|
-
snippet: sampledLines.join("\n"),
|
|
706
|
-
lineCount: sampledLines.length
|
|
707
|
-
};
|
|
708
|
-
} catch {
|
|
709
|
-
return {
|
|
710
|
-
snippet: "",
|
|
711
|
-
lineCount: 0
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
function readPackageDependencies(target) {
|
|
716
|
-
const packageJsonPath = join5(target, "package.json");
|
|
717
|
-
if (!existsSync3(packageJsonPath)) {
|
|
718
|
-
return /* @__PURE__ */ new Map();
|
|
719
|
-
}
|
|
720
|
-
try {
|
|
721
|
-
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
722
|
-
return new Map([
|
|
723
|
-
...Object.entries(packageJson.dependencies ?? {}),
|
|
724
|
-
...Object.entries(packageJson.devDependencies ?? {}),
|
|
725
|
-
...Object.entries(packageJson.peerDependencies ?? {}),
|
|
726
|
-
...Object.entries(packageJson.optionalDependencies ?? {})
|
|
727
|
-
]);
|
|
728
|
-
} catch {
|
|
729
|
-
return /* @__PURE__ */ new Map();
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
function readGitChurnWeight(target, relativePath) {
|
|
733
|
-
try {
|
|
734
|
-
const output = execFileSync3("git", ["log", "--follow", "--oneline", "-20", "--", relativePath], {
|
|
735
|
-
cwd: target,
|
|
736
|
-
encoding: "utf8",
|
|
737
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
738
|
-
timeout: 1e3
|
|
739
|
-
});
|
|
740
|
-
return output.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
741
|
-
} catch {
|
|
742
|
-
return 0;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
async function inferPatternHint(relativePath, snippet, options = {}) {
|
|
746
|
-
const input = {
|
|
747
|
-
relativePath,
|
|
748
|
-
snippet,
|
|
749
|
-
frameworkKind: options.frameworkKind ?? "unknown",
|
|
750
|
-
topology: options.topology ?? createEmptyTopology(),
|
|
751
|
-
packageDependencies: options.packageDependencies ?? /* @__PURE__ */ new Map()
|
|
752
|
-
};
|
|
753
|
-
const importAnalysis = await analyzeImports(input.relativePath, input.snippet);
|
|
754
|
-
if (importAnalysis.astLevel) {
|
|
755
|
-
const astResult = buildAstPatternHint(input, importAnalysis.imports);
|
|
756
|
-
if (astResult !== null) {
|
|
757
|
-
return astResult;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
return inferTextPatternHint(input.relativePath, input.snippet);
|
|
761
|
-
}
|
|
762
|
-
function createEmptyTopology() {
|
|
763
|
-
return {
|
|
764
|
-
total_files: 0,
|
|
765
|
-
by_ext: {},
|
|
766
|
-
key_dirs: [],
|
|
767
|
-
max_depth: 0,
|
|
768
|
-
files: []
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
function buildAstPatternHint(input, imports) {
|
|
772
|
-
const profile = resolveFrameworkImportProfile(input.frameworkKind, input.relativePath, imports);
|
|
773
|
-
if (profile === null) {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
const matchingImports = imports.filter((source) => matchesAnyFrameworkPackage(source, profile.packages));
|
|
777
|
-
const configFiles = getExpectedConfigFiles(input.frameworkKind).filter((file) => hasFile(input.topology.files, file));
|
|
778
|
-
const packageMatches = profile.packages.filter((packageName) => input.packageDependencies.has(packageName));
|
|
779
|
-
const coOccurring = compactPatternNames([
|
|
780
|
-
...matchingImports.map((source) => `import:${source}`),
|
|
781
|
-
...configFiles.map(normalizeConfigPattern),
|
|
782
|
-
...packageMatches.map((packageName) => `package:${packageName}`),
|
|
783
|
-
input.relativePath.startsWith("app/") ? "app-router" : null,
|
|
784
|
-
input.relativePath.startsWith("pages/") ? "pages-router" : null,
|
|
785
|
-
input.relativePath === "src/main.ts" || input.relativePath === "src/main.js" ? "main-entry" : null,
|
|
786
|
-
input.snippet.includes("@ccclass(") ? "ccclass-decorator" : null,
|
|
787
|
-
input.snippet.includes("extends Component") ? "component-base" : null
|
|
788
|
-
]);
|
|
789
|
-
return {
|
|
790
|
-
pattern: profile.pattern,
|
|
791
|
-
type: "pattern",
|
|
792
|
-
confidence: scoreFrameworkConfidence({
|
|
793
|
-
importCount: matchingImports.length,
|
|
794
|
-
configCount: configFiles.length,
|
|
795
|
-
packageCount: packageMatches.length,
|
|
796
|
-
astLevel: true
|
|
797
|
-
}),
|
|
798
|
-
evidence_lines: matchingImports.length > 0 ? matchingImports : imports.slice(0, 3),
|
|
799
|
-
co_occurring: coOccurring,
|
|
800
|
-
family: profile.family,
|
|
801
|
-
ast_level: true,
|
|
802
|
-
statement: profile.statement,
|
|
803
|
-
proposed_rule: profile.proposedRule,
|
|
804
|
-
alternatives: profile.alternatives,
|
|
805
|
-
rationale: profile.rationale
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
function inferTextPatternHint(relativePath, snippet) {
|
|
809
|
-
const cocosCoOccurring = compactPatternNames([
|
|
810
|
-
snippet.includes('from "cc"') || snippet.includes("from 'cc'") ? "cc-import" : null,
|
|
811
|
-
snippet.includes("@ccclass(") || snippet.includes("ccclass(") ? "ccclass-decorator" : null,
|
|
812
|
-
snippet.includes("extends Component") ? "component-base" : null,
|
|
813
|
-
snippet.includes("const { ccclass } = _decorator") ? "decorator-destructure" : null
|
|
814
|
-
]);
|
|
815
|
-
if (cocosCoOccurring.length > 0) {
|
|
816
|
-
return {
|
|
817
|
-
pattern: "cocos-component-class",
|
|
818
|
-
type: "pattern",
|
|
819
|
-
confidence: scoreFrameworkConfidence({
|
|
820
|
-
importCount: 0,
|
|
821
|
-
configCount: 0,
|
|
822
|
-
packageCount: 0,
|
|
823
|
-
astLevel: false,
|
|
824
|
-
keywordCount: cocosCoOccurring.length
|
|
825
|
-
}),
|
|
826
|
-
evidence_lines: compactPatternNames([
|
|
827
|
-
snippet.includes("_decorator") ? "_decorator" : null,
|
|
828
|
-
snippet.includes("@ccclass(") ? "@ccclass(" : null,
|
|
829
|
-
snippet.includes("extends Component") ? "extends Component" : null
|
|
830
|
-
]),
|
|
831
|
-
co_occurring: cocosCoOccurring,
|
|
832
|
-
family: "component",
|
|
833
|
-
ast_level: false,
|
|
834
|
-
statement: "Sampled entry files use Cocos Creator component classes.",
|
|
835
|
-
proposed_rule: "Treat assets/scripts/*.ts and adjacent .meta files as framework-owned structure unless the user says otherwise.",
|
|
836
|
-
alternatives: ["Generic TypeScript utility module"],
|
|
837
|
-
rationale: "Cocos-specific decorators and Component inheritance co-occur in sampled entry files."
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
const reactCoOccurring = compactPatternNames([
|
|
841
|
-
snippet.includes("createRoot(") ? "create-root" : null,
|
|
842
|
-
snippet.includes("ReactDOM.render(") ? "react-dom-render" : null,
|
|
843
|
-
snippet.includes('from "react-dom"') || snippet.includes("from 'react-dom'") ? "react-dom-import" : null
|
|
844
|
-
]);
|
|
845
|
-
if (reactCoOccurring.length > 0) {
|
|
846
|
-
return {
|
|
847
|
-
pattern: "react-root",
|
|
848
|
-
type: "pattern",
|
|
849
|
-
confidence: scoreFrameworkConfidence({
|
|
850
|
-
importCount: 0,
|
|
851
|
-
configCount: 0,
|
|
852
|
-
packageCount: 0,
|
|
853
|
-
astLevel: false,
|
|
854
|
-
keywordCount: reactCoOccurring.length
|
|
855
|
-
}),
|
|
856
|
-
evidence_lines: compactPatternNames([
|
|
857
|
-
snippet.includes("createRoot(") ? "createRoot(" : null,
|
|
858
|
-
snippet.includes("ReactDOM.render(") ? "ReactDOM.render(" : null
|
|
859
|
-
]),
|
|
860
|
-
co_occurring: reactCoOccurring,
|
|
861
|
-
family: "entry",
|
|
862
|
-
ast_level: false,
|
|
863
|
-
statement: "Sampled entry files bootstrap a React DOM root.",
|
|
864
|
-
proposed_rule: "Keep root rendering logic in the main application entry file.",
|
|
865
|
-
alternatives: ["Server-rendered route module"],
|
|
866
|
-
rationale: "React DOM root markers identify a frontend entrypoint."
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
if (relativePath.startsWith("app/") || relativePath.startsWith("pages/")) {
|
|
870
|
-
const coOccurring = compactPatternNames([
|
|
871
|
-
relativePath.startsWith("app/") ? "app-router" : null,
|
|
872
|
-
relativePath.startsWith("pages/") ? "pages-router" : null,
|
|
873
|
-
snippet.includes("export default") ? "default-export-route" : null
|
|
874
|
-
]);
|
|
875
|
-
return {
|
|
876
|
-
pattern: "next-route-component",
|
|
877
|
-
type: "pattern",
|
|
878
|
-
confidence: scoreFrameworkConfidence({
|
|
879
|
-
importCount: 0,
|
|
880
|
-
configCount: 0,
|
|
881
|
-
packageCount: 0,
|
|
882
|
-
astLevel: false,
|
|
883
|
-
keywordCount: coOccurring.length
|
|
884
|
-
}),
|
|
885
|
-
evidence_lines: compactPatternNames([
|
|
886
|
-
relativePath.startsWith("app/") ? "app/" : null,
|
|
887
|
-
relativePath.startsWith("pages/") ? "pages/" : null
|
|
888
|
-
]),
|
|
889
|
-
co_occurring: coOccurring,
|
|
890
|
-
family: "entry",
|
|
891
|
-
ast_level: false,
|
|
892
|
-
statement: "Sampled entry files align with Next.js route modules.",
|
|
893
|
-
proposed_rule: "Preserve route-segment boundaries when editing app/ or pages/ files.",
|
|
894
|
-
alternatives: ["Generic source module"],
|
|
895
|
-
rationale: "Route directory placement anchors these files to the Next.js request surface."
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
if (relativePath === "src/main.ts" || relativePath === "src/main.js") {
|
|
899
|
-
const coOccurring = compactPatternNames([
|
|
900
|
-
"main-entry",
|
|
901
|
-
snippet.includes("import.meta") ? "import-meta" : null,
|
|
902
|
-
snippet.includes("createRoot(") ? "react-root" : null
|
|
903
|
-
]);
|
|
904
|
-
return {
|
|
905
|
-
pattern: "vite-main-entry",
|
|
906
|
-
type: "pattern",
|
|
907
|
-
confidence: scoreFrameworkConfidence({
|
|
908
|
-
importCount: 0,
|
|
909
|
-
configCount: 0,
|
|
910
|
-
packageCount: 0,
|
|
911
|
-
astLevel: false,
|
|
912
|
-
keywordCount: coOccurring.length
|
|
913
|
-
}),
|
|
914
|
-
evidence_lines: ["src/main"],
|
|
915
|
-
co_occurring: coOccurring,
|
|
916
|
-
family: "entry",
|
|
917
|
-
ast_level: false,
|
|
918
|
-
statement: "Sampled entry files use the conventional Vite main entrypoint.",
|
|
919
|
-
proposed_rule: "Keep primary bootstrapping logic inside src/main.*.",
|
|
920
|
-
alternatives: ["Alternative bundler entrypoint"],
|
|
921
|
-
rationale: "src/main.* is the expected Vite bootstrap path."
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
return {
|
|
925
|
-
pattern: "source-entry",
|
|
926
|
-
type: "pattern",
|
|
927
|
-
confidence: "LOW",
|
|
928
|
-
evidence_lines: [basename(relativePath)],
|
|
929
|
-
co_occurring: [],
|
|
930
|
-
family: "domain",
|
|
931
|
-
ast_level: false,
|
|
932
|
-
statement: "Sampled entry file appears to be a generic source entry.",
|
|
933
|
-
alternatives: ["Framework-specific entrypoint"],
|
|
934
|
-
rationale: "No strong framework markers were detected in the sampled snippet."
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
|
-
async function analyzeImports(relativePath, snippet) {
|
|
938
|
-
if (snippet.trim().length === 0) {
|
|
939
|
-
return { imports: [], astLevel: false };
|
|
940
|
-
}
|
|
941
|
-
try {
|
|
942
|
-
const imports = await extractImports(snippet, getLanguageKindForPath(relativePath));
|
|
943
|
-
return { imports, astLevel: true };
|
|
944
|
-
} catch {
|
|
945
|
-
return { imports: [], astLevel: false };
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
async function extractImports(source, languageKind) {
|
|
949
|
-
const { parser } = await loadTreeSitter(languageKind);
|
|
950
|
-
let tree = null;
|
|
951
|
-
try {
|
|
952
|
-
tree = parser.parse(source);
|
|
953
|
-
if (tree === null || tree.rootNode.hasError) {
|
|
954
|
-
throw new Error("tree-sitter parse failed");
|
|
955
|
-
}
|
|
956
|
-
const imports = [];
|
|
957
|
-
collectImportSources(tree.rootNode, imports);
|
|
958
|
-
return compactPatternNames(imports);
|
|
959
|
-
} finally {
|
|
960
|
-
tree?.delete();
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
async function loadTreeSitter(languageKind) {
|
|
964
|
-
parserBundlePromiseByKind[languageKind] ??= createTreeSitterParserBundle(languageKind);
|
|
965
|
-
return parserBundlePromiseByKind[languageKind];
|
|
966
|
-
}
|
|
967
|
-
async function createTreeSitterParserBundle(languageKind) {
|
|
968
|
-
const treeSitter = await loadTreeSitterModule();
|
|
969
|
-
await initTreeSitterParser(treeSitter);
|
|
970
|
-
const language = await loadTreeSitterLanguage(treeSitter, languageKind);
|
|
971
|
-
const parser = new treeSitter.Parser();
|
|
972
|
-
parser.setLanguage(language);
|
|
973
|
-
return { parser, language };
|
|
974
|
-
}
|
|
975
|
-
function loadTreeSitterModule() {
|
|
976
|
-
treeSitterModulePromise ??= import("web-tree-sitter");
|
|
977
|
-
return treeSitterModulePromise;
|
|
978
|
-
}
|
|
979
|
-
function initTreeSitterParser(treeSitter) {
|
|
980
|
-
parserInitPromise ??= treeSitter.Parser.init({
|
|
981
|
-
locateFile: (scriptName) => scriptName.endsWith(".wasm") ? require2.resolve("web-tree-sitter/web-tree-sitter.wasm") : scriptName
|
|
982
|
-
});
|
|
983
|
-
return parserInitPromise;
|
|
984
|
-
}
|
|
985
|
-
function loadTreeSitterLanguage(treeSitter, languageKind) {
|
|
986
|
-
languagePromiseByKind[languageKind] ??= treeSitter.Language.load(resolveTreeSitterGrammarPath(languageKind));
|
|
987
|
-
return languagePromiseByKind[languageKind];
|
|
988
|
-
}
|
|
989
|
-
function resolveTreeSitterGrammarPath(languageKind) {
|
|
990
|
-
switch (languageKind) {
|
|
991
|
-
case "typescript":
|
|
992
|
-
return require2.resolve("tree-sitter-typescript/tree-sitter-typescript.wasm");
|
|
993
|
-
case "tsx":
|
|
994
|
-
return require2.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm");
|
|
995
|
-
case "javascript":
|
|
996
|
-
return require2.resolve("tree-sitter-javascript/tree-sitter-javascript.wasm");
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function getLanguageKindForPath(relativePath) {
|
|
1000
|
-
const extension = extname(relativePath);
|
|
1001
|
-
if (extension === ".tsx") {
|
|
1002
|
-
return "tsx";
|
|
1003
|
-
}
|
|
1004
|
-
if (extension === ".ts") {
|
|
1005
|
-
return "typescript";
|
|
1006
|
-
}
|
|
1007
|
-
return "javascript";
|
|
1008
|
-
}
|
|
1009
|
-
function collectImportSources(node, imports) {
|
|
1010
|
-
if (node.type === "import_statement" || node.type === "import_declaration") {
|
|
1011
|
-
const sourceNode = node.childForFieldName("source");
|
|
1012
|
-
if (sourceNode !== null) {
|
|
1013
|
-
const source = stripStringLiteral(sourceNode.text);
|
|
1014
|
-
if (source.length > 0) {
|
|
1015
|
-
imports.push(source);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
for (let index = 0; index < node.namedChildCount; index += 1) {
|
|
1020
|
-
const child = node.namedChild(index);
|
|
1021
|
-
if (child !== null) {
|
|
1022
|
-
collectImportSources(child, imports);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
function stripStringLiteral(value) {
|
|
1027
|
-
return value.replace(/^['"]|['"]$/g, "");
|
|
1028
|
-
}
|
|
1029
|
-
function resolveFrameworkImportProfile(frameworkKind, relativePath, imports) {
|
|
1030
|
-
const primaryProfile = FRAMEWORK_IMPORT_PROFILES[frameworkKind];
|
|
1031
|
-
if (primaryProfile !== void 0 && imports.some((source) => matchesAnyFrameworkPackage(source, primaryProfile.packages))) {
|
|
1032
|
-
return primaryProfile;
|
|
1033
|
-
}
|
|
1034
|
-
if ((relativePath.startsWith("app/") || relativePath.startsWith("pages/")) && FRAMEWORK_IMPORT_PROFILES.next !== void 0) {
|
|
1035
|
-
return FRAMEWORK_IMPORT_PROFILES.next;
|
|
1036
|
-
}
|
|
1037
|
-
return Object.values(FRAMEWORK_IMPORT_PROFILES).find(
|
|
1038
|
-
(profile) => imports.some((source) => matchesAnyFrameworkPackage(source, profile.packages))
|
|
1039
|
-
) ?? null;
|
|
1040
|
-
}
|
|
1041
|
-
function matchesAnyFrameworkPackage(source, packageNames) {
|
|
1042
|
-
return packageNames.some((packageName) => source === packageName || source.startsWith(`${packageName}/`));
|
|
1043
|
-
}
|
|
1044
|
-
function scoreFrameworkConfidence(input) {
|
|
1045
|
-
if (!input.astLevel) {
|
|
1046
|
-
return (input.keywordCount ?? 0) > 0 ? "MEDIUM" : "LOW";
|
|
1047
|
-
}
|
|
1048
|
-
if (input.importCount > 3) {
|
|
1049
|
-
return "HIGH";
|
|
1050
|
-
}
|
|
1051
|
-
if (input.importCount >= 1 && input.importCount <= 3) {
|
|
1052
|
-
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "MEDIUM";
|
|
1053
|
-
}
|
|
1054
|
-
return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
|
|
1055
|
-
}
|
|
1056
|
-
function readReadmeInfo(target) {
|
|
1057
|
-
const readmePath = join5(target, "README.md");
|
|
1058
|
-
const hasContributing = existsSync3(join5(target, "CONTRIBUTING.md"));
|
|
1059
|
-
if (!existsSync3(readmePath)) {
|
|
1060
|
-
return {
|
|
1061
|
-
quality: "missing",
|
|
1062
|
-
line_count: 0,
|
|
1063
|
-
has_contributing: hasContributing
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
const readme = readFileSync2(readmePath, "utf8");
|
|
1067
|
-
const wordCount = readme.trim().split(/\s+/).filter(Boolean).length;
|
|
1068
|
-
return {
|
|
1069
|
-
quality: wordCount >= 200 ? "ok" : "stub",
|
|
1070
|
-
line_count: readme.length === 0 ? 0 : readme.split(/\r?\n/).length,
|
|
1071
|
-
has_contributing: hasContributing
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
function buildAssertions(frameworkKind, topology, codeSamples) {
|
|
1075
|
-
const assertions = [
|
|
1076
|
-
buildFrameworkAssertion(frameworkKind, topology, codeSamples),
|
|
1077
|
-
buildDominantPatternAssertion(codeSamples),
|
|
1078
|
-
buildEntryDirectoryAssertion(frameworkKind, codeSamples),
|
|
1079
|
-
buildMetaSidecarAssertion(frameworkKind, topology),
|
|
1080
|
-
buildConfigAssertion(frameworkKind, topology),
|
|
1081
|
-
buildDomainAssertion(codeSamples)
|
|
1082
|
-
];
|
|
1083
|
-
return assertions.filter((assertion) => assertion !== null);
|
|
1084
|
-
}
|
|
1085
|
-
function buildCandidateFiles(topology, codeSamples, entryPoints) {
|
|
1086
|
-
const selected = /* @__PURE__ */ new Map();
|
|
1087
|
-
const codeSamplesByPath = new Map(codeSamples.map((sample) => [sample.path, sample]));
|
|
1088
|
-
const configFiles = topology.files.filter((file) => isConfigFile(file.relativePath));
|
|
1089
|
-
const testFiles = topology.files.filter((file) => isTestFile(file.relativePath));
|
|
1090
|
-
const domainFiles = topology.files.filter((file) => isDomainFile(file.relativePath));
|
|
1091
|
-
const componentSamples = codeSamples.filter((sample) => sample.pattern_analysis.family === "component").sort((left, right) => compareCandidateScore(buildComponentCandidateScore(right), buildComponentCandidateScore(left)));
|
|
1092
|
-
addCandidateFamily(
|
|
1093
|
-
selected,
|
|
1094
|
-
entryPoints.map((entryPoint) => ({
|
|
1095
|
-
path: entryPoint.path,
|
|
1096
|
-
family: "entry",
|
|
1097
|
-
rationale: `Representative ${entryPoint.reason} used as an application entry surface.`,
|
|
1098
|
-
score: buildEntryCandidateScore(entryPoint)
|
|
1099
|
-
})).sort((left, right) => compareCandidateScore(right.score, left.score)),
|
|
1100
|
-
ENTRY_FAMILY_LIMIT
|
|
1101
|
-
);
|
|
1102
|
-
addCandidateFamily(
|
|
1103
|
-
selected,
|
|
1104
|
-
componentSamples.map((sample) => ({
|
|
1105
|
-
path: sample.path,
|
|
1106
|
-
family: "component",
|
|
1107
|
-
rationale: sample.pattern_analysis.rationale,
|
|
1108
|
-
score: buildComponentCandidateScore(sample)
|
|
1109
|
-
})),
|
|
1110
|
-
FAMILY_LIMIT
|
|
1111
|
-
);
|
|
1112
|
-
addCandidateFamily(
|
|
1113
|
-
selected,
|
|
1114
|
-
configFiles.map((file) => ({
|
|
1115
|
-
path: file.relativePath,
|
|
1116
|
-
family: "config",
|
|
1117
|
-
rationale: "Bootstrap or compiler configuration file used to infer framework and project boundaries.",
|
|
1118
|
-
score: buildConfigCandidateScore(file.relativePath)
|
|
1119
|
-
})).sort((left, right) => compareCandidateScore(right.score, left.score)),
|
|
1120
|
-
FAMILY_LIMIT
|
|
1121
|
-
);
|
|
1122
|
-
addCandidateFamily(
|
|
1123
|
-
selected,
|
|
1124
|
-
testFiles.map((file) => ({
|
|
1125
|
-
path: file.relativePath,
|
|
1126
|
-
family: "test",
|
|
1127
|
-
rationale: "Existing test coverage surface that captures behavior expectations.",
|
|
1128
|
-
score: file.relativePath.includes("__tests__") ? 2 : 1
|
|
1129
|
-
})).sort((left, right) => compareCandidateScore(right.score, left.score)),
|
|
1130
|
-
FAMILY_LIMIT
|
|
1131
|
-
);
|
|
1132
|
-
addCandidateFamily(
|
|
1133
|
-
selected,
|
|
1134
|
-
domainFiles.filter((file) => !codeSamplesByPath.has(file.relativePath)).map((file) => ({
|
|
1135
|
-
path: file.relativePath,
|
|
1136
|
-
family: "domain",
|
|
1137
|
-
rationale: "Representative domain file outside entry/config/test hotspots.",
|
|
1138
|
-
score: buildDomainCandidateScore(file.relativePath)
|
|
1139
|
-
})).sort((left, right) => compareCandidateScore(right.score, left.score)),
|
|
1140
|
-
FAMILY_LIMIT
|
|
1141
|
-
);
|
|
1142
|
-
return [...selected.values()].slice(0, CANDIDATE_FILE_LIMIT);
|
|
1143
|
-
}
|
|
1144
|
-
function buildFrameworkAssertion(frameworkKind, topology, codeSamples) {
|
|
1145
|
-
if (frameworkKind === "unknown") {
|
|
1146
|
-
return createAssertion({
|
|
1147
|
-
type: "framework",
|
|
1148
|
-
statement: "Framework could not be determined from the sampled topology.",
|
|
1149
|
-
evidence: codeSamples.flatMap((sample) => sample.evidence).slice(0, 3),
|
|
1150
|
-
matched: 0,
|
|
1151
|
-
total: codeSamples.length,
|
|
1152
|
-
coOccurring: [],
|
|
1153
|
-
alternatives: ["Ask the user to confirm the primary framework"]
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
const matchedSamples = codeSamples.filter((sample) => matchesFrameworkPattern(frameworkKind, sample.pattern_analysis.pattern));
|
|
1157
|
-
const coOccurring = compactPatternNames([
|
|
1158
|
-
...matchedSamples.flatMap((sample) => sample.pattern_analysis.co_occurring),
|
|
1159
|
-
hasFile(topology.files, "project.config.json") ? "project-config-json" : null,
|
|
1160
|
-
(topology.by_ext[".meta"] ?? 0) > 0 ? "meta-sidecars" : null,
|
|
1161
|
-
hasFile(topology.files, "package.json") ? "package-json" : null
|
|
1162
|
-
]);
|
|
1163
|
-
const evidence = [
|
|
1164
|
-
...matchedSamples.flatMap((sample) => sample.evidence),
|
|
1165
|
-
...buildTopologyEvidence(topology, getExpectedConfigFiles(frameworkKind))
|
|
1166
|
-
].slice(0, 3);
|
|
1167
|
-
return createAssertion({
|
|
1168
|
-
type: "framework",
|
|
1169
|
-
statement: buildFrameworkStatement(frameworkKind),
|
|
1170
|
-
evidence,
|
|
1171
|
-
matched: matchedSamples.length,
|
|
1172
|
-
total: codeSamples.length,
|
|
1173
|
-
coOccurring,
|
|
1174
|
-
astLevel: matchedSamples.some((sample) => sample.pattern_analysis.ast_level),
|
|
1175
|
-
proposedRule: buildFrameworkRule(frameworkKind),
|
|
1176
|
-
alternatives: frameworkKind === "cocos-creator" ? ["Generic TypeScript utility modules"] : ["Alternative framework entry layout"]
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
function buildDominantPatternAssertion(codeSamples) {
|
|
1180
|
-
if (codeSamples.length === 0) {
|
|
1181
|
-
return null;
|
|
1182
|
-
}
|
|
1183
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1184
|
-
for (const sample of codeSamples) {
|
|
1185
|
-
const existing = counts.get(sample.pattern_analysis.pattern) ?? [];
|
|
1186
|
-
existing.push(sample);
|
|
1187
|
-
counts.set(sample.pattern_analysis.pattern, existing);
|
|
1188
|
-
}
|
|
1189
|
-
const dominant = [...counts.entries()].sort((left, right) => right[1].length - left[1].length)[0];
|
|
1190
|
-
if (dominant === void 0) {
|
|
1191
|
-
return null;
|
|
1192
|
-
}
|
|
1193
|
-
const [, samples] = dominant;
|
|
1194
|
-
const first = samples[0];
|
|
1195
|
-
return createAssertion({
|
|
1196
|
-
type: first.pattern_analysis.type,
|
|
1197
|
-
statement: first.pattern_analysis.statement,
|
|
1198
|
-
evidence: samples.flatMap((sample) => sample.evidence).slice(0, 3),
|
|
1199
|
-
matched: samples.length,
|
|
1200
|
-
total: codeSamples.length,
|
|
1201
|
-
coOccurring: compactPatternNames(samples.flatMap((sample) => sample.pattern_analysis.co_occurring)),
|
|
1202
|
-
astLevel: samples.some((sample) => sample.pattern_analysis.ast_level),
|
|
1203
|
-
proposedRule: first.pattern_analysis.proposed_rule,
|
|
1204
|
-
alternatives: first.pattern_analysis.alternatives
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
function buildEntryDirectoryAssertion(frameworkKind, codeSamples) {
|
|
1208
|
-
if (codeSamples.length === 0) {
|
|
1209
|
-
return null;
|
|
1210
|
-
}
|
|
1211
|
-
const directoryGroups = /* @__PURE__ */ new Map();
|
|
1212
|
-
for (const sample of codeSamples) {
|
|
1213
|
-
const directory2 = posix.dirname(sample.path);
|
|
1214
|
-
const existing = directoryGroups.get(directory2) ?? [];
|
|
1215
|
-
existing.push(sample);
|
|
1216
|
-
directoryGroups.set(directory2, existing);
|
|
1217
|
-
}
|
|
1218
|
-
const primaryDirectory = [...directoryGroups.entries()].sort((left, right) => right[1].length - left[1].length)[0];
|
|
1219
|
-
if (primaryDirectory === void 0) {
|
|
1220
|
-
return null;
|
|
1221
|
-
}
|
|
1222
|
-
const [directory, samples] = primaryDirectory;
|
|
1223
|
-
return createAssertion({
|
|
1224
|
-
type: "pattern",
|
|
1225
|
-
statement: `Entry samples are concentrated in ${directory}, indicating a stable primary source boundary.`,
|
|
1226
|
-
evidence: samples.flatMap((sample) => sample.evidence).slice(0, 3),
|
|
1227
|
-
matched: samples.length,
|
|
1228
|
-
total: codeSamples.length,
|
|
1229
|
-
coOccurring: compactPatternNames([
|
|
1230
|
-
directory === "." ? "root-entry" : directory,
|
|
1231
|
-
frameworkKind !== "unknown" ? frameworkKind : null,
|
|
1232
|
-
...samples.flatMap((sample) => sample.pattern_analysis.co_occurring.slice(0, 1))
|
|
1233
|
-
]),
|
|
1234
|
-
proposedRule: directory === "." ? "Keep primary entry files at the repository root only if the framework expects it." : `Treat ${directory} as the main execution boundary during initialization.`
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
function buildMetaSidecarAssertion(frameworkKind, topology) {
|
|
1238
|
-
const relevantScripts = topology.files.filter((file) => SCRIPT_EXTENSIONS.has(extname(file.relativePath)));
|
|
1239
|
-
if (relevantScripts.length === 0) {
|
|
1240
|
-
return null;
|
|
1241
|
-
}
|
|
1242
|
-
const matchedScripts = relevantScripts.filter((file) => hasFile(topology.files, `${file.relativePath}.meta`));
|
|
1243
|
-
if (matchedScripts.length === 0 && frameworkKind !== "cocos-creator") {
|
|
1244
|
-
return null;
|
|
1245
|
-
}
|
|
1246
|
-
return createAssertion({
|
|
1247
|
-
type: "invariant",
|
|
1248
|
-
statement: matchedScripts.length > 0 ? "Script files have adjacent .meta sidecars, which should be treated as coupled assets." : "No .meta sidecars were detected for sampled scripts.",
|
|
1249
|
-
evidence: matchedScripts.length > 0 ? matchedScripts.slice(0, 3).map((file) => makeSyntheticEvidence(`${file.relativePath}.meta`, `${file.relativePath}.meta sidecar present`)) : buildTopologyEvidence(topology, relevantScripts.slice(0, 1).map((file) => file.relativePath)),
|
|
1250
|
-
matched: matchedScripts.length,
|
|
1251
|
-
total: relevantScripts.length,
|
|
1252
|
-
coOccurring: compactPatternNames([
|
|
1253
|
-
matchedScripts.length > 0 ? "meta-sidecar" : null,
|
|
1254
|
-
frameworkKind === "cocos-creator" ? "cocos-creator" : null,
|
|
1255
|
-
relevantScripts.some((file) => file.relativePath.startsWith("assets/scripts/")) ? "assets-scripts" : null
|
|
1256
|
-
]),
|
|
1257
|
-
proposedRule: matchedScripts.length > 0 ? "Do not edit or delete .meta sidecars without explicit user confirmation." : void 0
|
|
1258
|
-
});
|
|
1259
|
-
}
|
|
1260
|
-
function buildConfigAssertion(frameworkKind, topology) {
|
|
1261
|
-
const expectedFiles = getExpectedConfigFiles(frameworkKind);
|
|
1262
|
-
if (expectedFiles.length === 0) {
|
|
1263
|
-
return null;
|
|
1264
|
-
}
|
|
1265
|
-
const matchedFiles = expectedFiles.filter((file) => hasFile(topology.files, file));
|
|
1266
|
-
return createAssertion({
|
|
1267
|
-
type: "invariant",
|
|
1268
|
-
statement: `Project configuration is anchored by ${expectedFiles.join(", ")}.`,
|
|
1269
|
-
evidence: buildTopologyEvidence(topology, matchedFiles),
|
|
1270
|
-
matched: matchedFiles.length,
|
|
1271
|
-
total: expectedFiles.length,
|
|
1272
|
-
coOccurring: compactPatternNames(matchedFiles.map(normalizeConfigPattern)),
|
|
1273
|
-
proposedRule: "Read bootstrap and compiler config before generating new rules or project structure."
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
function buildDomainAssertion(codeSamples) {
|
|
1277
|
-
if (codeSamples.length === 0) {
|
|
1278
|
-
return null;
|
|
1279
|
-
}
|
|
1280
|
-
const namedSamples = codeSamples.filter((sample) => {
|
|
1281
|
-
const fileBase = basename(sample.path, extname(sample.path));
|
|
1282
|
-
return sample.snippet.includes(`class ${fileBase}`) || sample.snippet.includes(`class ${sanitizeIdentifier(fileBase)}`);
|
|
1283
|
-
});
|
|
1284
|
-
if (namedSamples.length === 0) {
|
|
1285
|
-
return null;
|
|
1286
|
-
}
|
|
1287
|
-
const namedModules = compactPatternNames(namedSamples.map((sample) => basename(sample.path, extname(sample.path))));
|
|
1288
|
-
return createAssertion({
|
|
1289
|
-
type: "domain",
|
|
1290
|
-
statement: `Sampled modules are named as concrete domain concepts (${namedModules.join(", ")}).`,
|
|
1291
|
-
evidence: namedSamples.flatMap((sample) => sample.evidence).slice(0, 3),
|
|
1292
|
-
matched: namedSamples.length,
|
|
1293
|
-
total: codeSamples.length,
|
|
1294
|
-
coOccurring: compactPatternNames([
|
|
1295
|
-
namedSamples.every((sample) => /^[A-Z]/.test(basename(sample.path))) ? "pascal-case-modules" : null,
|
|
1296
|
-
namedModules.length >= 2 ? "domain-named-components" : null,
|
|
1297
|
-
namedSamples.some((sample) => sample.snippet.includes("start():")) ? "lifecycle-hook" : null
|
|
1298
|
-
]),
|
|
1299
|
-
proposedRule: "Preserve domain-specific module names when authoring knowledge entries that reference these modules."
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
function createAssertion(input) {
|
|
1303
|
-
const coverage = {
|
|
1304
|
-
ratio: input.total === 0 ? 0 : roundCoverageRatio(input.matched / input.total),
|
|
1305
|
-
total: input.total,
|
|
1306
|
-
matched: input.matched,
|
|
1307
|
-
co_occurring_patterns: compactPatternNames(input.coOccurring)
|
|
1308
|
-
};
|
|
1309
|
-
return {
|
|
1310
|
-
type: input.type,
|
|
1311
|
-
statement: input.statement,
|
|
1312
|
-
confidence: determineConfidence(coverage.ratio, coverage.co_occurring_patterns, input.astLevel ?? false),
|
|
1313
|
-
evidence: dedupeEvidence(input.evidence),
|
|
1314
|
-
coverage,
|
|
1315
|
-
proposed_rule: input.proposedRule,
|
|
1316
|
-
alternatives: input.alternatives
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
function buildEvidenceAnchors(relativePath, snippet, evidenceLines) {
|
|
1320
|
-
const lines = snippet.split("\n");
|
|
1321
|
-
const anchors = [];
|
|
1322
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1323
|
-
for (const pattern of evidenceLines) {
|
|
1324
|
-
const lineIndex = lines.findIndex((line) => line.includes(pattern));
|
|
1325
|
-
if (lineIndex === -1) {
|
|
1326
|
-
continue;
|
|
1327
|
-
}
|
|
1328
|
-
const key = `${relativePath}:${lineIndex + 1}`;
|
|
1329
|
-
if (seen.has(key)) {
|
|
1330
|
-
continue;
|
|
1331
|
-
}
|
|
1332
|
-
seen.add(key);
|
|
1333
|
-
anchors.push({
|
|
1334
|
-
file: relativePath,
|
|
1335
|
-
line: String(lineIndex + 1),
|
|
1336
|
-
snippet: lines[lineIndex]?.trim() ?? ""
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
if (anchors.length > 0) {
|
|
1340
|
-
return anchors;
|
|
1341
|
-
}
|
|
1342
|
-
const fallbackIndex = lines.findIndex((line) => line.trim().length > 0);
|
|
1343
|
-
return [
|
|
1344
|
-
{
|
|
1345
|
-
file: relativePath,
|
|
1346
|
-
line: String(fallbackIndex === -1 ? 1 : fallbackIndex + 1),
|
|
1347
|
-
snippet: fallbackIndex === -1 ? "" : lines[fallbackIndex]?.trim() ?? ""
|
|
1348
|
-
}
|
|
1349
|
-
];
|
|
1350
|
-
}
|
|
1351
|
-
function addCandidateFamily(selected, candidates, familyLimit) {
|
|
1352
|
-
let added = 0;
|
|
1353
|
-
for (const candidate of candidates) {
|
|
1354
|
-
if (selected.size >= CANDIDATE_FILE_LIMIT || added >= familyLimit || selected.has(candidate.path)) {
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
selected.set(candidate.path, {
|
|
1358
|
-
path: candidate.path,
|
|
1359
|
-
family: candidate.family,
|
|
1360
|
-
rationale: candidate.rationale
|
|
1361
|
-
});
|
|
1362
|
-
added += 1;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
function buildTopologyEvidence(topology, preferredPaths) {
|
|
1366
|
-
return preferredPaths.filter((path) => hasFile(topology.files, path)).slice(0, 3).map((path) => makeSyntheticEvidence(path, `${path} present in project topology`));
|
|
1367
|
-
}
|
|
1368
|
-
function makeSyntheticEvidence(file, snippet) {
|
|
1369
|
-
return {
|
|
1370
|
-
file,
|
|
1371
|
-
line: "1",
|
|
1372
|
-
snippet
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
function dedupeEvidence(evidence) {
|
|
1376
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1377
|
-
const deduped = [];
|
|
1378
|
-
for (const entry of evidence) {
|
|
1379
|
-
const key = `${entry.file}:${entry.line}`;
|
|
1380
|
-
if (seen.has(key)) {
|
|
1381
|
-
continue;
|
|
1382
|
-
}
|
|
1383
|
-
seen.add(key);
|
|
1384
|
-
deduped.push(entry);
|
|
1385
|
-
}
|
|
1386
|
-
return deduped.slice(0, 3);
|
|
1387
|
-
}
|
|
1388
|
-
function matchesFrameworkPattern(frameworkKind, pattern) {
|
|
1389
|
-
if (frameworkKind === "cocos-creator") {
|
|
1390
|
-
return pattern === "cocos-component-class";
|
|
1391
|
-
}
|
|
1392
|
-
if (frameworkKind === "next") {
|
|
1393
|
-
return pattern === "next-route-component";
|
|
1394
|
-
}
|
|
1395
|
-
if (frameworkKind === "vite") {
|
|
1396
|
-
return pattern === "vite-main-entry" || pattern === "react-root";
|
|
1397
|
-
}
|
|
1398
|
-
return pattern !== "source-entry";
|
|
1399
|
-
}
|
|
1400
|
-
function buildFrameworkStatement(frameworkKind) {
|
|
1401
|
-
if (frameworkKind === "cocos-creator") {
|
|
1402
|
-
return "Project strongly matches a Cocos Creator TypeScript component layout.";
|
|
1403
|
-
}
|
|
1404
|
-
if (frameworkKind === "next") {
|
|
1405
|
-
return "Project topology and entry samples align with a Next.js route-driven application.";
|
|
1406
|
-
}
|
|
1407
|
-
if (frameworkKind === "vite") {
|
|
1408
|
-
return "Project topology aligns with a Vite-style application bootstrap.";
|
|
1409
|
-
}
|
|
1410
|
-
return `Project surfaces align with ${frameworkKind}.`;
|
|
1411
|
-
}
|
|
1412
|
-
function buildFrameworkRule(frameworkKind) {
|
|
1413
|
-
if (frameworkKind === "cocos-creator") {
|
|
1414
|
-
return "Preserve Cocos component decorators, lifecycle methods, and paired .meta files during initialization.";
|
|
1415
|
-
}
|
|
1416
|
-
if (frameworkKind === "next") {
|
|
1417
|
-
return "Respect app/pages route boundaries when generating instructions or edits.";
|
|
1418
|
-
}
|
|
1419
|
-
if (frameworkKind === "vite") {
|
|
1420
|
-
return "Keep bootstrap logic centered on src/main.* and surrounding config files.";
|
|
1421
|
-
}
|
|
1422
|
-
return void 0;
|
|
1423
|
-
}
|
|
1424
|
-
function determineConfidence(ratio, coOccurringPatterns, astLevel, hasConflict = false) {
|
|
1425
|
-
if (hasConflict) {
|
|
1426
|
-
return "LOW";
|
|
1427
|
-
}
|
|
1428
|
-
if (astLevel) {
|
|
1429
|
-
return "HIGH";
|
|
1430
|
-
}
|
|
1431
|
-
if (ratio < 0.5) {
|
|
1432
|
-
return "LOW";
|
|
1433
|
-
}
|
|
1434
|
-
if (ratio >= 0.8 && coOccurringPatterns.length >= 2) {
|
|
1435
|
-
return "HIGH";
|
|
1436
|
-
}
|
|
1437
|
-
return "MEDIUM";
|
|
1438
|
-
}
|
|
1439
|
-
function compactPatternNames(patterns) {
|
|
1440
|
-
return [...new Set(patterns.filter((pattern) => pattern !== null && pattern !== void 0 && pattern.length > 0))];
|
|
1441
|
-
}
|
|
1442
|
-
function roundCoverageRatio(value) {
|
|
1443
|
-
return Math.round(value * 1e3) / 1e3;
|
|
1444
|
-
}
|
|
1445
|
-
function getExpectedConfigFiles(frameworkKind) {
|
|
1446
|
-
return EXPECTED_CONFIG_FILES_BY_FRAMEWORK[frameworkKind] ?? ["package.json"];
|
|
1447
|
-
}
|
|
1448
|
-
function hasFile(files, relativePath) {
|
|
1449
|
-
return files.some((file) => file.relativePath === relativePath);
|
|
1450
|
-
}
|
|
1451
|
-
function normalizeConfigPattern(relativePath) {
|
|
1452
|
-
return relativePath.replace(/\./g, "-");
|
|
1453
|
-
}
|
|
1454
|
-
function sanitizeIdentifier(value) {
|
|
1455
|
-
return value.replace(/[^A-Za-z0-9_$]/g, "");
|
|
1456
|
-
}
|
|
1457
|
-
function compareCandidateScore(left, right) {
|
|
1458
|
-
return left - right;
|
|
1459
|
-
}
|
|
1460
|
-
function buildEntryCandidateScore(entryPoint) {
|
|
1461
|
-
let score = 0;
|
|
1462
|
-
if (entryPoint.reason === "application entry") {
|
|
1463
|
-
score += 3;
|
|
1464
|
-
}
|
|
1465
|
-
if (entryPoint.reason.includes("route")) {
|
|
1466
|
-
score += 2;
|
|
1467
|
-
}
|
|
1468
|
-
if ((entryPoint.size_bytes ?? 0) > 0) {
|
|
1469
|
-
score += 1;
|
|
1470
|
-
}
|
|
1471
|
-
return score;
|
|
1472
|
-
}
|
|
1473
|
-
function buildComponentCandidateScore(sample) {
|
|
1474
|
-
let score = sample.pattern_analysis.co_occurring.length;
|
|
1475
|
-
if (sample.pattern_analysis.ast_level) {
|
|
1476
|
-
score += 3;
|
|
1477
|
-
}
|
|
1478
|
-
if (sample.pattern_analysis.confidence === "HIGH") {
|
|
1479
|
-
score += 2;
|
|
1480
|
-
}
|
|
1481
|
-
return score;
|
|
1482
|
-
}
|
|
1483
|
-
function buildConfigCandidateScore(relativePath) {
|
|
1484
|
-
if (relativePath === "project.config.json") {
|
|
1485
|
-
return 4;
|
|
1486
|
-
}
|
|
1487
|
-
if (relativePath === "package.json") {
|
|
1488
|
-
return 3;
|
|
1489
|
-
}
|
|
1490
|
-
if (relativePath === "tsconfig.json") {
|
|
1491
|
-
return 2;
|
|
1492
|
-
}
|
|
1493
|
-
return 1;
|
|
1494
|
-
}
|
|
1495
|
-
function buildDomainCandidateScore(relativePath) {
|
|
1496
|
-
let score = 0;
|
|
1497
|
-
if (relativePath.startsWith("src/") || relativePath.startsWith("assets/")) {
|
|
1498
|
-
score += 2;
|
|
1499
|
-
}
|
|
1500
|
-
if (SCRIPT_EXTENSIONS.has(extname(relativePath))) {
|
|
1501
|
-
score += 1;
|
|
1502
|
-
}
|
|
1503
|
-
if (relativePath.includes("/domain/") || relativePath.includes("/models/")) {
|
|
1504
|
-
score += 1;
|
|
1505
|
-
}
|
|
1506
|
-
return score;
|
|
1507
|
-
}
|
|
1508
|
-
function isConfigFile(relativePath) {
|
|
1509
|
-
return /(^|\/)(package\.json|project\.config\.json|tsconfig\.json|vite\.config\.[^.]+|next\.config\.[^.]+)$/.test(relativePath);
|
|
1510
|
-
}
|
|
1511
|
-
function isTestFile(relativePath) {
|
|
1512
|
-
return /(^|\/)(__tests__|tests)(\/|$)/.test(relativePath) || /\.(test|spec)\.[^.]+$/.test(relativePath);
|
|
1513
|
-
}
|
|
1514
|
-
function isDomainFile(relativePath) {
|
|
1515
|
-
const extension = extname(relativePath);
|
|
1516
|
-
if (!DOMAIN_FILE_EXTENSIONS.has(extension)) {
|
|
1517
|
-
return false;
|
|
1518
|
-
}
|
|
1519
|
-
return !isConfigFile(relativePath) && !isTestFile(relativePath);
|
|
1520
|
-
}
|
|
1521
|
-
function buildSkillRecommendations(frameworkKind, topology, readme, projectRoot) {
|
|
1522
|
-
return buildScanRecommendations(
|
|
1523
|
-
{
|
|
1524
|
-
frameworkKind,
|
|
1525
|
-
hasMeta: (topology.by_ext[".meta"] ?? 0) > 0,
|
|
1526
|
-
readmeOk: readme.quality === "ok"
|
|
1527
|
-
},
|
|
1528
|
-
getProjectTranslator(projectRoot)
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1531
|
-
function readProjectName(target) {
|
|
1532
|
-
const packageJsonPath = join5(target, "package.json");
|
|
1533
|
-
if (existsSync3(packageJsonPath)) {
|
|
1534
|
-
try {
|
|
1535
|
-
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
1536
|
-
if (packageJson.name !== void 0 && packageJson.name.trim().length > 0) {
|
|
1537
|
-
return packageJson.name;
|
|
1538
|
-
}
|
|
1539
|
-
} catch {
|
|
1540
|
-
return basename(target);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
return basename(target);
|
|
1544
|
-
}
|
|
1545
|
-
function getCliVersion() {
|
|
1546
|
-
return true ? "2.2.0-rc.1" : "unknown";
|
|
1547
|
-
}
|
|
1548
|
-
function sortRecord(record) {
|
|
1549
|
-
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
1550
|
-
}
|
|
1551
|
-
function toPosixPath(path) {
|
|
1552
|
-
return path.split(sep).join("/");
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
// src/commands/install.ts
|
|
1556
|
-
var LOCAL_FABRIC_SERVER_PATH = join6("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
1557
|
-
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
1558
|
-
var INIT_WIZARD_GROUP_CANCELLED = /* @__PURE__ */ Symbol("init-wizard-group-cancelled");
|
|
1559
|
-
var installCommand = defineCommand({
|
|
1560
|
-
meta: {
|
|
1561
|
-
name: "install",
|
|
1562
|
-
description: t("cli.install.description")
|
|
1563
|
-
},
|
|
1564
|
-
args: {
|
|
1565
|
-
debug: {
|
|
1566
|
-
type: "boolean",
|
|
1567
|
-
description: t("cli.install.args.debug.description"),
|
|
1568
|
-
default: false
|
|
1569
|
-
},
|
|
1570
|
-
"dry-run": {
|
|
1571
|
-
type: "boolean",
|
|
1572
|
-
description: t("cli.install.args.dry-run.description"),
|
|
1573
|
-
default: false
|
|
1574
|
-
},
|
|
1575
|
-
target: {
|
|
1576
|
-
type: "string",
|
|
1577
|
-
description: t("cli.install.args.target.description")
|
|
1578
|
-
},
|
|
1579
|
-
yes: {
|
|
1580
|
-
type: "boolean",
|
|
1581
|
-
description: t("cli.install.args.yes.description"),
|
|
1582
|
-
default: false
|
|
1583
|
-
},
|
|
1584
|
-
"force-skills-only": {
|
|
1585
|
-
type: "boolean",
|
|
1586
|
-
description: t("cli.install.args.force-skills-only.description"),
|
|
1587
|
-
default: false
|
|
1588
|
-
},
|
|
1589
|
-
"force-hooks-only": {
|
|
1590
|
-
type: "boolean",
|
|
1591
|
-
description: t("cli.install.args.force-hooks-only.description"),
|
|
1592
|
-
default: false
|
|
1593
|
-
},
|
|
1594
|
-
global: {
|
|
1595
|
-
type: "boolean",
|
|
1596
|
-
description: "Set up global Fabric (~/.fabric: uid + personal store + config)",
|
|
1597
|
-
default: false
|
|
1598
|
-
},
|
|
1599
|
-
url: {
|
|
1600
|
-
type: "string",
|
|
1601
|
-
description: "With --global: clone + mount this shared store remote"
|
|
1602
|
-
}
|
|
1603
|
-
},
|
|
1604
|
-
async run({ args }) {
|
|
1605
|
-
await runInitCommand(args);
|
|
1606
|
-
}
|
|
1607
|
-
});
|
|
1608
|
-
var install_default = installCommand;
|
|
1609
|
-
async function runSkillsOnlyRefresh(targetInput) {
|
|
1610
|
-
const target = normalizeTarget3(targetInput);
|
|
1611
|
-
const metaPath = join6(target, ".fabric", "agents.meta.json");
|
|
1612
|
-
if (!existsSync4(metaPath)) {
|
|
1613
|
-
const message = t("cli.install.force-skills-only.uninitialised.message");
|
|
1614
|
-
const hint = t("cli.install.force-skills-only.uninitialised.hint");
|
|
1615
|
-
process.stderr.write(`${message}
|
|
1616
|
-
${hint}
|
|
1617
|
-
`);
|
|
1618
|
-
process.exitCode = 1;
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
console.log(formatInitStageHeader(t("cli.install.force-skills-only.banner")));
|
|
1622
|
-
const results = [];
|
|
1623
|
-
results.push(...await cleanupDeprecatedSkills(target));
|
|
1624
|
-
results.push(...await installFabricArchiveSkill(target));
|
|
1625
|
-
results.push(...await installFabricReviewSkill(target));
|
|
1626
|
-
results.push(...await installFabricImportSkill(target));
|
|
1627
|
-
results.push(...await installFabricSyncSkill(target));
|
|
1628
|
-
results.push(...await installFabricStoreSkill(target));
|
|
1629
|
-
results.push(...await installFabricAuditSkill(target));
|
|
1630
|
-
results.push(...await installFabricConnectSkill(target));
|
|
1631
|
-
results.push(...await installSharedSkillLib(target));
|
|
1632
|
-
let written = 0;
|
|
1633
|
-
let skipped = 0;
|
|
1634
|
-
let errors = 0;
|
|
1635
|
-
for (const r of results) {
|
|
1636
|
-
if (r.status === "written") written += 1;
|
|
1637
|
-
else if (r.status === "skipped") skipped += 1;
|
|
1638
|
-
else if (r.status === "error") errors += 1;
|
|
1639
|
-
}
|
|
1640
|
-
console.log(
|
|
1641
|
-
t("cli.install.force-skills-only.summary", {
|
|
1642
|
-
written: String(written),
|
|
1643
|
-
skipped: String(skipped),
|
|
1644
|
-
errors: String(errors)
|
|
1645
|
-
})
|
|
1646
|
-
);
|
|
1647
|
-
if (errors > 0) {
|
|
1648
|
-
for (const r of results) {
|
|
1649
|
-
if (r.status === "error") {
|
|
1650
|
-
process.stderr.write(` ${r.step} ${r.path}: ${r.message ?? "error"}
|
|
1651
|
-
`);
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
process.exitCode = 1;
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
async function runHooksOnlyRefresh(targetInput) {
|
|
1658
|
-
const target = normalizeTarget3(targetInput);
|
|
1659
|
-
const metaPath = join6(target, ".fabric", "agents.meta.json");
|
|
1660
|
-
if (!existsSync4(metaPath)) {
|
|
1661
|
-
const message = t("cli.install.force-hooks-only.uninitialised.message");
|
|
1662
|
-
const hint = t("cli.install.force-hooks-only.uninitialised.hint");
|
|
1663
|
-
process.stderr.write(`${message}
|
|
1664
|
-
${hint}
|
|
1665
|
-
`);
|
|
1666
|
-
process.exitCode = 1;
|
|
1667
|
-
return;
|
|
1668
|
-
}
|
|
1669
|
-
console.log(formatInitStageHeader(t("cli.install.force-hooks-only.banner")));
|
|
1670
|
-
const result = await installHooks(target);
|
|
1671
|
-
console.log(
|
|
1672
|
-
t("cli.install.force-hooks-only.summary", {
|
|
1673
|
-
written: String(result.installed.length),
|
|
1674
|
-
skipped: String(result.skipped.length),
|
|
1675
|
-
errors: String(result.errors.length)
|
|
1676
|
-
})
|
|
1677
|
-
);
|
|
1678
|
-
if (result.errors.length > 0) {
|
|
1679
|
-
for (const err of result.errors) {
|
|
1680
|
-
process.stderr.write(` ${err}
|
|
1681
|
-
`);
|
|
1682
|
-
}
|
|
1683
|
-
process.exitCode = 1;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
async function runInitCommand(args) {
|
|
1687
|
-
const logger = createDebugLogger(args.debug);
|
|
1688
|
-
if (args.global === true) {
|
|
1689
|
-
await runGlobalInstall({ url: args.url });
|
|
1690
|
-
return;
|
|
1691
|
-
}
|
|
1692
|
-
const resolution = resolveDevMode(args.target, process.cwd());
|
|
1693
|
-
if (args["force-skills-only"] === true) {
|
|
1694
|
-
await runSkillsOnlyRefresh(resolution.target);
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
if (args["force-hooks-only"] === true) {
|
|
1698
|
-
await runHooksOnlyRefresh(resolution.target);
|
|
1699
|
-
return;
|
|
1700
|
-
}
|
|
1701
|
-
const intent = resolveInitCliIntent(args, resolution.target);
|
|
1702
|
-
logger(`init target source: ${resolution.source}`);
|
|
1703
|
-
for (const step of resolution.chain) {
|
|
1704
|
-
logger(step);
|
|
1705
|
-
}
|
|
1706
|
-
const supports = detectClientSupports(intent.target);
|
|
1707
|
-
const basePlan = await buildInitExecutionPlan({
|
|
1708
|
-
target: intent.target,
|
|
1709
|
-
options: intent.options,
|
|
1710
|
-
mcpInstallMode: intent.mcpInstallMode,
|
|
1711
|
-
claudeMcpScope: intent.claudeMcpScope,
|
|
1712
|
-
interactive: intent.interactiveSummary && !intent.wizardEnabled,
|
|
1713
|
-
supports
|
|
1714
|
-
});
|
|
1715
|
-
const plan = intent.wizardEnabled ? await resolveInitExecutionPlanWithWizard(basePlan, createDefaultInitWizardAdapter()) : basePlan;
|
|
1716
|
-
if (plan === null) {
|
|
1717
|
-
process.exitCode = 130;
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
const result = await executeInitExecutionPlan(plan);
|
|
1721
|
-
if (!intent.options.planOnly) {
|
|
1722
|
-
console.log("");
|
|
1723
|
-
console.log(t("cli.install.next-steps"));
|
|
1724
|
-
console.log("");
|
|
1725
|
-
console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
|
|
1726
|
-
}
|
|
1727
|
-
return result;
|
|
1728
|
-
}
|
|
1729
|
-
var FABRIC_GITIGNORE_CONTENT = [
|
|
1730
|
-
"# Fabric per-dev activity ledgers & caches \u2014 auto-generated, not shared.",
|
|
1731
|
-
"# Managed by `fabric install`; edit freely (re-install never overwrites this).",
|
|
1732
|
-
"events.jsonl",
|
|
1733
|
-
"metrics.jsonl",
|
|
1734
|
-
"cite-rollup.jsonl",
|
|
1735
|
-
"injections.jsonl",
|
|
1736
|
-
".cache/",
|
|
1737
|
-
"*.lock",
|
|
1738
|
-
"*.corrupted.*",
|
|
1739
|
-
""
|
|
1740
|
-
].join("\n");
|
|
1741
|
-
function writeDefaultGitignore(fabricDir) {
|
|
1742
|
-
const target = join6(fabricDir, ".gitignore");
|
|
1743
|
-
if (existsSync4(target)) return;
|
|
1744
|
-
mkdirSync2(fabricDir, { recursive: true });
|
|
1745
|
-
writeFileSync(target, FABRIC_GITIGNORE_CONTENT, "utf8");
|
|
1746
|
-
}
|
|
1747
|
-
function writeDefaultFabricConfig(fabricDir, targetRoot) {
|
|
1748
|
-
const target = join6(fabricDir, "fabric-config.json");
|
|
1749
|
-
if (existsSync4(target)) return;
|
|
1750
|
-
const detectedLanguage = detectExistingLanguage(targetRoot);
|
|
1751
|
-
const FABRIC_CONFIG_DEFAULTS = {
|
|
1752
|
-
// Scan/import language policy. Fixated at init time by probing
|
|
1753
|
-
// README.md + docs/*.md (CJK ratio > 0.3 → "zh-CN", else "en"). Users
|
|
1754
|
-
// can edit `.fabric/fabric-config.json` to override. See
|
|
1755
|
-
// packages/shared/src/schemas/fabric-config.ts for the enum.
|
|
1756
|
-
fabric_language: detectedLanguage,
|
|
1757
|
-
// fabric-hint Stop hook Signal A (archive): time-branch threshold, hours
|
|
1758
|
-
// since last knowledge_proposed event.
|
|
1759
|
-
archive_hint_hours: 24,
|
|
1760
|
-
// fabric-hint Stop hook cooldown after ANY signal fires, in hours.
|
|
1761
|
-
archive_hint_cooldown_hours: 12,
|
|
1762
|
-
// fabric-hint Stop hook Signal B (review): pending-count cutoff.
|
|
1763
|
-
review_hint_pending_count: 10,
|
|
1764
|
-
// fabric-hint Stop hook Signal B (review): pending-age cutoff in days.
|
|
1765
|
-
review_hint_pending_age_days: 7,
|
|
1766
|
-
// fabric-hint Stop hook Signal D (maintenance): days since last doctor.
|
|
1767
|
-
maintenance_hint_days: 14,
|
|
1768
|
-
// fabric-hint Stop hook Signal D (maintenance): cooldown between
|
|
1769
|
-
// reminders, in days.
|
|
1770
|
-
maintenance_hint_cooldown_days: 7,
|
|
1771
|
-
// fabric-hint Stop hook Signal A (archive): edit-count branch threshold;
|
|
1772
|
-
// PreToolUse fires recorded in .fabric/.cache/edit-counter since the
|
|
1773
|
-
// last knowledge_proposed event.
|
|
1774
|
-
archive_edit_threshold: 20,
|
|
1775
|
-
// fabric-hint Stop hook Signal C (import) + doctor lint #22: canonical
|
|
1776
|
-
// knowledge node count below this value flags an underseeded workspace.
|
|
1777
|
-
underseed_node_threshold: 10,
|
|
1778
|
-
// rc.9+ (skill-contract-fix B1): fabric-import first-run git-history
|
|
1779
|
-
// window in months. Default 60 captures the bulk of a mature repo's
|
|
1780
|
-
// signal in one pass; lower to 12-24 for fresh / small repos.
|
|
1781
|
-
import_window_first_run_months: 60,
|
|
1782
|
-
// rc.9+ (skill-contract-fix B1): fabric-import rerun window in months.
|
|
1783
|
-
// Default 2; raise to 6 if the workspace pauses imports for long stretches.
|
|
1784
|
-
import_window_rerun_months: 2,
|
|
1785
|
-
// rc.9+ (skill-contract-fix B1): hard cap on pending entries produced
|
|
1786
|
-
// per fabric-import invocation. Default 10 matches one-sitting triage.
|
|
1787
|
-
import_max_pending_per_run: 10,
|
|
1788
|
-
// rc.9+ (skill-contract-fix B1): hard cap on commits scanned per
|
|
1789
|
-
// fabric-import invocation. Default 500 covers ~2 months of typical churn.
|
|
1790
|
-
import_max_commits_scan: 500,
|
|
1791
|
-
// rc.9+ (skill-contract-fix B1): canonical-node count above which
|
|
1792
|
-
// fabric-import suggests review over importing more. Default 50.
|
|
1793
|
-
import_skip_canonical_threshold: 50,
|
|
1794
|
-
// rc.9+ (skill-contract-fix B1): max candidates per fabric-archive batch.
|
|
1795
|
-
// Default 8 keeps each batch reviewable in one sitting.
|
|
1796
|
-
archive_max_candidates_per_batch: 8,
|
|
1797
|
-
// rc.9+ (skill-contract-fix B1): max recently-touched paths in
|
|
1798
|
-
// fabric-archive's relevance digest. Default 20.
|
|
1799
|
-
archive_max_recent_paths: 20,
|
|
1800
|
-
// rc.9+ (skill-contract-fix B1): max prior fabric-archive sessions
|
|
1801
|
-
// summarised in the digest the skill loads on start. Default 10.
|
|
1802
|
-
archive_digest_max_sessions: 10,
|
|
1803
|
-
// rc.9+ (skill-contract-fix B1): max review results per topic cluster
|
|
1804
|
-
// in fabric-review. Default 8.
|
|
1805
|
-
review_topic_result_cap: 8,
|
|
1806
|
-
// rc.9+ (skill-contract-fix B1): age (days) above which a pending entry
|
|
1807
|
-
// is considered stale by fabric-review. Default 14.
|
|
1808
|
-
review_stale_pending_days: 14
|
|
1809
|
-
};
|
|
1810
|
-
mkdirSync2(fabricDir, { recursive: true });
|
|
1811
|
-
writeFileSync(target, JSON.stringify(FABRIC_CONFIG_DEFAULTS, null, 2) + "\n", "utf8");
|
|
1812
|
-
log.info(
|
|
1813
|
-
`Detected and fixated fabric_language = ${detectedLanguage}; edit ${target} to override.`
|
|
1814
|
-
);
|
|
1815
|
-
}
|
|
1816
|
-
function resolveInitCliIntent(args, targetInput) {
|
|
1817
|
-
const target = normalizeTarget3(targetInput);
|
|
1818
|
-
const mcpInstallMode = "global";
|
|
1819
|
-
const claudeMcpScope = "project";
|
|
1820
|
-
const terminalInteractive = isInteractiveInit();
|
|
1821
|
-
const planOnly = args["dry-run"] === true;
|
|
1822
|
-
const options = {
|
|
1823
|
-
planOnly
|
|
1824
|
-
};
|
|
1825
|
-
return {
|
|
1826
|
-
target,
|
|
1827
|
-
options,
|
|
1828
|
-
mcpInstallMode,
|
|
1829
|
-
claudeMcpScope,
|
|
1830
|
-
interactiveSummary: terminalInteractive,
|
|
1831
|
-
wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
async function buildInitExecutionPlan(input) {
|
|
1835
|
-
const options = input.options ?? {};
|
|
1836
|
-
const scaffold = await buildInitFabricPlan(input.target, options);
|
|
1837
|
-
const supports = input.supports ?? detectClientSupports(input.target);
|
|
1838
|
-
const mcpInstallMode = input.mcpInstallMode ?? "global";
|
|
1839
|
-
const claudeMcpScope = input.claudeMcpScope ?? "project";
|
|
1840
|
-
const stages = [
|
|
1841
|
-
{ name: "bootstrap", skipped: Boolean(options.skipBootstrap) },
|
|
1842
|
-
{
|
|
1843
|
-
name: "mcp",
|
|
1844
|
-
skipped: Boolean(options.skipMcp),
|
|
1845
|
-
installMode: mcpInstallMode,
|
|
1846
|
-
claudeMcpScope,
|
|
1847
|
-
localServerPath: mcpInstallMode === "local" ? LOCAL_FABRIC_SERVER_PATH : void 0,
|
|
1848
|
-
packageManager: mcpInstallMode === "local" ? detectPackageManager(input.target) : void 0
|
|
1849
|
-
},
|
|
1850
|
-
{ name: "hooks", skipped: Boolean(options.skipHooks) }
|
|
1851
|
-
];
|
|
1852
|
-
return {
|
|
1853
|
-
target: input.target,
|
|
1854
|
-
options,
|
|
1855
|
-
mcpInstallMode,
|
|
1856
|
-
claudeMcpScope,
|
|
1857
|
-
interactive: input.interactive ?? false,
|
|
1858
|
-
supports,
|
|
1859
|
-
scaffold,
|
|
1860
|
-
stages,
|
|
1861
|
-
steps: [
|
|
1862
|
-
{ name: "preflight" },
|
|
1863
|
-
{ name: "scaffold" },
|
|
1864
|
-
...stages.map((stage) => ({ name: stage.name, skipped: stage.skipped })),
|
|
1865
|
-
{ name: "post-setup" }
|
|
1866
|
-
]
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
async function executeInitExecutionPlan(plan) {
|
|
1870
|
-
if (plan.interactive) {
|
|
1871
|
-
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
1872
|
-
}
|
|
1873
|
-
const scaffoldStates = [
|
|
1874
|
-
{ path: plan.scaffold.metaPath, state: plan.scaffold.metaState },
|
|
1875
|
-
{ path: plan.scaffold.eventsPath, state: plan.scaffold.eventsState },
|
|
1876
|
-
{ path: plan.scaffold.forensicPath, state: plan.scaffold.forensicState }
|
|
1877
|
-
];
|
|
1878
|
-
if (plan.options.planOnly) {
|
|
1879
|
-
printInitPlanPreview(plan);
|
|
1880
|
-
printInitDiffStateTable(scaffoldStates);
|
|
1881
|
-
return {
|
|
1882
|
-
plan,
|
|
1883
|
-
created: buildPlanOnlyScaffoldResult(plan.scaffold),
|
|
1884
|
-
stageResults: plan.stages.map((stage) => ({ name: stage.name, disposition: "skipped" })),
|
|
1885
|
-
finalSupports: plan.supports
|
|
1886
|
-
};
|
|
1887
|
-
}
|
|
1888
|
-
if (existsSync4(plan.scaffold.fabricDir) && !statSync4(plan.scaffold.fabricDir).isDirectory()) {
|
|
1889
|
-
throw new Error(
|
|
1890
|
-
t("cli.install.diff.drift-abort", { path: plan.scaffold.fabricDir })
|
|
1891
|
-
);
|
|
1892
|
-
}
|
|
1893
|
-
const drifted = scaffoldStates.find(
|
|
1894
|
-
(entry) => entry.state === "drifted" || entry.state === "user-modified"
|
|
1895
|
-
);
|
|
1896
|
-
if (drifted !== void 0) {
|
|
1897
|
-
throw new Error(t("cli.install.diff.drift-abort", { path: drifted.path }));
|
|
1898
|
-
}
|
|
1899
|
-
let created = null;
|
|
1900
|
-
const stageResults = [];
|
|
1901
|
-
let finalSupports = plan.supports;
|
|
1902
|
-
for (const step of plan.steps) {
|
|
1903
|
-
switch (step.name) {
|
|
1904
|
-
case "preflight":
|
|
1905
|
-
break;
|
|
1906
|
-
case "scaffold":
|
|
1907
|
-
created = await executeInitFabricPlan(plan.scaffold);
|
|
1908
|
-
printInitScaffoldResult(created);
|
|
1909
|
-
break;
|
|
1910
|
-
case "bootstrap":
|
|
1911
|
-
case "mcp":
|
|
1912
|
-
case "hooks":
|
|
1913
|
-
stageResults.push(await executeInitStagePlan(plan, step.name));
|
|
1914
|
-
break;
|
|
1915
|
-
case "post-setup":
|
|
1916
|
-
finalSupports = detectClientSupports(plan.target);
|
|
1917
|
-
printInitPostSetup(plan, stageResults, finalSupports);
|
|
1918
|
-
break;
|
|
1919
|
-
default:
|
|
1920
|
-
exhaustiveInitExecutionStep(step);
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
if (scaffoldStates.every((entry) => entry.state === "present-canonical")) {
|
|
1924
|
-
console.log(
|
|
1925
|
-
t("cli.install.diff.canonical", { count: String(scaffoldStates.length) })
|
|
1926
|
-
);
|
|
1927
|
-
}
|
|
1928
|
-
return {
|
|
1929
|
-
plan,
|
|
1930
|
-
created: created ?? unreachableInitScaffold(),
|
|
1931
|
-
stageResults,
|
|
1932
|
-
finalSupports
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
var KNOWLEDGE_SUBDIRS = ["decisions", "pitfalls", "guidelines", "models", "processes", "pending"];
|
|
1936
|
-
function resolvePersonalFabricRoot() {
|
|
1937
|
-
return process.env.FABRIC_HOME ?? homedir();
|
|
1938
|
-
}
|
|
1939
|
-
async function buildInitFabricPlan(target, options) {
|
|
1940
|
-
assertExistingDirectory3(target);
|
|
1941
|
-
const fabricDir = join6(target, ".fabric");
|
|
1942
|
-
const agentsMdPath = join6(target, "AGENTS.md");
|
|
1943
|
-
const agentsMdAction = existsSync4(agentsMdPath) ? "preserved" : "created";
|
|
1944
|
-
const knowledgeDir = join6(fabricDir, "knowledge");
|
|
1945
|
-
const personalKnowledgeDir = join6(resolvePersonalFabricRoot(), ".fabric", "knowledge");
|
|
1946
|
-
const forensicPath = join6(fabricDir, "forensic.json");
|
|
1947
|
-
const eventsPath = join6(fabricDir, "events.jsonl");
|
|
1948
|
-
const metaPath = join6(fabricDir, "agents.meta.json");
|
|
1949
|
-
const replaceFabricDir = shouldReplaceWritableDirectory(fabricDir, options);
|
|
1950
|
-
const knowledgeDirAction = existsSync4(knowledgeDir) ? "overwritten" : "created";
|
|
1951
|
-
const metaClassification = classifyFreshPath(metaPath, "structural");
|
|
1952
|
-
const eventsClassification = classifyFreshPath(eventsPath, "presence");
|
|
1953
|
-
const forensicClassification = classifyFreshPath(forensicPath, "always-rewrite");
|
|
1954
|
-
const metaAction = diffStateToWriteAction(metaClassification.state);
|
|
1955
|
-
const eventsAction = diffStateToWriteAction(eventsClassification.state);
|
|
1956
|
-
const forensicAction = diffStateToWriteAction(forensicClassification.state);
|
|
1957
|
-
const showScanProgress = process.stderr.isTTY === true;
|
|
1958
|
-
if (showScanProgress) {
|
|
1959
|
-
process.stderr.write(`${t("cli.install.scanning")}
|
|
1960
|
-
`);
|
|
1961
|
-
}
|
|
1962
|
-
const forensicReport = await buildForensicReport(target);
|
|
1963
|
-
if (showScanProgress) {
|
|
1964
|
-
process.stderr.write(`${t("cli.install.scan-complete")}
|
|
1965
|
-
`);
|
|
1966
|
-
}
|
|
1967
|
-
const meta = createInitialMeta();
|
|
1968
|
-
return {
|
|
1969
|
-
target,
|
|
1970
|
-
options,
|
|
1971
|
-
fabricDir,
|
|
1972
|
-
replaceFabricDir,
|
|
1973
|
-
agentsMdPath,
|
|
1974
|
-
agentsMdAction,
|
|
1975
|
-
knowledgeDir,
|
|
1976
|
-
knowledgeDirAction,
|
|
1977
|
-
personalKnowledgeDir,
|
|
1978
|
-
metaPath,
|
|
1979
|
-
metaAction,
|
|
1980
|
-
meta,
|
|
1981
|
-
eventsPath,
|
|
1982
|
-
eventsAction,
|
|
1983
|
-
forensicPath,
|
|
1984
|
-
forensicAction,
|
|
1985
|
-
forensicReport,
|
|
1986
|
-
metaState: metaClassification.state,
|
|
1987
|
-
eventsState: eventsClassification.state,
|
|
1988
|
-
forensicState: forensicClassification.state
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
async function executeInitFabricPlan(plan) {
|
|
1992
|
-
if (plan.replaceFabricDir) {
|
|
1993
|
-
rmSync2(plan.fabricDir, { force: true });
|
|
1994
|
-
}
|
|
1995
|
-
mkdirSync2(plan.fabricDir, { recursive: true });
|
|
1996
|
-
writeDefaultFabricConfig(plan.fabricDir, plan.target);
|
|
1997
|
-
writeDefaultGitignore(plan.fabricDir);
|
|
1998
|
-
mkdirSync2(plan.knowledgeDir, { recursive: true });
|
|
1999
|
-
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
2000
|
-
const teamSubDir = join6(plan.knowledgeDir, sub);
|
|
2001
|
-
mkdirSync2(teamSubDir, { recursive: true });
|
|
2002
|
-
const teamGitkeep = join6(teamSubDir, ".gitkeep");
|
|
2003
|
-
if (!existsSync4(teamGitkeep)) {
|
|
2004
|
-
writeFileSync(teamGitkeep, "", "utf8");
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
try {
|
|
2008
|
-
mkdirSync2(plan.personalKnowledgeDir, { recursive: true });
|
|
2009
|
-
for (const sub of KNOWLEDGE_SUBDIRS) {
|
|
2010
|
-
mkdirSync2(join6(plan.personalKnowledgeDir, sub), { recursive: true });
|
|
2011
|
-
}
|
|
2012
|
-
} catch {
|
|
2013
|
-
}
|
|
2014
|
-
if (plan.metaState === "missing") {
|
|
2015
|
-
preparePlannedPath(plan.metaPath, plan.metaAction);
|
|
2016
|
-
await atomicWriteJson(plan.metaPath, plan.meta);
|
|
2017
|
-
}
|
|
2018
|
-
if (plan.eventsState === "missing") {
|
|
2019
|
-
preparePlannedPath(plan.eventsPath, plan.eventsAction);
|
|
2020
|
-
mkdirSync2(dirname(plan.eventsPath), { recursive: true });
|
|
2021
|
-
writeFileSync(plan.eventsPath, "", "utf8");
|
|
2022
|
-
}
|
|
2023
|
-
preparePlannedPath(plan.forensicPath, plan.forensicAction);
|
|
2024
|
-
await atomicWriteJson(plan.forensicPath, plan.forensicReport);
|
|
2025
|
-
if (existsSync4(plan.eventsPath)) {
|
|
2026
|
-
const applied = [];
|
|
2027
|
-
const canonical = [];
|
|
2028
|
-
const drifted = [];
|
|
2029
|
-
for (const entry of [
|
|
2030
|
-
{ path: plan.metaPath, state: plan.metaState },
|
|
2031
|
-
{ path: plan.eventsPath, state: plan.eventsState },
|
|
2032
|
-
{ path: plan.forensicPath, state: plan.forensicState }
|
|
2033
|
-
]) {
|
|
2034
|
-
if (entry.state === "missing") {
|
|
2035
|
-
applied.push(entry.path);
|
|
2036
|
-
} else if (entry.state === "present-canonical") {
|
|
2037
|
-
canonical.push(entry.path);
|
|
2038
|
-
} else {
|
|
2039
|
-
drifted.push(entry.path);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
appendInstallDiffLedgerEvent(plan.eventsPath, { applied, canonical, drifted });
|
|
2043
|
-
}
|
|
2044
|
-
return {
|
|
2045
|
-
agentsMdPath: plan.agentsMdPath,
|
|
2046
|
-
agentsMdAction: plan.agentsMdAction,
|
|
2047
|
-
knowledgeDir: plan.knowledgeDir,
|
|
2048
|
-
knowledgeDirAction: plan.knowledgeDirAction,
|
|
2049
|
-
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2050
|
-
metaPath: plan.metaPath,
|
|
2051
|
-
metaAction: plan.metaAction,
|
|
2052
|
-
eventsPath: plan.eventsPath,
|
|
2053
|
-
eventsAction: plan.eventsAction,
|
|
2054
|
-
forensicPath: plan.forensicPath,
|
|
2055
|
-
forensicAction: plan.forensicAction
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
async function initFabric(target, options) {
|
|
2059
|
-
return await executeInitFabricPlan(await buildInitFabricPlan(target, options));
|
|
2060
|
-
}
|
|
2061
|
-
function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
|
|
2062
|
-
return terminalInteractive && args.yes !== true;
|
|
2063
|
-
}
|
|
2064
|
-
async function resolveInitExecutionPlanWithWizard(basePlan, wizardAdapter) {
|
|
2065
|
-
const selection = await wizardAdapter.run({
|
|
2066
|
-
target: basePlan.target,
|
|
2067
|
-
options: basePlan.options,
|
|
2068
|
-
supports: basePlan.supports,
|
|
2069
|
-
mcpInstallMode: basePlan.mcpInstallMode,
|
|
2070
|
-
claudeMcpScope: basePlan.claudeMcpScope,
|
|
2071
|
-
lockedStages: []
|
|
2072
|
-
});
|
|
2073
|
-
if (selection === null) {
|
|
2074
|
-
return null;
|
|
2075
|
-
}
|
|
2076
|
-
return buildInitExecutionPlan({
|
|
2077
|
-
target: basePlan.target,
|
|
2078
|
-
options: {
|
|
2079
|
-
...basePlan.options,
|
|
2080
|
-
skipBootstrap: !selection.bootstrap,
|
|
2081
|
-
skipMcp: !selection.mcp,
|
|
2082
|
-
skipHooks: !selection.hooks
|
|
2083
|
-
},
|
|
2084
|
-
mcpInstallMode: selection.mcp ? selection.mcpInstallMode : basePlan.mcpInstallMode,
|
|
2085
|
-
claudeMcpScope: selection.claudeMcpScope,
|
|
2086
|
-
interactive: false,
|
|
2087
|
-
supports: basePlan.supports
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2090
|
-
function unreachableInitScaffold() {
|
|
2091
|
-
throw new Error("Init scaffold step did not execute");
|
|
2092
|
-
}
|
|
2093
|
-
function exhaustiveInitExecutionStep(value) {
|
|
2094
|
-
throw new Error(`Unsupported init execution step: ${JSON.stringify(value)}`);
|
|
2095
|
-
}
|
|
2096
|
-
function exhaustiveInitStagePlan(value) {
|
|
2097
|
-
throw new Error(`Unsupported init stage plan: ${JSON.stringify(value)}`);
|
|
2098
|
-
}
|
|
2099
|
-
function printInitScaffoldResult(created) {
|
|
2100
|
-
console.log(formatAgentsMdAction(created.agentsMdPath, created.agentsMdAction));
|
|
2101
|
-
console.log(formatInitPathAction(created.knowledgeDir, created.knowledgeDirAction));
|
|
2102
|
-
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
2103
|
-
console.log(formatInitPathAction(created.eventsPath, created.eventsAction));
|
|
2104
|
-
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
2105
|
-
}
|
|
2106
|
-
function printInitPostSetup(plan, stageResults, finalSupports) {
|
|
2107
|
-
if (shouldPrintHooksNextStep(plan.options, stageResults)) {
|
|
2108
|
-
console.log(
|
|
2109
|
-
t("cli.install.next-step", {
|
|
2110
|
-
label: nextLabel(),
|
|
2111
|
-
message: paint.muted(t("cli.install.next-step.message"))
|
|
2112
|
-
})
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
console.log(
|
|
2116
|
-
t("cli.install.reason-message", {
|
|
2117
|
-
label: reasonLabel(),
|
|
2118
|
-
message: paint.muted(formatInitReasonMessage(finalSupports))
|
|
2119
|
-
})
|
|
2120
|
-
);
|
|
2121
|
-
printInitStageSummary(stageResults);
|
|
2122
|
-
printInitCapabilitySummary(finalSupports, stageResults, plan.options);
|
|
2123
|
-
const fabricLanguage = readFabricLanguagePreference(plan.target);
|
|
2124
|
-
console.log(
|
|
2125
|
-
paint.muted(t("cli.install.language_preference_hint", { value: fabricLanguage }))
|
|
2126
|
-
);
|
|
2127
|
-
}
|
|
2128
|
-
function printInitDiffStateTable(entries) {
|
|
2129
|
-
for (const entry of entries) {
|
|
2130
|
-
console.log(` ${formatDiffFileState(entry.state)} ${entry.path}`);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
function printInitPlanPreview(plan) {
|
|
2134
|
-
console.log(t("cli.install.plan.preview-title"));
|
|
2135
|
-
printInitPlanSummary(plan.target, plan.options, plan.mcpInstallMode, plan.supports);
|
|
2136
|
-
console.log(
|
|
2137
|
-
t("cli.install.plan.preview-result", {
|
|
2138
|
-
mode: t("cli.install.mode.default"),
|
|
2139
|
-
bootstrap: yesNoLabel(!plan.options.skipBootstrap),
|
|
2140
|
-
mcp: yesNoLabel(!plan.options.skipMcp),
|
|
2141
|
-
hooks: yesNoLabel(!plan.options.skipHooks)
|
|
2142
|
-
})
|
|
2143
|
-
);
|
|
2144
|
-
}
|
|
2145
|
-
function buildPlanOnlyScaffoldResult(plan) {
|
|
2146
|
-
return {
|
|
2147
|
-
agentsMdPath: plan.agentsMdPath,
|
|
2148
|
-
agentsMdAction: plan.agentsMdAction,
|
|
2149
|
-
knowledgeDir: plan.knowledgeDir,
|
|
2150
|
-
knowledgeDirAction: plan.knowledgeDirAction,
|
|
2151
|
-
personalKnowledgeDir: plan.personalKnowledgeDir,
|
|
2152
|
-
metaPath: plan.metaPath,
|
|
2153
|
-
metaAction: plan.metaAction,
|
|
2154
|
-
eventsPath: plan.eventsPath,
|
|
2155
|
-
eventsAction: plan.eventsAction,
|
|
2156
|
-
forensicPath: plan.forensicPath,
|
|
2157
|
-
forensicAction: plan.forensicAction
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
async function executeInitStagePlan(plan, stageName) {
|
|
2161
|
-
const stage = plan.stages.find((entry) => entry.name === stageName);
|
|
2162
|
-
if (stage === void 0) {
|
|
2163
|
-
throw new Error(`Missing init stage plan: ${stageName}`);
|
|
2164
|
-
}
|
|
2165
|
-
if (stage.skipped) {
|
|
2166
|
-
return { name: stageName, disposition: "skipped" };
|
|
2167
|
-
}
|
|
2168
|
-
console.log(formatInitStageHeader(t(`cli.install.stages.${stageName}`)));
|
|
2169
|
-
try {
|
|
2170
|
-
switch (stage.name) {
|
|
2171
|
-
case "bootstrap": {
|
|
2172
|
-
const installResults = [];
|
|
2173
|
-
installResults.push(...await runBestEffort("skill-deprecated-cleanup", () => cleanupDeprecatedSkills(plan.target)));
|
|
2174
|
-
installResults.push(...await runBestEffort("skill-install", () => installFabricArchiveSkill(plan.target)));
|
|
2175
|
-
installResults.push(...await runBestEffort("skill-review-install", () => installFabricReviewSkill(plan.target)));
|
|
2176
|
-
installResults.push(...await runBestEffort("skill-import-install", () => installFabricImportSkill(plan.target)));
|
|
2177
|
-
installResults.push(...await runBestEffort("skill-sync-install", () => installFabricSyncSkill(plan.target)));
|
|
2178
|
-
installResults.push(...await runBestEffort("skill-store-install", () => installFabricStoreSkill(plan.target)));
|
|
2179
|
-
installResults.push(...await runBestEffort("skill-audit-install", () => installFabricAuditSkill(plan.target)));
|
|
2180
|
-
installResults.push(...await runBestEffort("skill-connect-install", () => installFabricConnectSkill(plan.target)));
|
|
2181
|
-
installResults.push(...await runBestEffort("skill-shared-lib", () => installSharedSkillLib(plan.target)));
|
|
2182
|
-
installResults.push(...await runBestEffort("hook-script", () => installArchiveHintHook(plan.target)));
|
|
2183
|
-
installResults.push(...await runBestEffort("hook-broad-script", () => installKnowledgeHintBroadHook(plan.target)));
|
|
2184
|
-
installResults.push(...await runBestEffort("hook-narrow-script", () => installKnowledgeHintNarrowHook(plan.target)));
|
|
2185
|
-
installResults.push(...await runBestEffort("hook-cite-policy-evict-script", () => installCitePolicyEvictHook(plan.target)));
|
|
2186
|
-
installResults.push(...await runBestEffort("hook-lib", () => installHookLibs(plan.target)));
|
|
2187
|
-
installResults.push(await runBestEffortSingle("claude-hook-config", () => mergeClaudeCodeHookConfig(plan.target)));
|
|
2188
|
-
installResults.push(await runBestEffortSingle("codex-hook-config", () => mergeCodexHookConfig(plan.target)));
|
|
2189
|
-
installResults.push(await runBestEffortSingle("cursor-hook-config", () => mergeCursorHookConfig(plan.target)));
|
|
2190
|
-
installResults.push(await runBestEffortSingle("bootstrap-snapshot", () => writeFabricAgentsSnapshot(plan.target)));
|
|
2191
|
-
installResults.push(await runBestEffortSingle("bootstrap-claude", () => writeClaudeBootstrapThinShell(plan.target)));
|
|
2192
|
-
installResults.push(await runBestEffortSingle("bootstrap-codex", () => writeCodexBootstrapManagedBlock(plan.target)));
|
|
2193
|
-
installResults.push(await runBestEffortSingle("bootstrap-cursor", () => writeCursorBootstrapManagedBlock(plan.target)));
|
|
2194
|
-
const installedCount = installResults.filter((r) => r.status === "written").length;
|
|
2195
|
-
const skippedCount = installResults.filter((r) => r.status === "skipped").length;
|
|
2196
|
-
const errorCount = installResults.filter((r) => r.status === "error").length;
|
|
2197
|
-
for (const result of installResults) {
|
|
2198
|
-
if (result.status === "error") {
|
|
2199
|
-
writeStderr(`bootstrap ${result.step} ${result.path}: ${result.message ?? "unknown error"}`);
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
const note2 = errorCount > 0 ? `errors=${errorCount}` : void 0;
|
|
2203
|
-
console.log(formatInitStageResult("bootstrap", "completed", installedCount, skippedCount, note2));
|
|
2204
|
-
return { name: "bootstrap", disposition: "ran" };
|
|
2205
|
-
}
|
|
2206
|
-
case "mcp": {
|
|
2207
|
-
if (stage.installMode === "local") {
|
|
2208
|
-
const manager = stage.packageManager ?? detectPackageManager(plan.target);
|
|
2209
|
-
writeStderr(t("cli.install.mcp.install.local"));
|
|
2210
|
-
writeStderr(t("cli.install.mcp.local.installing", { manager }));
|
|
2211
|
-
installLocalFabricServer(plan.target, manager);
|
|
2212
|
-
writeStderr(t("cli.install.mcp.local.installed"));
|
|
2213
|
-
} else {
|
|
2214
|
-
writeStderr(t("cli.install.mcp.install.global"));
|
|
2215
|
-
}
|
|
2216
|
-
const result = await installMcpClients(plan.target, {
|
|
2217
|
-
localServerPath: stage.localServerPath,
|
|
2218
|
-
claudeMcpScope: stage.claudeMcpScope
|
|
2219
|
-
});
|
|
2220
|
-
if (result.details.length === 0) {
|
|
2221
|
-
console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
|
|
2222
|
-
return { name: "mcp", disposition: "skipped" };
|
|
2223
|
-
}
|
|
2224
|
-
console.log(formatInitStageResult("mcp", "completed", result.installed.length, result.skipped.length));
|
|
2225
|
-
return { name: "mcp", disposition: "ran" };
|
|
2226
|
-
}
|
|
2227
|
-
case "hooks": {
|
|
2228
|
-
const result = await installHooks(plan.target);
|
|
2229
|
-
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
2230
|
-
return { name: "hooks", disposition: "ran" };
|
|
2231
|
-
}
|
|
2232
|
-
default:
|
|
2233
|
-
return exhaustiveInitStagePlan(stage);
|
|
2234
|
-
}
|
|
2235
|
-
} catch (error) {
|
|
2236
|
-
writeStderr(formatInitStageFailure(stageName, error));
|
|
2237
|
-
return { name: stageName, disposition: "failed" };
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
function shouldReplaceWritableDirectory(path, _options) {
|
|
2241
|
-
if (!existsSync4(path)) {
|
|
2242
|
-
return false;
|
|
2243
|
-
}
|
|
2244
|
-
if (statSync4(path).isDirectory()) {
|
|
2245
|
-
return false;
|
|
2246
|
-
}
|
|
2247
|
-
return false;
|
|
2248
|
-
}
|
|
2249
|
-
function classifyFreshPath(path, strategy) {
|
|
2250
|
-
if (!existsSync4(path)) {
|
|
2251
|
-
return { path, state: "missing" };
|
|
2252
|
-
}
|
|
2253
|
-
let stat;
|
|
2254
|
-
try {
|
|
2255
|
-
stat = statSync4(path);
|
|
2256
|
-
} catch (error) {
|
|
2257
|
-
return {
|
|
2258
|
-
path,
|
|
2259
|
-
state: "user-modified",
|
|
2260
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
2261
|
-
};
|
|
2262
|
-
}
|
|
2263
|
-
if (!stat.isFile()) {
|
|
2264
|
-
return { path, state: "user-modified", reason: "expected a file" };
|
|
2265
|
-
}
|
|
2266
|
-
if (strategy === "presence" || strategy === "always-rewrite") {
|
|
2267
|
-
return { path, state: "present-canonical" };
|
|
2268
|
-
}
|
|
2269
|
-
try {
|
|
2270
|
-
const raw = readFileSync3(path, "utf8");
|
|
2271
|
-
const parsed = JSON.parse(raw);
|
|
2272
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2273
|
-
return { path, state: "user-modified", reason: "not a JSON object" };
|
|
2274
|
-
}
|
|
2275
|
-
const record = parsed;
|
|
2276
|
-
const hasRevision = typeof record["revision"] === "string";
|
|
2277
|
-
const hasNodes = record["nodes"] !== void 0 && record["nodes"] !== null && typeof record["nodes"] === "object" && !Array.isArray(record["nodes"]);
|
|
2278
|
-
const hasCounters = record["counters"] !== void 0 && record["counters"] !== null && typeof record["counters"] === "object" && !Array.isArray(record["counters"]);
|
|
2279
|
-
if (!hasRevision || !hasNodes || !hasCounters) {
|
|
2280
|
-
return { path, state: "drifted", reason: "missing required AgentsMeta fields" };
|
|
2281
|
-
}
|
|
2282
|
-
return { path, state: "present-canonical" };
|
|
2283
|
-
} catch (error) {
|
|
2284
|
-
return {
|
|
2285
|
-
path,
|
|
2286
|
-
state: "user-modified",
|
|
2287
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
function diffStateToWriteAction(_state) {
|
|
2292
|
-
return "created";
|
|
2293
|
-
}
|
|
2294
|
-
function formatDiffFileState(state) {
|
|
2295
|
-
return t(`cli.install.diff.state.${state}`);
|
|
2296
|
-
}
|
|
2297
|
-
function preparePlannedPath(path, action) {
|
|
2298
|
-
mkdirSync2(dirname(path), { recursive: true });
|
|
2299
|
-
if (action === "overwritten" && existsSync4(path)) {
|
|
2300
|
-
rmSync2(path, { recursive: true, force: true });
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
function createDefaultInitWizardAdapter() {
|
|
2304
|
-
return {
|
|
2305
|
-
async run(context) {
|
|
2306
|
-
intro(t("cli.install.wizard.intro"));
|
|
2307
|
-
note(
|
|
2308
|
-
t("cli.install.wizard.overview.body", {
|
|
2309
|
-
target: context.target,
|
|
2310
|
-
mode: formatInitModeBadge(context.options)
|
|
2311
|
-
}),
|
|
2312
|
-
t("cli.install.wizard.overview.title")
|
|
2313
|
-
);
|
|
2314
|
-
printInitPlanSummary(context.target, context.options, context.mcpInstallMode, context.supports);
|
|
2315
|
-
log.step(t("cli.install.wizard.step.target"));
|
|
2316
|
-
const continueWithTarget = await confirm({
|
|
2317
|
-
message: t("cli.install.wizard.target.confirm", { target: context.target }),
|
|
2318
|
-
initialValue: true
|
|
2319
|
-
});
|
|
2320
|
-
if (isCancel(continueWithTarget) || !continueWithTarget) {
|
|
2321
|
-
emitInitWizardCancellation();
|
|
2322
|
-
return null;
|
|
2323
|
-
}
|
|
2324
|
-
log.step(t("cli.install.wizard.step.plan"));
|
|
2325
|
-
let groupedSelection;
|
|
2326
|
-
try {
|
|
2327
|
-
groupedSelection = await group(
|
|
2328
|
-
{
|
|
2329
|
-
bootstrap: async () => context.lockedStages.includes("bootstrap") ? false : confirmInGroup({
|
|
2330
|
-
message: t("cli.install.wizard.stage.bootstrap", {
|
|
2331
|
-
defaultValue: formatPromptDefault(!context.options.skipBootstrap)
|
|
2332
|
-
}),
|
|
2333
|
-
initialValue: !context.options.skipBootstrap
|
|
2334
|
-
}),
|
|
2335
|
-
mcp: async () => context.lockedStages.includes("mcp") ? false : confirmInGroup({
|
|
2336
|
-
message: t("cli.install.wizard.stage.mcp", {
|
|
2337
|
-
defaultValue: formatPromptDefault(!context.options.skipMcp)
|
|
2338
|
-
}),
|
|
2339
|
-
initialValue: !context.options.skipMcp
|
|
2340
|
-
}),
|
|
2341
|
-
mcpInstallMode: async ({ results }) => results.mcp ? selectMcpInstallModeInGroup({
|
|
2342
|
-
message: t("cli.install.wizard.mcp-install", { defaultValue: context.mcpInstallMode }),
|
|
2343
|
-
initialValue: context.mcpInstallMode,
|
|
2344
|
-
options: [
|
|
2345
|
-
{ value: "global", label: "global", hint: t("cli.install.mcp.install.global") },
|
|
2346
|
-
{ value: "local", label: "local", hint: t("cli.install.mcp.install.local") }
|
|
2347
|
-
]
|
|
2348
|
-
}) : context.mcpInstallMode,
|
|
2349
|
-
claudeMcpScope: async ({ results }) => results.mcp ? selectClaudeMcpScopeInGroup({
|
|
2350
|
-
message: t("cli.install.wizard.mcp-scope", { defaultValue: context.claudeMcpScope }),
|
|
2351
|
-
initialValue: context.claudeMcpScope,
|
|
2352
|
-
options: [
|
|
2353
|
-
{ value: "project", label: "project", hint: t("cli.install.mcp.scope.project") },
|
|
2354
|
-
{ value: "user", label: "user", hint: t("cli.install.mcp.scope.user") }
|
|
2355
|
-
]
|
|
2356
|
-
}) : context.claudeMcpScope,
|
|
2357
|
-
hooks: async () => context.lockedStages.includes("hooks") ? false : confirmInGroup({
|
|
2358
|
-
message: t("cli.install.wizard.stage.hooks", {
|
|
2359
|
-
defaultValue: formatPromptDefault(!context.options.skipHooks)
|
|
2360
|
-
}),
|
|
2361
|
-
initialValue: !context.options.skipHooks
|
|
2362
|
-
})
|
|
2363
|
-
},
|
|
2364
|
-
{
|
|
2365
|
-
onCancel() {
|
|
2366
|
-
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
);
|
|
2370
|
-
} catch (error) {
|
|
2371
|
-
if (error === INIT_WIZARD_GROUP_CANCELLED) {
|
|
2372
|
-
emitInitWizardCancellation();
|
|
2373
|
-
return null;
|
|
2374
|
-
}
|
|
2375
|
-
throw error;
|
|
2376
|
-
}
|
|
2377
|
-
if (groupedSelection === null) {
|
|
2378
|
-
emitInitWizardCancellation();
|
|
2379
|
-
return null;
|
|
2380
|
-
}
|
|
2381
|
-
const previewOptions = {
|
|
2382
|
-
...context.options,
|
|
2383
|
-
skipBootstrap: !groupedSelection.bootstrap,
|
|
2384
|
-
skipMcp: !groupedSelection.mcp,
|
|
2385
|
-
skipHooks: !groupedSelection.hooks
|
|
2386
|
-
};
|
|
2387
|
-
log.step(t("cli.install.wizard.step.review"));
|
|
2388
|
-
printInitPlanSummary(context.target, previewOptions, groupedSelection.mcpInstallMode, context.supports);
|
|
2389
|
-
const confirmed = await confirm({
|
|
2390
|
-
message: t("cli.install.wizard.execute.confirm"),
|
|
2391
|
-
initialValue: true
|
|
2392
|
-
});
|
|
2393
|
-
if (isCancel(confirmed) || !confirmed) {
|
|
2394
|
-
emitInitWizardCancellation();
|
|
2395
|
-
return null;
|
|
2396
|
-
}
|
|
2397
|
-
outro(t("cli.install.wizard.outro"));
|
|
2398
|
-
return groupedSelection;
|
|
2399
|
-
}
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
function emitInitWizardCancellation() {
|
|
2403
|
-
cancel(t("cli.install.wizard.cancelled"));
|
|
2404
|
-
}
|
|
2405
|
-
async function confirmInGroup(options) {
|
|
2406
|
-
const result = await confirm(options);
|
|
2407
|
-
if (isCancel(result)) {
|
|
2408
|
-
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
2409
|
-
}
|
|
2410
|
-
return result;
|
|
2411
|
-
}
|
|
2412
|
-
async function selectMcpInstallModeInGroup(options) {
|
|
2413
|
-
const result = await select({
|
|
2414
|
-
message: options.message,
|
|
2415
|
-
initialValue: options.initialValue,
|
|
2416
|
-
options: options.options
|
|
2417
|
-
});
|
|
2418
|
-
if (isCancel(result)) {
|
|
2419
|
-
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
2420
|
-
}
|
|
2421
|
-
return result;
|
|
2422
|
-
}
|
|
2423
|
-
async function selectClaudeMcpScopeInGroup(options) {
|
|
2424
|
-
const result = await select({
|
|
2425
|
-
message: options.message,
|
|
2426
|
-
initialValue: options.initialValue,
|
|
2427
|
-
options: options.options
|
|
2428
|
-
});
|
|
2429
|
-
if (isCancel(result)) {
|
|
2430
|
-
throw INIT_WIZARD_GROUP_CANCELLED;
|
|
2431
|
-
}
|
|
2432
|
-
return result;
|
|
2433
|
-
}
|
|
2434
|
-
function formatPromptDefault(value) {
|
|
2435
|
-
return value ? "Y/n" : "y/N";
|
|
2436
|
-
}
|
|
2437
|
-
function formatInitModeBanner(options) {
|
|
2438
|
-
if (options.planOnly) {
|
|
2439
|
-
return t("cli.install.plan.mode-banner.plan");
|
|
2440
|
-
}
|
|
2441
|
-
return t("cli.install.plan.mode-banner.default");
|
|
2442
|
-
}
|
|
2443
|
-
function formatInitModeBadge(options) {
|
|
2444
|
-
if (options.planOnly) {
|
|
2445
|
-
return t("cli.install.mode.badge.plan");
|
|
2446
|
-
}
|
|
2447
|
-
return t("cli.install.mode.badge.default");
|
|
2448
|
-
}
|
|
2449
|
-
function normalizeTarget3(targetInput) {
|
|
2450
|
-
return isAbsolute3(targetInput) ? targetInput : resolve3(process.cwd(), targetInput);
|
|
2451
|
-
}
|
|
2452
|
-
function assertExistingDirectory3(target) {
|
|
2453
|
-
if (!existsSync4(target) || !statSync4(target).isDirectory()) {
|
|
2454
|
-
throw new Error(`Target must be an existing directory: ${target}`);
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
function detectPackageManager(cwd) {
|
|
2458
|
-
const workspaceRoot = resolve3(cwd);
|
|
2459
|
-
if (existsSync4(join6(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
2460
|
-
return "pnpm";
|
|
2461
|
-
}
|
|
2462
|
-
if (existsSync4(join6(workspaceRoot, "yarn.lock"))) {
|
|
2463
|
-
return "yarn";
|
|
2464
|
-
}
|
|
2465
|
-
if (existsSync4(join6(workspaceRoot, "package-lock.json"))) {
|
|
2466
|
-
return "npm";
|
|
2467
|
-
}
|
|
2468
|
-
return "npm";
|
|
2469
|
-
}
|
|
2470
|
-
function installLocalFabricServer(target, manager) {
|
|
2471
|
-
const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
|
|
2472
|
-
childProcess.execFileSync(manager, installArgs, {
|
|
2473
|
-
cwd: target,
|
|
2474
|
-
stdio: "inherit",
|
|
2475
|
-
shell: process.platform === "win32"
|
|
2476
|
-
});
|
|
2477
|
-
}
|
|
2478
|
-
function createInitialMeta() {
|
|
2479
|
-
return {
|
|
2480
|
-
revision: "sha256:initial",
|
|
2481
|
-
nodes: {},
|
|
2482
|
-
counters: defaultAgentsMetaCounters()
|
|
2483
|
-
};
|
|
2484
|
-
}
|
|
2485
|
-
function appendInstallDiffLedgerEvent(eventsPath, payload) {
|
|
2486
|
-
const event = {
|
|
2487
|
-
kind: "fabric-event",
|
|
2488
|
-
id: `event:${randomUUID2()}`,
|
|
2489
|
-
ts: Date.now(),
|
|
2490
|
-
schema_version: 1,
|
|
2491
|
-
event_type: "install_diff_applied",
|
|
2492
|
-
applied: payload.applied,
|
|
2493
|
-
canonical: payload.canonical,
|
|
2494
|
-
drifted: payload.drifted
|
|
2495
|
-
};
|
|
2496
|
-
const line = `${JSON.stringify(event)}
|
|
2497
|
-
`;
|
|
2498
|
-
appendFileSync(eventsPath, line, "utf8");
|
|
2499
|
-
}
|
|
2500
|
-
async function runBestEffort(step, fn) {
|
|
2501
|
-
try {
|
|
2502
|
-
return await fn();
|
|
2503
|
-
} catch (error) {
|
|
2504
|
-
return [
|
|
2505
|
-
{
|
|
2506
|
-
step,
|
|
2507
|
-
path: "",
|
|
2508
|
-
status: "error",
|
|
2509
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2510
|
-
}
|
|
2511
|
-
];
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
async function runBestEffortSingle(step, fn) {
|
|
2515
|
-
try {
|
|
2516
|
-
return await fn();
|
|
2517
|
-
} catch (error) {
|
|
2518
|
-
return {
|
|
2519
|
-
step,
|
|
2520
|
-
path: "",
|
|
2521
|
-
status: "error",
|
|
2522
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2523
|
-
};
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
function formatInitStageHeader(message) {
|
|
2527
|
-
return `${nextLabel()} ${paint.muted(message)}`;
|
|
2528
|
-
}
|
|
2529
|
-
function formatInitStageResult(stage, status, installedCount, skippedCount, note2) {
|
|
2530
|
-
const label = status === "completed" ? completedStageLabel() : skippedStageLabel();
|
|
2531
|
-
const counts = `installed=${installedCount} skipped=${skippedCount}`;
|
|
2532
|
-
const suffix = note2 ? ` ${paint.muted(`(${note2})`)}` : "";
|
|
2533
|
-
return `${label} ${stage}: ${counts}${suffix}`;
|
|
2534
|
-
}
|
|
2535
|
-
function formatInitStageFailure(stage, error) {
|
|
2536
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2537
|
-
return `${failedStageLabel()} ${stage}: ${message}`;
|
|
2538
|
-
}
|
|
2539
|
-
function printInitStageSummary(stageResults) {
|
|
2540
|
-
console.log(formatInitStageSummaryLine("ran", collectInitStageNames(stageResults, "ran")));
|
|
2541
|
-
console.log(formatInitStageSummaryLine("skipped", collectInitStageNames(stageResults, "skipped")));
|
|
2542
|
-
console.log(formatInitStageSummaryLine("failed", collectInitStageNames(stageResults, "failed")));
|
|
2543
|
-
}
|
|
2544
|
-
function formatInitStageSummaryLine(disposition, stages) {
|
|
2545
|
-
const label = disposition === "ran" ? paint.success(t("cli.install.stages.summary.ran")) : disposition === "skipped" ? paint.muted(t("cli.install.stages.summary.skipped")) : paint.error(t("cli.install.stages.summary.failed"));
|
|
2546
|
-
return `${label}: ${stages.length > 0 ? stages.join(", ") : t("cli.shared.none")}`;
|
|
2547
|
-
}
|
|
2548
|
-
function collectInitStageNames(stageResults, disposition) {
|
|
2549
|
-
return stageResults.filter((stage) => stage.disposition === disposition).map((stage) => stage.name);
|
|
2550
|
-
}
|
|
2551
|
-
function shouldPrintHooksNextStep(options, stageResults) {
|
|
2552
|
-
return Boolean(options.skipHooks) || stageResults.some((stage) => stage.name === "hooks" && stage.disposition === "failed");
|
|
2553
|
-
}
|
|
2554
|
-
function isInteractiveInit() {
|
|
2555
|
-
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
2556
|
-
}
|
|
2557
|
-
function printInitPlanSummary(target, options, mcpInstallMode, supports) {
|
|
2558
|
-
console.log(t("cli.install.plan.title"));
|
|
2559
|
-
console.log(formatInitModeBanner(options));
|
|
2560
|
-
console.log(t("cli.install.plan.target", { target }));
|
|
2561
|
-
console.log(
|
|
2562
|
-
t("cli.install.plan.actions", {
|
|
2563
|
-
bootstrap: yesNoLabel(!options.skipBootstrap),
|
|
2564
|
-
mcp: yesNoLabel(!options.skipMcp),
|
|
2565
|
-
hooks: yesNoLabel(!options.skipHooks),
|
|
2566
|
-
mcpInstall: mcpInstallMode
|
|
2567
|
-
})
|
|
2568
|
-
);
|
|
2569
|
-
const detected = supports.filter((support) => support.detected);
|
|
2570
|
-
console.log(
|
|
2571
|
-
t("cli.install.plan.detected", {
|
|
2572
|
-
clients: detected.length > 0 ? detected.map((support) => support.label).join(", ") : t("cli.shared.none")
|
|
2573
|
-
})
|
|
2574
|
-
);
|
|
2575
|
-
console.log(t("cli.install.plan.writes"));
|
|
2576
|
-
console.log(` - ${target}/.fabric/knowledge/{decisions,pitfalls,guidelines,models,processes,pending}/`);
|
|
2577
|
-
console.log(` - ${target}/.fabric/agents.meta.json`);
|
|
2578
|
-
console.log(` - ${target}/.fabric/events.jsonl`);
|
|
2579
|
-
console.log(` - ${target}/.fabric/forensic.json`);
|
|
2580
|
-
console.log(` - ${target}/.fabric/fabric-config.json`);
|
|
2581
|
-
}
|
|
2582
|
-
function printInitCapabilitySummary(supports, stageResults, options) {
|
|
2583
|
-
const detected = supports.filter((support) => support.detected);
|
|
2584
|
-
if (detected.length === 0) {
|
|
2585
|
-
console.log(t("cli.install.capabilities.none"));
|
|
2586
|
-
return;
|
|
2587
|
-
}
|
|
2588
|
-
console.log(t("cli.install.capabilities.title"));
|
|
2589
|
-
const rows = detected.map((support) => toCapabilityRow(support, stageResults, options));
|
|
2590
|
-
const headers = {
|
|
2591
|
-
client: t("cli.install.capabilities.header.client"),
|
|
2592
|
-
bootstrap: t("cli.install.capabilities.header.bootstrap"),
|
|
2593
|
-
mcp: t("cli.install.capabilities.header.mcp"),
|
|
2594
|
-
hook: t("cli.install.capabilities.header.hook"),
|
|
2595
|
-
skill: t("cli.install.capabilities.header.skill"),
|
|
2596
|
-
followUp: t("cli.install.capabilities.header.follow-up")
|
|
2597
|
-
};
|
|
2598
|
-
const widths = {
|
|
2599
|
-
client: Math.max(displayWidth(headers.client), ...rows.map((row) => displayWidth(row.client))),
|
|
2600
|
-
bootstrap: Math.max(displayWidth(headers.bootstrap), ...rows.map((row) => displayWidth(row.bootstrap))),
|
|
2601
|
-
mcp: Math.max(displayWidth(headers.mcp), ...rows.map((row) => displayWidth(row.mcp))),
|
|
2602
|
-
hook: Math.max(displayWidth(headers.hook), ...rows.map((row) => displayWidth(row.hook))),
|
|
2603
|
-
skill: Math.max(displayWidth(headers.skill), ...rows.map((row) => displayWidth(row.skill))),
|
|
2604
|
-
followUp: Math.max(displayWidth(headers.followUp), ...rows.map((row) => displayWidth(row.followUp)))
|
|
2605
|
-
};
|
|
2606
|
-
console.log(formatCapabilityTableRow(headers, widths));
|
|
2607
|
-
console.log(formatCapabilityDivider(widths));
|
|
2608
|
-
for (const row of rows) {
|
|
2609
|
-
console.log(formatCapabilityTableRow(row, widths));
|
|
2610
|
-
}
|
|
2611
|
-
console.log("");
|
|
2612
|
-
console.log(t("cli.install.restart-banner"));
|
|
2613
|
-
}
|
|
2614
|
-
function toCapabilityRow(support, stageResults, options) {
|
|
2615
|
-
const stage = (name) => stageResults.find((entry) => entry.name === name)?.disposition ?? null;
|
|
2616
|
-
const bootstrap = support.capabilities.bootstrap ? capabilityStatus(options.skipBootstrap ? "skipped" : stage("bootstrap")) : t("cli.install.capabilities.status.na");
|
|
2617
|
-
const mcp = support.capabilities.mcp ? capabilityStatus(options.skipMcp ? "skipped" : stage("mcp")) : t("cli.install.capabilities.status.na");
|
|
2618
|
-
const hook = capabilityInstallStatus(support, "hook");
|
|
2619
|
-
const skill = capabilityInstallStatus(support, "skill");
|
|
2620
|
-
return {
|
|
2621
|
-
client: support.label,
|
|
2622
|
-
bootstrap,
|
|
2623
|
-
mcp,
|
|
2624
|
-
hook,
|
|
2625
|
-
skill,
|
|
2626
|
-
followUp: hasInstalledCapability(support, "skill") ? t("cli.install.capabilities.follow-up.ready") : support.capabilities.skill ? t("cli.install.capabilities.follow-up.install") : t("cli.install.capabilities.follow-up.manual")
|
|
2627
|
-
};
|
|
2628
|
-
}
|
|
2629
|
-
function capabilityInstallStatus(support, capability) {
|
|
2630
|
-
if (!support.capabilities[capability]) {
|
|
2631
|
-
return t("cli.install.capabilities.status.na");
|
|
2632
|
-
}
|
|
2633
|
-
return hasInstalledCapability(support, capability) ? t("cli.install.capabilities.status.installed") : t("cli.install.capabilities.status.supported");
|
|
2634
|
-
}
|
|
2635
|
-
function hasInstalledCapability(support, capability) {
|
|
2636
|
-
return support.installedCapabilities?.[capability] === true;
|
|
2637
|
-
}
|
|
2638
|
-
function capabilityStatus(disposition) {
|
|
2639
|
-
switch (disposition) {
|
|
2640
|
-
case "ran":
|
|
2641
|
-
return t("cli.install.capabilities.status.ready");
|
|
2642
|
-
case "skipped":
|
|
2643
|
-
return t("cli.install.capabilities.status.skipped");
|
|
2644
|
-
case "failed":
|
|
2645
|
-
return t("cli.install.capabilities.status.failed");
|
|
2646
|
-
case null:
|
|
2647
|
-
return t("cli.install.capabilities.status.na");
|
|
2648
|
-
default:
|
|
2649
|
-
return t("cli.install.capabilities.status.ready");
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
function formatCapabilityTableRow(row, widths) {
|
|
2653
|
-
return [
|
|
2654
|
-
padEnd(row.client, widths.client),
|
|
2655
|
-
padEnd(row.bootstrap, widths.bootstrap),
|
|
2656
|
-
padEnd(row.mcp, widths.mcp),
|
|
2657
|
-
padEnd(row.hook, widths.hook),
|
|
2658
|
-
padEnd(row.skill, widths.skill),
|
|
2659
|
-
padEnd(row.followUp, widths.followUp)
|
|
2660
|
-
].join(" ");
|
|
2661
|
-
}
|
|
2662
|
-
function formatCapabilityDivider(widths) {
|
|
2663
|
-
return [
|
|
2664
|
-
"".padEnd(widths.client, "-"),
|
|
2665
|
-
"".padEnd(widths.bootstrap, "-"),
|
|
2666
|
-
"".padEnd(widths.mcp, "-"),
|
|
2667
|
-
"".padEnd(widths.hook, "-"),
|
|
2668
|
-
"".padEnd(widths.skill, "-"),
|
|
2669
|
-
"".padEnd(widths.followUp, "-")
|
|
2670
|
-
].join(" ");
|
|
2671
|
-
}
|
|
2672
|
-
function formatInitReasonMessage(supports) {
|
|
2673
|
-
const detected = supports.filter((support) => support.detected);
|
|
2674
|
-
if (detected.some((support) => support.capabilities.skill)) {
|
|
2675
|
-
return t("cli.install.reason-message.installable-body");
|
|
2676
|
-
}
|
|
2677
|
-
return t("cli.install.reason-message.manual-body");
|
|
2678
|
-
}
|
|
2679
|
-
function yesNoLabel(value) {
|
|
2680
|
-
return value ? t("cli.shared.yes") : t("cli.shared.no");
|
|
2681
|
-
}
|
|
2682
|
-
function formatInitPathAction(path, action) {
|
|
2683
|
-
return t("cli.install.created-path", { label: labelForInitWriteAction(action), path });
|
|
2684
|
-
}
|
|
2685
|
-
function formatAgentsMdAction(path, action) {
|
|
2686
|
-
if (action === "preserved") {
|
|
2687
|
-
return t("cli.install.skipped-existing-path", { label: skippedLabel(), path });
|
|
2688
|
-
}
|
|
2689
|
-
return t("cli.install.created-path", { label: createdLabel(), path });
|
|
2690
|
-
}
|
|
2691
|
-
function labelForInitWriteAction(action) {
|
|
2692
|
-
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
2693
|
-
}
|
|
2694
|
-
function createdLabel() {
|
|
2695
|
-
return paint.success(t("cli.shared.created"));
|
|
2696
|
-
}
|
|
2697
|
-
function skippedLabel() {
|
|
2698
|
-
return paint.muted(t("cli.shared.skipped"));
|
|
2699
|
-
}
|
|
2700
|
-
function nextLabel() {
|
|
2701
|
-
return paint.ai(t("cli.shared.next"));
|
|
2702
|
-
}
|
|
2703
|
-
function reasonLabel() {
|
|
2704
|
-
return paint.human(t("cli.shared.reason"));
|
|
2705
|
-
}
|
|
2706
|
-
function overwrittenLabel() {
|
|
2707
|
-
return paint.warn(t("cli.install.label.overwritten"));
|
|
2708
|
-
}
|
|
2709
|
-
function completedStageLabel() {
|
|
2710
|
-
return paint.success(t("cli.install.stages.completed"));
|
|
2711
|
-
}
|
|
2712
|
-
function skippedStageLabel() {
|
|
2713
|
-
return paint.muted(t("cli.install.stages.skipped"));
|
|
2714
|
-
}
|
|
2715
|
-
function failedStageLabel() {
|
|
2716
|
-
return paint.error(t("cli.install.stages.failed"));
|
|
2717
|
-
}
|
|
2718
|
-
function writeStderr(message) {
|
|
2719
|
-
process.stderr.write(`${message}
|
|
2720
|
-
`);
|
|
2721
|
-
}
|
|
2722
|
-
export {
|
|
2723
|
-
buildInitExecutionPlan,
|
|
2724
|
-
buildInitFabricPlan,
|
|
2725
|
-
createDefaultInitWizardAdapter,
|
|
2726
|
-
install_default as default,
|
|
2727
|
-
detectPackageManager,
|
|
2728
|
-
executeInitExecutionPlan,
|
|
2729
|
-
executeInitFabricPlan,
|
|
2730
|
-
initFabric,
|
|
2731
|
-
installCommand,
|
|
2732
|
-
resolveInitExecutionPlanWithWizard,
|
|
2733
|
-
runHooksOnlyRefresh,
|
|
2734
|
-
runInitCommand,
|
|
2735
|
-
runSkillsOnlyRefresh,
|
|
2736
|
-
shouldUseInitWizard
|
|
2737
|
-
};
|