@fenglimg/fabric-cli 2.1.0-rc.2 → 2.2.0-rc.10

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