@fenglimg/fabric-cli 2.2.0-rc.9 → 2.3.0-rc.1

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 (76) hide show
  1. package/README.md +2 -2
  2. package/dist/audit-PURSJJFH.js +734 -0
  3. package/dist/{chunk-YM4XATJF.js → chunk-722JU5BP.js} +2 -0
  4. package/dist/{chunk-QPAW6IYT.js → chunk-7V4XMLQ2.js} +3 -3
  5. package/dist/{chunk-7ZDXBOOU.js → chunk-ACSMNX3V.js} +44 -128
  6. package/dist/{chunk-PTGQAZEW.js → chunk-GGDVZCD6.js} +2 -4
  7. package/dist/{chunk-EOT63RDH.js → chunk-I5F5BHWI.js} +9 -0
  8. package/dist/chunk-PP7QVRXH.js +565 -0
  9. package/dist/chunk-SL77FXX7.js +54 -0
  10. package/dist/{chunk-3D7B2UAZ.js → chunk-VQKXTMWH.js} +44 -4
  11. package/dist/doctor-S6KPGS35.js +27 -0
  12. package/dist/index.js +91 -81
  13. package/dist/{info-7FKBTMVO.js → info-NJEY26H6.js} +91 -46
  14. package/dist/{context-7NUKXDB6.js → inspect-5YZMJPFM.js} +11 -11
  15. package/dist/{install-v2-I6PJ6IFT.js → install-v2-KGIDII4H.js} +163 -364
  16. package/dist/{plan-context-hint-G75R4P4J.js → plan-context-hint-5TNGH3R4.js} +1 -1
  17. package/dist/{store-HOCORVL3.js → store-GF4SFBMJ.js} +155 -57
  18. package/dist/{sync-DT5UJMMR.js → sync-3XCIRDPK.js} +3 -4
  19. package/dist/{uninstall-IFN2KYBK.js → uninstall-BG4ML4FC.js} +39 -10
  20. package/package.json +3 -7
  21. package/templates/hooks/cite-policy-evict.cjs +1 -1
  22. package/templates/hooks/configs/claude-code.json +1 -5
  23. package/templates/hooks/configs/codex-hooks.json +1 -5
  24. package/templates/hooks/fabric-hint.cjs +346 -138
  25. package/templates/hooks/knowledge-hint-broad.cjs +265 -75
  26. package/templates/hooks/knowledge-hint-narrow.cjs +3 -3
  27. package/templates/hooks/knowledge-pretooluse.cjs +111 -0
  28. package/templates/hooks/lib/banner-i18n.cjs +31 -12
  29. package/templates/hooks/lib/bindings-snapshot-reader.cjs +1 -1
  30. package/templates/hooks/lib/event-writer.cjs +79 -0
  31. package/templates/hooks/lib/nudge-policy.cjs +11 -0
  32. package/templates/hooks/lib/theme.cjs +62 -0
  33. package/templates/hooks/post-tooluse-mutation.cjs +28 -39
  34. package/templates/skills/fabric-archive/SKILL.md +43 -12
  35. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  36. package/templates/skills/fabric-archive/ref/i18n-policy.md +1 -1
  37. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +5 -5
  38. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +2 -2
  39. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  40. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +1 -1
  41. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +1 -1
  42. package/templates/skills/fabric-archive/ref/phase-3-classify.md +1 -1
  43. package/templates/skills/fabric-archive/ref/phase-4-5-emit.md +1 -1
  44. package/templates/skills/fabric-archive/ref/phase-4-mcp-persist.md +6 -5
  45. package/templates/skills/{fabric-import/ref/checkpoint-state.md → fabric-archive/ref/source-checkpoint.md} +3 -3
  46. package/templates/skills/{fabric-import/ref/phase-3-dedup.md → fabric-archive/ref/source-dedup.md} +4 -4
  47. package/templates/skills/{fabric-import/ref/phase-2-mining.md → fabric-archive/ref/source-mining.md} +20 -20
  48. package/templates/skills/{fabric-import/ref/output-contract.md → fabric-archive/ref/source-output-contract.md} +3 -3
  49. package/templates/skills/{fabric-import/ref/state-recovery.md → fabric-archive/ref/source-state-recovery.md} +2 -2
  50. package/templates/skills/{fabric-import/ref/worked-examples.md → fabric-archive/ref/source-worked-examples.md} +10 -10
  51. package/templates/skills/fabric-archive/ref/worked-examples.md +3 -3
  52. package/templates/skills/fabric-review/SKILL.md +28 -15
  53. package/templates/skills/fabric-review/ref/cite-contract.md +2 -2
  54. package/templates/skills/fabric-review/ref/modify-flow.md +13 -1
  55. package/templates/skills/fabric-review/ref/per-mode-flows.md +5 -5
  56. package/templates/skills/fabric-review/ref/relate-mode.md +33 -0
  57. package/templates/skills/fabric-review/ref/retire-mode.md +47 -0
  58. package/templates/skills/fabric-review/ref/semantic-check.md +1 -1
  59. package/templates/skills/fabric-review/ref/worked-examples.md +5 -5
  60. package/templates/skills/fabric-store/SKILL.md +12 -27
  61. package/templates/skills/fabric-sync/SKILL.md +16 -35
  62. package/templates/skills/lib/shared-policy.md +6 -4
  63. package/dist/chunk-27HK6H5Y.js +0 -69
  64. package/dist/chunk-E7HJUU34.js +0 -1096
  65. package/dist/chunk-NLNH64A3.js +0 -43
  66. package/dist/chunk-QFIVFZRH.js +0 -13
  67. package/dist/doctor-MDTZWKBK.js +0 -24
  68. package/dist/metrics-HMFH4YHK.js +0 -135
  69. package/dist/scope-explain-HLJZ2M33.js +0 -48
  70. package/dist/status-4R3TM4FJ.js +0 -37
  71. package/dist/whoami-ITGEFWH4.js +0 -49
  72. package/templates/skills/fabric/SKILL.md +0 -100
  73. package/templates/skills/fabric-audit/SKILL.md +0 -63
  74. package/templates/skills/fabric-connect/SKILL.md +0 -48
  75. package/templates/skills/fabric-import/SKILL.md +0 -151
  76. package/templates/skills/fabric-import/ref/i18n-policy.md +0 -78
@@ -0,0 +1,565 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureStoreProjectBinding,
4
+ migrateRootConfig,
5
+ tree
6
+ } from "./chunk-VQKXTMWH.js";
7
+ import {
8
+ resolveDevMode
9
+ } from "./chunk-WA3DYGSY.js";
10
+ import {
11
+ paint,
12
+ symbol
13
+ } from "./chunk-SL77FXX7.js";
14
+ import {
15
+ detectAliasLinkDrift,
16
+ missingRequiredStores,
17
+ syncStoreAliasLinks,
18
+ unboundAvailableStores
19
+ } from "./chunk-7V4XMLQ2.js";
20
+ import {
21
+ loadProjectConfig
22
+ } from "./chunk-I5F5BHWI.js";
23
+ import {
24
+ loadGlobalConfig,
25
+ resolveGlobalRoot
26
+ } from "./chunk-FNHDQTPC.js";
27
+ import {
28
+ getDoctorTranslator,
29
+ t
30
+ } from "./chunk-HORSMSZL.js";
31
+
32
+ // src/commands/doctor.ts
33
+ import { confirm, isCancel } from "@clack/prompts";
34
+ import { defineCommand } from "citty";
35
+ import {
36
+ appendEventLedgerEvent,
37
+ runDoctorApplyLint as runDoctorFixKnowledge,
38
+ runDoctorFix,
39
+ runDoctorReport
40
+ } from "@fenglimg/fabric-server";
41
+
42
+ // src/install/backfill-unbound-project.ts
43
+ import { detectUnboundProject } from "@fenglimg/fabric-server";
44
+ async function backfillUnboundProject(projectRoot, globalRoot = resolveGlobalRoot()) {
45
+ const violation = detectUnboundProject(projectRoot);
46
+ if (violation === null) {
47
+ return null;
48
+ }
49
+ const result = await ensureStoreProjectBinding(projectRoot, violation.alias, { globalRoot });
50
+ return {
51
+ alias: violation.alias,
52
+ project_id: result.project_id,
53
+ active_project: result.active_project
54
+ };
55
+ }
56
+
57
+ // src/commands/doctor.ts
58
+ import { sectionBar } from "@fenglimg/fabric-shared/theme";
59
+
60
+ // src/store/doctor-checks.ts
61
+ import { join } from "path";
62
+ import { findStoreExecutableViolations, storeRelativePathForMount } from "@fenglimg/fabric-shared";
63
+ function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
64
+ const diagnostics = [];
65
+ const global = loadGlobalConfig(globalRoot);
66
+ if (global === null) {
67
+ diagnostics.push({
68
+ code: "no_global_config",
69
+ severity: "warn",
70
+ message: "no global Fabric config \u2014 run `fabric install --global <url>`"
71
+ });
72
+ return diagnostics;
73
+ }
74
+ for (const missing of missingRequiredStores(projectRoot, globalRoot)) {
75
+ diagnostics.push({
76
+ code: "missing_required_store",
77
+ severity: "warn",
78
+ ref: missing.id,
79
+ message: `required store '${missing.id}' is not mounted; run \`fabric store mount\``
80
+ });
81
+ }
82
+ for (const store of unboundAvailableStores(projectRoot, globalRoot)) {
83
+ diagnostics.push({
84
+ code: "unbound_available_store",
85
+ severity: "info",
86
+ ref: store.alias,
87
+ message: `store '${store.alias}' is mounted but not bound to this project; run \`fabric store bind ${store.alias}\` to read its knowledge here (then \`fabric store switch-write ${store.alias}\` to write team knowledge into it)`
88
+ });
89
+ }
90
+ const aliasDrift = detectAliasLinkDrift(globalRoot);
91
+ if (aliasDrift.length > 0) {
92
+ diagnostics.push({
93
+ code: "store_alias_link_drift",
94
+ severity: "info",
95
+ ref: aliasDrift.join(", "),
96
+ message: `by-alias readability link(s) out of sync for ${aliasDrift.join(", ")}; run \`fabric doctor --fix\` to repair ~/.fabric/stores/by-alias/`
97
+ });
98
+ }
99
+ for (const store of global.stores) {
100
+ if (store.remote === void 0 && store.personal !== true) {
101
+ diagnostics.push({
102
+ code: "local_only_store",
103
+ severity: "info",
104
+ ref: store.alias,
105
+ message: `store '${store.alias}' is local-only; add a git remote to back it up`
106
+ });
107
+ }
108
+ const violations = findStoreExecutableViolations(join(globalRoot, storeRelativePathForMount(store)));
109
+ if (violations.length > 0) {
110
+ diagnostics.push({
111
+ code: "executable_in_store",
112
+ severity: "warn",
113
+ ref: store.alias,
114
+ message: `store '${store.alias}' contains executable/script files (${violations.slice(0, 3).join(", ")}${violations.length > 3 ? ", \u2026" : ""}) \u2014 stores are data-only; Fabric never runs them (S65)`
115
+ });
116
+ }
117
+ }
118
+ return diagnostics;
119
+ }
120
+
121
+ // src/commands/doctor.ts
122
+ import { buildDebugBundle } from "@fenglimg/fabric-shared";
123
+ var FIX_KNOWLEDGE_CODE_LABELS = {
124
+ knowledge_pending_auto_archive: "archive (git mv, pending)",
125
+ knowledge_index_drift: "counter bump (agents.meta)",
126
+ knowledge_session_hints_stale: "cache delete"
127
+ };
128
+ var PLAN_PREVIEW_LIMIT = 12;
129
+ var doctorCommand = defineCommand({
130
+ meta: {
131
+ name: "doctor",
132
+ description: t("cli.doctor.description")
133
+ },
134
+ args: {
135
+ target: {
136
+ type: "string",
137
+ description: t("cli.doctor.args.target.description")
138
+ },
139
+ fix: {
140
+ type: "boolean",
141
+ description: t("cli.doctor.args.fix.description"),
142
+ default: false
143
+ },
144
+ json: {
145
+ type: "boolean",
146
+ description: t("cli.doctor.args.json.description"),
147
+ default: false
148
+ },
149
+ // v2.1.0-rc.1 P6 (S40): emit a redacted diagnostic bundle (config + store
150
+ // diagnostics; events excluded by default). Every string is secret-redacted
151
+ // so the bundle is safe to paste into a bug report. Read-only.
152
+ // EPIC-009: hidden flag (internal debug tool).
153
+ "debug-bundle": {
154
+ type: "boolean",
155
+ description: "Emit a redacted diagnostic bundle (config + store health) for bug reports",
156
+ default: false
157
+ },
158
+ // EPIC-009: hidden flag (CI automation).
159
+ yes: {
160
+ type: "boolean",
161
+ description: t("cli.doctor.args.yes.description"),
162
+ default: false
163
+ },
164
+ // EPIC-009: hidden flag (advanced output).
165
+ verbose: {
166
+ type: "boolean",
167
+ description: t("cli.doctor.args.verbose.description"),
168
+ default: false
169
+ },
170
+ // EPIC-009: hidden flag (preview --fix without writing).
171
+ "dry-run": {
172
+ type: "boolean",
173
+ description: t("cli.doctor.args.dry-run.description"),
174
+ default: false
175
+ },
176
+ // EPIC-009: hidden flag (strict mode for CI).
177
+ strict: {
178
+ type: "boolean",
179
+ description: t("cli.doctor.args.strict.description"),
180
+ default: false
181
+ }
182
+ },
183
+ async run({ args }) {
184
+ const workspaceRoot = process.cwd();
185
+ const resolution = resolveDevMode(args.target, workspaceRoot);
186
+ const dt = getDoctorTranslator(resolution.target);
187
+ const fix = args.fix === true;
188
+ if (args["debug-bundle"] === true) {
189
+ const globalRoot = resolveGlobalRoot();
190
+ let config = {};
191
+ try {
192
+ config = {
193
+ global: loadGlobalConfig(globalRoot) ?? null,
194
+ project: loadProjectConfig(resolution.target) ?? null
195
+ };
196
+ } catch {
197
+ config = {};
198
+ }
199
+ const bundle = buildDebugBundle({
200
+ config,
201
+ diagnostics: collectStoreDiagnostics(resolution.target)
202
+ });
203
+ writeStdout(JSON.stringify(bundle, null, 2));
204
+ return;
205
+ }
206
+ let fixKnowledgeReport = null;
207
+ let fixReport = null;
208
+ let unboundProjectFix = null;
209
+ let rootConfigMigration = null;
210
+ let report;
211
+ if (fix) {
212
+ const preReport = await runDoctorReport(resolution.target);
213
+ const plan = computeFixKnowledgePlan(preReport);
214
+ if (args["dry-run"] === true) {
215
+ if (plan.totalCount > 0) {
216
+ renderFixKnowledgePlan(plan);
217
+ }
218
+ report = preReport;
219
+ } else {
220
+ if (plan.totalCount > 0) {
221
+ renderFixKnowledgePlan(plan);
222
+ const decision = await resolveFixKnowledgeConsent({
223
+ yesFlag: args.yes === true,
224
+ envBypass: process.env.FABRIC_NONINTERACTIVE === "1",
225
+ plan
226
+ });
227
+ if (decision === "abort") {
228
+ process.exitCode = 1;
229
+ return;
230
+ }
231
+ }
232
+ rootConfigMigration = migrateRootConfig(resolution.target);
233
+ unboundProjectFix = await backfillUnboundProject(resolution.target);
234
+ fixReport = await runDoctorFix(resolution.target);
235
+ syncStoreAliasLinks();
236
+ fixKnowledgeReport = await runDoctorFixKnowledge(resolution.target);
237
+ report = fixKnowledgeReport?.report ?? preReport;
238
+ }
239
+ } else {
240
+ report = await runDoctorReport(resolution.target);
241
+ }
242
+ const storeDiagnostics = collectStoreDiagnostics(resolution.target);
243
+ if (args.json === true) {
244
+ writeStdout(
245
+ JSON.stringify(
246
+ {
247
+ ...fixKnowledgeReport ?? fixReport ?? report,
248
+ store_diagnostics: storeDiagnostics,
249
+ ...unboundProjectFix === null ? {} : { unbound_project_fix: unboundProjectFix },
250
+ ...rootConfigMigration?.migrated === true ? { root_config_migration: rootConfigMigration } : {}
251
+ },
252
+ null,
253
+ 2
254
+ )
255
+ );
256
+ } else {
257
+ if (fixReport != null) {
258
+ writeStdout(fixReport.message);
259
+ if (unboundProjectFix !== null) {
260
+ writeStdout(
261
+ dt("cli.doctor.unbound-project-backfilled", {
262
+ alias: unboundProjectFix.alias,
263
+ project: unboundProjectFix.active_project
264
+ })
265
+ );
266
+ }
267
+ if (rootConfigMigration?.migrated === true) {
268
+ writeStdout(
269
+ `config: migrated legacy root fabric.config.json \u2192 .fabric/fabric-config.json${rootConfigMigration.mergedKeys.length > 0 ? ` (merged: ${rootConfigMigration.mergedKeys.join(", ")})` : ""}`
270
+ );
271
+ }
272
+ }
273
+ if (fixKnowledgeReport != null) {
274
+ writeStdout(fixKnowledgeReport.message);
275
+ if (fixKnowledgeReport.aborted && fixKnowledgeReport.abort_reason !== void 0) {
276
+ writeStderr(fixKnowledgeReport.abort_reason);
277
+ }
278
+ renderFixKnowledgeMutations(fixKnowledgeReport, dt);
279
+ }
280
+ if (fix && args["dry-run"] === true) {
281
+ writeStdout(dt("cli.doctor.fix-dry-run-banner"));
282
+ }
283
+ renderHumanReport(report, dt, args.verbose === true);
284
+ renderStoreDiagnostics(storeDiagnostics);
285
+ }
286
+ await emitDoctorRunEventBestEffort(resolution.target, {
287
+ mode: fixKnowledgeReport != null ? "fix-knowledge" : "lint",
288
+ issues: report.fixable_errors.length + report.manual_errors.length + report.warnings.length,
289
+ mutations: fixKnowledgeReport != null ? fixKnowledgeReport.mutations.filter((m) => m.applied).length : void 0
290
+ });
291
+ if (fixKnowledgeReport != null) {
292
+ if (fixKnowledgeReport.aborted) {
293
+ process.exitCode = 1;
294
+ return;
295
+ }
296
+ if (fixKnowledgeReport.mutations.some((m) => !m.applied)) {
297
+ process.exitCode = 1;
298
+ return;
299
+ }
300
+ }
301
+ if (report.status === "error" || args.strict === true && (report.status === "warn" || report.warnings.length > 0)) {
302
+ process.exitCode = 1;
303
+ }
304
+ }
305
+ });
306
+ var doctor_default = doctorCommand;
307
+ function renderHumanReport(report, dt, verbose) {
308
+ writeStdout(renderDoctorHeader(report));
309
+ renderTldrHeader(report, dt, verbose);
310
+ const checksBlock = renderDoctorChecks(report, verbose);
311
+ if (checksBlock.length > 0) {
312
+ writeStdout(checksBlock);
313
+ }
314
+ const opts = { verbose, dt };
315
+ writeIssueSection(dt("doctor.section.fixable"), report.fixable_errors, opts);
316
+ writeIssueSection(dt("doctor.section.manual"), report.manual_errors, opts);
317
+ writeIssueSection(dt("doctor.section.warnings"), report.warnings, opts);
318
+ renderPayloadLimits(report, dt);
319
+ }
320
+ function collectStoreDiagnostics(projectRoot) {
321
+ try {
322
+ return storeDoctorChecks(projectRoot);
323
+ } catch {
324
+ return [];
325
+ }
326
+ }
327
+ function renderStoreDiagnostics(diagnostics) {
328
+ const block = renderDoctorStoreHealth(diagnostics);
329
+ if (block.length === 0) {
330
+ return;
331
+ }
332
+ writeStdout("");
333
+ writeStdout(block);
334
+ }
335
+ function renderDoctorHeader(report) {
336
+ return `${sectionBar(`fabric doctor \xB7 ${report.summary.target}`)} ${renderStatus(report.status)}`;
337
+ }
338
+ function renderDoctorStoreHealth(diagnostics) {
339
+ if (diagnostics.length === 0) {
340
+ return "";
341
+ }
342
+ const rows = diagnostics.map((diagnostic) => {
343
+ const mark = diagnostic.severity === "error" ? symbol.error : diagnostic.severity === "warn" ? symbol.warn : "[info]";
344
+ const ref = diagnostic.ref === void 0 ? "" : ` [${diagnostic.ref}]`;
345
+ return { text: `${mark}${ref} ${diagnostic.message}` };
346
+ });
347
+ return `${sectionBar("Store Health")}
348
+ ${tree(rows)}`;
349
+ }
350
+ function renderDoctorChecks(report, verbose) {
351
+ const rows = report.checks.filter((check) => verbose || check.status !== "ok").map((check) => ({ text: `${renderStatus(check.status)} ${check.name}: ${check.message}` }));
352
+ if (rows.length === 0) {
353
+ return "";
354
+ }
355
+ return `${sectionBar("Checks")}
356
+ ${tree(rows)}`;
357
+ }
358
+ function renderPayloadLimits(report, dt) {
359
+ const limits = report.summary.payload_limits;
360
+ if (limits === void 0) {
361
+ return;
362
+ }
363
+ writeStdout("");
364
+ writeStdout(dt("doctor.section.payload-limits"));
365
+ writeStdout(
366
+ `- ${dt("doctor.payload-limits.line", {
367
+ warnKb: String(Math.round(limits.warn_bytes / 1024)),
368
+ hardKb: String(Math.round(limits.hard_bytes / 1024)),
369
+ source: limits.source
370
+ })}`
371
+ );
372
+ }
373
+ function renderFixKnowledgeMutations(fixKnowledgeReport, dt) {
374
+ if (fixKnowledgeReport.mutations.length === 0) {
375
+ return;
376
+ }
377
+ writeStdout("");
378
+ writeStdout(dt("doctor.section.fix-knowledge-mutations"));
379
+ for (const mutation of fixKnowledgeReport.mutations) {
380
+ const marker = mutation.applied ? symbol.ok : symbol.error;
381
+ const errSuffix = mutation.applied || mutation.error === void 0 ? "" : ` (${mutation.error})`;
382
+ writeStdout(`${marker} ${mutation.kind}: ${mutation.path} [${mutation.detail}]${errSuffix}`);
383
+ }
384
+ }
385
+ function writeIssueSection(title, issues, options) {
386
+ if (issues.length === 0) {
387
+ return;
388
+ }
389
+ writeStdout("");
390
+ writeStdout(title);
391
+ for (const issue of issues) {
392
+ writeStdout(`- ${issue.code}: ${issue.message}`);
393
+ if (issue.actionHint !== void 0 && issue.actionHint.length > 0) {
394
+ if (issue.audience === "maintainer" && !options.verbose) {
395
+ writeStdout(` \u2192 ${options.dt("doctor.maintainer-hint-folded")}`);
396
+ } else {
397
+ writeStdout(` \u2192 ${issue.actionHint}`);
398
+ }
399
+ }
400
+ }
401
+ }
402
+ function renderTldrHeader(report, dt, verbose) {
403
+ const ranked = [];
404
+ for (const issue of report.fixable_errors) {
405
+ ranked.push({ severity: "fixable", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
406
+ }
407
+ for (const issue of report.manual_errors) {
408
+ ranked.push({ severity: "manual", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
409
+ }
410
+ for (const issue of report.warnings) {
411
+ ranked.push({ severity: "warn", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
412
+ }
413
+ if (ranked.length === 0) {
414
+ writeStdout(`${symbol.ok} TL;DR: all 48 checks green \u2014 nothing to fix.`);
415
+ return;
416
+ }
417
+ const top3 = ranked.slice(0, 3);
418
+ writeStdout(
419
+ `TL;DR (top ${top3.length} of ${ranked.length}, severity order: fixable\u2192manual\u2192warn):`
420
+ );
421
+ for (const item of top3) {
422
+ const marker = item.severity === "fixable" ? symbol.error : item.severity === "manual" ? symbol.error : symbol.warn;
423
+ const truncated = item.message.length > 140 ? `${item.message.slice(0, 137)}...` : item.message;
424
+ writeStdout(` ${marker} ${item.code}: ${truncated}`);
425
+ if (item.actionHint !== void 0 && item.actionHint.length > 0) {
426
+ writeStdout(
427
+ item.audience === "maintainer" && !verbose ? ` \u2192 ${dt("doctor.maintainer-hint-folded")}` : ` \u2192 ${item.actionHint}`
428
+ );
429
+ }
430
+ }
431
+ }
432
+ function renderStatus(status) {
433
+ if (status === "ok") {
434
+ return symbol.ok;
435
+ }
436
+ if (status === "warn") {
437
+ return symbol.warn;
438
+ }
439
+ return symbol.error;
440
+ }
441
+ function writeStdout(message) {
442
+ process.stdout.write(`${message}
443
+ `);
444
+ }
445
+ async function emitDoctorRunEventBestEffort(projectRoot, payload) {
446
+ try {
447
+ await appendEventLedgerEvent(projectRoot, {
448
+ event_type: "doctor_run",
449
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
450
+ mode: payload.mode,
451
+ issues: payload.issues,
452
+ ...payload.mutations !== void 0 ? { mutations: payload.mutations } : {}
453
+ });
454
+ } catch {
455
+ }
456
+ }
457
+ function writeStderr(message) {
458
+ process.stderr.write(`${message}
459
+ `);
460
+ }
461
+ function computeFixKnowledgePlan(report) {
462
+ const buckets = {};
463
+ const sources = [
464
+ ...report.fixable_errors,
465
+ ...report.warnings
466
+ ];
467
+ for (const issue of sources) {
468
+ if (FIX_KNOWLEDGE_CODE_LABELS[issue.code] === void 0) continue;
469
+ if (!Array.isArray(buckets[issue.code])) {
470
+ buckets[issue.code] = [];
471
+ }
472
+ buckets[issue.code].push(issue);
473
+ }
474
+ const codes = Object.keys(buckets).sort(
475
+ (a, b) => FIX_KNOWLEDGE_CODE_LABELS[a].localeCompare(FIX_KNOWLEDGE_CODE_LABELS[b])
476
+ );
477
+ const perCodeLines = [];
478
+ let totalCount = 0;
479
+ for (const code of codes) {
480
+ const items = buckets[code];
481
+ totalCount += items.length;
482
+ perCodeLines.push(` - ${FIX_KNOWLEDGE_CODE_LABELS[code]}: ${items.length}`);
483
+ }
484
+ const previewLines = [];
485
+ const flattened = codes.flatMap((c) => buckets[c]);
486
+ for (const item of flattened.slice(0, PLAN_PREVIEW_LIMIT)) {
487
+ const where = item.path !== void 0 && item.path.length > 0 ? `${item.path}` : "(no path)";
488
+ previewLines.push(` \u2022 ${where} \u2014 ${item.message}`);
489
+ }
490
+ if (flattened.length > PLAN_PREVIEW_LIMIT) {
491
+ previewLines.push(` \u2022 ... and ${flattened.length - PLAN_PREVIEW_LIMIT} more`);
492
+ }
493
+ return { totalCount, perCodeLines, previewLines };
494
+ }
495
+ function renderFixKnowledgePlan(plan) {
496
+ writeStdout("");
497
+ writeStdout(`${paint.warn("fix-knowledge mutation plan")} (${plan.totalCount} total)`);
498
+ for (const line of plan.perCodeLines) {
499
+ writeStdout(line);
500
+ }
501
+ if (plan.previewLines.length > 0) {
502
+ writeStdout("");
503
+ writeStdout(" preview:");
504
+ for (const line of plan.previewLines) {
505
+ writeStdout(line);
506
+ }
507
+ }
508
+ }
509
+ async function resolveFixKnowledgeConsent(options) {
510
+ if (options.yesFlag || options.envBypass) {
511
+ return "proceed";
512
+ }
513
+ if (process.stdin.isTTY !== true) {
514
+ writeStderr(
515
+ "doctor --fix-knowledge: stdin is not a TTY and neither --yes nor FABRIC_NONINTERACTIVE=1 is set. Refusing to mutate."
516
+ );
517
+ return "abort";
518
+ }
519
+ const message = `About to apply ${options.plan.totalCount} mutation(s) to knowledge entries (frontmatter writes + git mv + cache deletes). Proceed?`;
520
+ const answer = await confirm({
521
+ message,
522
+ initialValue: false
523
+ });
524
+ if (isCancel(answer) || answer !== true) {
525
+ writeStderr("doctor --fix-knowledge: aborted by user.");
526
+ return "abort";
527
+ }
528
+ return "proceed";
529
+ }
530
+ function renderDoctorFilteredHelp() {
531
+ const lines = [];
532
+ lines.push(paint.ai("fabric doctor") + " \u2014 Diagnose and fix Fabric workspace issues");
533
+ lines.push("");
534
+ lines.push(`${paint.human("USAGE")}`);
535
+ lines.push(` fabric doctor [OPTIONS]`);
536
+ lines.push("");
537
+ lines.push(`${paint.human("OPTIONS")}`);
538
+ lines.push("");
539
+ const exposedOptions = [
540
+ ["--target <path>", "Override project root (defaults to cwd)"],
541
+ ["--fix", "Auto-fix issues (derived-state + knowledge frontmatter/git mv)"],
542
+ ["--json", "Output as JSON for programmatic consumption"],
543
+ ["--verbose", "Show maintainer-audience action hints"]
544
+ ];
545
+ for (const [flag, desc] of exposedOptions) {
546
+ lines.push(` ${paint.ai(flag)} ${desc}`);
547
+ }
548
+ lines.push("");
549
+ lines.push(`${paint.human("EXAMPLES")}`);
550
+ lines.push(` ${paint.ai("fabric doctor")} # Run diagnostics`);
551
+ lines.push(` ${paint.ai("fabric doctor --fix")} # Fix derived-state + knowledge issues`);
552
+ lines.push("");
553
+ lines.push(paint.human("Run `fabric doctor` to see a full diagnostic report. Audits \u2192 `fabric audit`."));
554
+ writeStdout(lines.join("\n"));
555
+ }
556
+
557
+ export {
558
+ doctorCommand,
559
+ doctor_default,
560
+ renderDoctorHeader,
561
+ renderDoctorStoreHealth,
562
+ renderDoctorChecks,
563
+ renderTldrHeader,
564
+ renderDoctorFilteredHelp
565
+ };
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/colors.ts
4
+ import {
5
+ isColorEnabled as themeColorEnabled,
6
+ paint as themePaint,
7
+ symbol as themeSymbol
8
+ } from "@fenglimg/fabric-shared/theme";
9
+ import stringWidth from "string-width";
10
+ function isColorEnabled() {
11
+ return themeColorEnabled();
12
+ }
13
+ function tokenPainter(token) {
14
+ return (value) => themePaint(token, value, isColorEnabled());
15
+ }
16
+ var paint = {
17
+ success: tokenPainter("success"),
18
+ warn: tokenPainter("warn"),
19
+ error: tokenPainter("error"),
20
+ drift: tokenPainter("drift"),
21
+ ai: tokenPainter("ai"),
22
+ human: tokenPainter("human"),
23
+ muted: tokenPainter("muted")
24
+ };
25
+ var symbol = {
26
+ get ok() {
27
+ return themeSymbol("ok", isColorEnabled());
28
+ },
29
+ get warn() {
30
+ return themeSymbol("warn", isColorEnabled());
31
+ },
32
+ get error() {
33
+ return themeSymbol("error", isColorEnabled());
34
+ }
35
+ };
36
+ function displayWidth(value) {
37
+ return stringWidth(value);
38
+ }
39
+ function padEnd(value, width, char = " ") {
40
+ const fill = char.length > 0 ? char : " ";
41
+ let result = value;
42
+ while (displayWidth(result) < width) {
43
+ result += fill;
44
+ }
45
+ return result;
46
+ }
47
+
48
+ export {
49
+ isColorEnabled,
50
+ paint,
51
+ symbol,
52
+ displayWidth,
53
+ padEnd
54
+ };
@@ -1,18 +1,24 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ displayWidth,
4
+ isColorEnabled,
5
+ padEnd,
6
+ paint
7
+ } from "./chunk-SL77FXX7.js";
2
8
  import {
3
9
  regenerateBindingsSnapshot
4
- } from "./chunk-PTGQAZEW.js";
10
+ } from "./chunk-GGDVZCD6.js";
5
11
  import {
6
12
  storeBind,
7
13
  storeProjectCreate,
8
14
  storeProjectList,
9
15
  storeSetWriteRoute,
10
16
  storeSwitchWrite
11
- } from "./chunk-QPAW6IYT.js";
17
+ } from "./chunk-7V4XMLQ2.js";
12
18
  import {
13
19
  loadProjectConfig,
14
20
  saveProjectConfig
15
- } from "./chunk-QFIVFZRH.js";
21
+ } from "./chunk-I5F5BHWI.js";
16
22
 
17
23
  // src/install/migrate-root-config.ts
18
24
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -79,6 +85,38 @@ function migrateRootConfig(projectRoot) {
79
85
  return result;
80
86
  }
81
87
 
88
+ // src/tui/structure.ts
89
+ function tree(items, opts = {}) {
90
+ const on = isColorEnabled();
91
+ const indent = opts.indent ?? " ";
92
+ const mid = on ? "\u251C\u2500 " : "+- ";
93
+ const last = on ? "\u2514\u2500 " : "`- ";
94
+ return items.map((it, i) => {
95
+ const branch = paint.muted(i === items.length - 1 ? last : mid);
96
+ const marker = it.marker ? `${it.marker} ` : "";
97
+ return `${indent}${branch}${marker}${it.text}`;
98
+ }).join("\n");
99
+ }
100
+ function grid(rows, opts = {}) {
101
+ const on = isColorEnabled();
102
+ const gap = opts.gap ?? 2;
103
+ const cols = rows.length > 0 ? Math.max(...rows.map((r) => r.length)) : 0;
104
+ const widths = [];
105
+ for (let c = 0; c < cols; c++) {
106
+ widths[c] = Math.max(0, ...rows.map((r) => displayWidth(r[c] ?? "")));
107
+ }
108
+ const sep = " ".repeat(gap);
109
+ const lines = rows.map(
110
+ (r) => r.map((cell, c) => c === r.length - 1 ? cell ?? "" : padEnd(cell ?? "", widths[c])).join(sep)
111
+ );
112
+ if (opts.rule && lines.length > 0) {
113
+ const total = widths.reduce((a, b) => a + b, 0) + gap * Math.max(0, cols - 1);
114
+ const ruleLine = paint.muted((on ? "\u2500" : "-").repeat(total));
115
+ lines.splice(1, 0, ruleLine);
116
+ }
117
+ return lines.join("\n");
118
+ }
119
+
82
120
  // src/install/store-project-onboarding.ts
83
121
  import { execFileSync } from "child_process";
84
122
  import { randomUUID } from "crypto";
@@ -145,5 +183,7 @@ export {
145
183
  migrateRootConfig,
146
184
  suggestStoreProjectId,
147
185
  normalizeStoreProjectId,
148
- ensureStoreProjectBinding
186
+ ensureStoreProjectBinding,
187
+ tree,
188
+ grid
149
189
  };