@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
@@ -1,1096 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ensureStoreProjectBinding,
4
- migrateRootConfig
5
- } from "./chunk-3D7B2UAZ.js";
6
- import {
7
- resolveDevMode
8
- } from "./chunk-WA3DYGSY.js";
9
- import {
10
- paint,
11
- symbol
12
- } from "./chunk-NLNH64A3.js";
13
- import {
14
- detectAliasLinkDrift,
15
- missingRequiredStores,
16
- syncStoreAliasLinks,
17
- unboundAvailableStores
18
- } from "./chunk-QPAW6IYT.js";
19
- import {
20
- loadProjectConfig
21
- } from "./chunk-QFIVFZRH.js";
22
- import {
23
- loadGlobalConfig,
24
- resolveGlobalRoot
25
- } from "./chunk-FNHDQTPC.js";
26
- import {
27
- getDoctorTranslator,
28
- t
29
- } from "./chunk-HORSMSZL.js";
30
-
31
- // src/commands/doctor.ts
32
- import { confirm, isCancel } from "@clack/prompts";
33
- import { defineCommand } from "citty";
34
- import {
35
- appendEventLedgerEvent,
36
- enrichDescriptions,
37
- runDoctorApplyLint as runDoctorFixKnowledge,
38
- runDoctorArchiveHistory,
39
- runDoctorCiteCoverage,
40
- runDoctorFix,
41
- runDoctorHistoryAll,
42
- runDoctorReport,
43
- runDoctorConflictLint
44
- } from "@fenglimg/fabric-server";
45
-
46
- // src/install/backfill-unbound-project.ts
47
- import { detectUnboundProject } from "@fenglimg/fabric-server";
48
- async function backfillUnboundProject(projectRoot, globalRoot = resolveGlobalRoot()) {
49
- const violation = detectUnboundProject(projectRoot);
50
- if (violation === null) {
51
- return null;
52
- }
53
- const result = await ensureStoreProjectBinding(projectRoot, violation.alias, { globalRoot });
54
- return {
55
- alias: violation.alias,
56
- project_id: result.project_id,
57
- active_project: result.active_project
58
- };
59
- }
60
-
61
- // src/store/doctor-checks.ts
62
- import { join } from "path";
63
- import { findStoreExecutableViolations, storeRelativePathForMount } from "@fenglimg/fabric-shared";
64
- function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
65
- const diagnostics = [];
66
- const global = loadGlobalConfig(globalRoot);
67
- if (global === null) {
68
- diagnostics.push({
69
- code: "no_global_config",
70
- severity: "warn",
71
- message: "no global Fabric config \u2014 run `fabric install --global <url>`"
72
- });
73
- return diagnostics;
74
- }
75
- for (const missing of missingRequiredStores(projectRoot, globalRoot)) {
76
- diagnostics.push({
77
- code: "missing_required_store",
78
- severity: "warn",
79
- ref: missing.id,
80
- message: `required store '${missing.id}' is not mounted; run \`fabric store add\``
81
- });
82
- }
83
- for (const store of unboundAvailableStores(projectRoot, globalRoot)) {
84
- diagnostics.push({
85
- code: "unbound_available_store",
86
- severity: "info",
87
- ref: store.alias,
88
- 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)`
89
- });
90
- }
91
- const aliasDrift = detectAliasLinkDrift(globalRoot);
92
- if (aliasDrift.length > 0) {
93
- diagnostics.push({
94
- code: "store_alias_link_drift",
95
- severity: "info",
96
- ref: aliasDrift.join(", "),
97
- message: `by-alias readability link(s) out of sync for ${aliasDrift.join(", ")}; run \`fabric doctor --fix\` to repair ~/.fabric/stores/by-alias/`
98
- });
99
- }
100
- for (const store of global.stores) {
101
- if (store.remote === void 0 && store.personal !== true) {
102
- diagnostics.push({
103
- code: "local_only_store",
104
- severity: "info",
105
- ref: store.alias,
106
- message: `store '${store.alias}' is local-only; add a git remote to back it up`
107
- });
108
- }
109
- const violations = findStoreExecutableViolations(join(globalRoot, storeRelativePathForMount(store)));
110
- if (violations.length > 0) {
111
- diagnostics.push({
112
- code: "executable_in_store",
113
- severity: "warn",
114
- ref: store.alias,
115
- 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)`
116
- });
117
- }
118
- }
119
- return diagnostics;
120
- }
121
-
122
- // src/commands/doctor.ts
123
- import { buildDebugBundle } from "@fenglimg/fabric-shared";
124
- var FIX_KNOWLEDGE_CODE_LABELS = {
125
- knowledge_pending_auto_archive: "archive (git mv, pending)",
126
- knowledge_index_drift: "counter bump (agents.meta)",
127
- knowledge_session_hints_stale: "cache delete"
128
- };
129
- var PLAN_PREVIEW_LIMIT = 12;
130
- var doctorCommand = defineCommand({
131
- meta: {
132
- name: "doctor",
133
- description: t("cli.doctor.description")
134
- },
135
- args: {
136
- target: {
137
- type: "string",
138
- description: t("cli.doctor.args.target.description")
139
- },
140
- fix: {
141
- type: "boolean",
142
- description: t("cli.doctor.args.fix.description"),
143
- default: false
144
- },
145
- "fix-knowledge": {
146
- type: "boolean",
147
- description: t("cli.doctor.args.fix-knowledge.description"),
148
- default: false
149
- },
150
- json: {
151
- type: "boolean",
152
- description: t("cli.doctor.args.json.description"),
153
- default: false
154
- },
155
- // v2.1.0-rc.1 P6 (S40): emit a redacted diagnostic bundle (config + store
156
- // diagnostics; events excluded by default). Every string is secret-redacted
157
- // so the bundle is safe to paste into a bug report. Read-only.
158
- // EPIC-009: hidden flag (internal debug tool).
159
- "debug-bundle": {
160
- type: "boolean",
161
- description: "Emit a redacted diagnostic bundle (config + store health) for bug reports",
162
- default: false
163
- },
164
- // EPIC-009: hidden flag (CI automation).
165
- yes: {
166
- type: "boolean",
167
- description: t("cli.doctor.args.yes.description"),
168
- default: false
169
- },
170
- // EPIC-009: hidden flag (advanced output).
171
- verbose: {
172
- type: "boolean",
173
- description: t("cli.doctor.args.verbose.description"),
174
- default: false
175
- },
176
- // EPIC-009: hidden flags (report surfaces).
177
- "cite-coverage": {
178
- type: "boolean",
179
- description: t("cli.doctor.args.cite-coverage.description"),
180
- default: false
181
- },
182
- since: {
183
- type: "string",
184
- description: t("cli.doctor.args.since.description"),
185
- default: "7d"
186
- },
187
- client: {
188
- type: "string",
189
- description: t("cli.doctor.args.client.description"),
190
- default: "all",
191
- valueHint: "cc|codex|all"
192
- },
193
- layer: {
194
- type: "string",
195
- description: t("cli.doctor.args.layer.description"),
196
- default: "all",
197
- valueHint: "team|personal|all"
198
- },
199
- "enrich-descriptions": {
200
- type: "boolean",
201
- description: t("cli.doctor.args.enrich-descriptions.description"),
202
- default: false
203
- },
204
- auto: {
205
- type: "boolean",
206
- description: t("cli.doctor.args.auto.description"),
207
- default: false
208
- },
209
- "dry-run": {
210
- type: "boolean",
211
- description: t("cli.doctor.args.dry-run.description"),
212
- default: false
213
- },
214
- "archive-history": {
215
- type: "boolean",
216
- description: t("cli.doctor.args.archive-history.description"),
217
- default: false
218
- },
219
- history: {
220
- type: "string",
221
- description: t("cli.doctor.args.history.description"),
222
- valueHint: "archive|fix|all"
223
- },
224
- "lint-conflicts": {
225
- type: "boolean",
226
- description: t("cli.doctor.args.lint-conflicts.description"),
227
- default: false
228
- },
229
- deep: {
230
- type: "boolean",
231
- description: t("cli.doctor.args.deep.description"),
232
- default: false
233
- },
234
- // EPIC-009: hidden flag (strict mode for CI).
235
- strict: {
236
- type: "boolean",
237
- description: t("cli.doctor.args.strict.description"),
238
- default: false
239
- }
240
- },
241
- async run({ args }) {
242
- const workspaceRoot = process.cwd();
243
- const resolution = resolveDevMode(args.target, workspaceRoot);
244
- const dt = getDoctorTranslator(resolution.target);
245
- const fixKnowledge = args["fix-knowledge"] === true;
246
- const fix = args.fix === true;
247
- const citeCoverage = args["cite-coverage"] === true;
248
- const enrichDesc = args["enrich-descriptions"] === true;
249
- const archiveHistory = args["archive-history"] === true;
250
- if (args["debug-bundle"] === true) {
251
- const globalRoot = resolveGlobalRoot();
252
- let config = {};
253
- try {
254
- config = {
255
- global: loadGlobalConfig(globalRoot) ?? null,
256
- project: loadProjectConfig(resolution.target) ?? null
257
- };
258
- } catch {
259
- config = {};
260
- }
261
- const bundle = buildDebugBundle({
262
- config,
263
- diagnostics: collectStoreDiagnostics(resolution.target)
264
- });
265
- writeStdout(JSON.stringify(bundle, null, 2));
266
- return;
267
- }
268
- if (args.since !== void 0) {
269
- try {
270
- parseSinceDuration(args.since);
271
- } catch {
272
- writeStderr(dt("cli.doctor.errors.invalid-since", { input: args.since }));
273
- process.exitCode = 1;
274
- return;
275
- }
276
- }
277
- const historyMode = args.history;
278
- if (typeof historyMode === "string" && historyMode.length > 0) {
279
- if (fix || fixKnowledge || citeCoverage || enrichDesc || archiveHistory) {
280
- writeStderr(dt("cli.doctor.errors.history-mutex"));
281
- process.exitCode = 1;
282
- return;
283
- }
284
- if (historyMode !== "archive" && historyMode !== "fix" && historyMode !== "all") {
285
- writeStderr(dt("cli.doctor.errors.invalid-history-mode", { input: historyMode }));
286
- process.exitCode = 1;
287
- return;
288
- }
289
- const sinceInput = args.since ?? "7d";
290
- let sinceMs;
291
- try {
292
- sinceMs = parseSinceDuration(sinceInput);
293
- } catch {
294
- writeStderr(dt("cli.doctor.errors.invalid-since", { input: sinceInput }));
295
- process.exitCode = 1;
296
- return;
297
- }
298
- if (historyMode === "archive") {
299
- const report3 = await runDoctorArchiveHistory(resolution.target, { since: sinceMs });
300
- if (args.json === true) {
301
- writeStdout(JSON.stringify(report3, null, 2));
302
- } else {
303
- renderArchiveHistoryReport(report3, sinceInput, dt);
304
- }
305
- return;
306
- }
307
- const report2 = await runDoctorHistoryAll(resolution.target, { since: sinceMs });
308
- if (args.json === true) {
309
- writeStdout(JSON.stringify(report2, null, 2));
310
- } else {
311
- renderHistoryAllReport(report2, sinceInput, historyMode, dt);
312
- }
313
- return;
314
- }
315
- if (archiveHistory) {
316
- if (fix || fixKnowledge || citeCoverage || enrichDesc) {
317
- writeStderr(dt("cli.doctor.errors.archive-history-mutex"));
318
- process.exitCode = 1;
319
- return;
320
- }
321
- const sinceInput = args.since ?? "7d";
322
- let sinceMs;
323
- try {
324
- sinceMs = parseSinceDuration(sinceInput);
325
- } catch {
326
- writeStderr(dt("cli.doctor.errors.invalid-since", { input: sinceInput }));
327
- process.exitCode = 1;
328
- return;
329
- }
330
- const report2 = await runDoctorArchiveHistory(resolution.target, {
331
- since: sinceMs
332
- });
333
- if (args.json === true) {
334
- writeStdout(JSON.stringify(report2, null, 2));
335
- } else {
336
- renderArchiveHistoryReport(report2, sinceInput, dt);
337
- }
338
- return;
339
- }
340
- if (enrichDesc) {
341
- if (fix || fixKnowledge || citeCoverage) {
342
- writeStderr(dt("cli.doctor.errors.enrich-descriptions-mutex"));
343
- process.exitCode = 1;
344
- return;
345
- }
346
- const autoFlag = args.auto === true;
347
- const dryRun = args["dry-run"] === true;
348
- const report2 = await enrichDescriptions(resolution.target, {
349
- auto: autoFlag,
350
- dryRun
351
- });
352
- if (args.json === true) {
353
- writeStdout(JSON.stringify(report2, null, 2));
354
- } else {
355
- renderEnrichDescriptionsReport(report2, dt);
356
- }
357
- return;
358
- }
359
- if (citeCoverage) {
360
- if (fix || fixKnowledge) {
361
- writeStderr(dt("cli.doctor.errors.cite-coverage-mutex"));
362
- process.exitCode = 1;
363
- return;
364
- }
365
- let sinceMs;
366
- try {
367
- sinceMs = parseSinceDuration(args.since ?? "7d");
368
- } catch {
369
- writeStderr(dt("cli.doctor.errors.invalid-since", { input: args.since ?? "7d" }));
370
- process.exitCode = 1;
371
- return;
372
- }
373
- const clientFilter = args.client ?? "all";
374
- if (!isValidClientFilter(clientFilter)) {
375
- writeStderr(dt("cli.doctor.errors.invalid-client", { input: clientFilter }));
376
- process.exitCode = 1;
377
- return;
378
- }
379
- const layerFilter = args.layer ?? "all";
380
- if (!isValidLayerFilter(layerFilter)) {
381
- writeStderr(dt("cli.doctor.errors.invalid-layer", { input: layerFilter }));
382
- process.exitCode = 1;
383
- return;
384
- }
385
- const report2 = await runDoctorCiteCoverage(resolution.target, {
386
- since: sinceMs,
387
- client: clientFilter,
388
- layer: layerFilter
389
- });
390
- renderCiteCoverageReport(report2, args.json === true, dt);
391
- return;
392
- }
393
- if (args["lint-conflicts"] === true) {
394
- if (fix || fixKnowledge || citeCoverage) {
395
- writeStderr(dt("cli.doctor.errors.lint-conflicts-mutex"));
396
- process.exitCode = 1;
397
- return;
398
- }
399
- const report2 = await runDoctorConflictLint(resolution.target, {
400
- deep: args.deep === true
401
- });
402
- if (args.json === true) {
403
- writeStdout(JSON.stringify(report2, null, 2));
404
- } else {
405
- renderConflictLintReport(report2, args.deep === true, dt);
406
- }
407
- return;
408
- }
409
- if (fixKnowledge && fix) {
410
- writeStderr(dt("cli.doctor.errors.fix-knowledge-fix-mutually-exclusive"));
411
- process.exitCode = 1;
412
- return;
413
- }
414
- let fixKnowledgeReport = null;
415
- let fixReport = null;
416
- let unboundProjectFix = null;
417
- let rootConfigMigration = null;
418
- let report;
419
- if (fixKnowledge) {
420
- const preReport = await runDoctorReport(resolution.target);
421
- const plan = computeFixKnowledgePlan(preReport);
422
- if (args["dry-run"] === true) {
423
- if (plan.totalCount > 0) {
424
- renderFixKnowledgePlan(plan);
425
- }
426
- report = preReport;
427
- } else {
428
- const yesFlag = args.yes === true;
429
- const envBypass = process.env.FABRIC_NONINTERACTIVE === "1";
430
- if (plan.totalCount === 0) {
431
- } else {
432
- renderFixKnowledgePlan(plan);
433
- const decision = await resolveFixKnowledgeConsent({
434
- yesFlag,
435
- envBypass,
436
- plan
437
- });
438
- if (decision === "abort") {
439
- process.exitCode = 1;
440
- return;
441
- }
442
- }
443
- fixKnowledgeReport = await runDoctorFixKnowledge(resolution.target);
444
- report = fixKnowledgeReport.report;
445
- }
446
- } else if (fix) {
447
- if (args["dry-run"] === true) {
448
- report = await runDoctorReport(resolution.target);
449
- } else {
450
- rootConfigMigration = migrateRootConfig(resolution.target);
451
- unboundProjectFix = await backfillUnboundProject(resolution.target);
452
- fixReport = await runDoctorFix(resolution.target);
453
- report = fixReport.report;
454
- syncStoreAliasLinks();
455
- }
456
- } else {
457
- report = await runDoctorReport(resolution.target);
458
- }
459
- const storeDiagnostics = collectStoreDiagnostics(resolution.target);
460
- if (args.json === true) {
461
- writeStdout(
462
- JSON.stringify(
463
- {
464
- ...fixKnowledgeReport ?? fixReport ?? report,
465
- store_diagnostics: storeDiagnostics,
466
- ...unboundProjectFix === null ? {} : { unbound_project_fix: unboundProjectFix },
467
- ...rootConfigMigration?.migrated === true ? { root_config_migration: rootConfigMigration } : {}
468
- },
469
- null,
470
- 2
471
- )
472
- );
473
- } else {
474
- if (fixKnowledgeReport !== null) {
475
- writeStdout(fixKnowledgeReport.message);
476
- if (fixKnowledgeReport.aborted && fixKnowledgeReport.abort_reason !== void 0) {
477
- writeStderr(fixKnowledgeReport.abort_reason);
478
- }
479
- renderFixKnowledgeMutations(fixKnowledgeReport, dt);
480
- } else if (fixReport !== null) {
481
- writeStdout(fixReport.message);
482
- if (unboundProjectFix !== null) {
483
- writeStdout(
484
- dt("cli.doctor.unbound-project-backfilled", {
485
- alias: unboundProjectFix.alias,
486
- project: unboundProjectFix.active_project
487
- })
488
- );
489
- }
490
- if (rootConfigMigration?.migrated === true) {
491
- writeStdout(
492
- `config: migrated legacy root fabric.config.json \u2192 .fabric/fabric-config.json${rootConfigMigration.mergedKeys.length > 0 ? ` (merged: ${rootConfigMigration.mergedKeys.join(", ")})` : ""}`
493
- );
494
- }
495
- } else if ((fix || fixKnowledge) && args["dry-run"] === true) {
496
- writeStdout(dt("cli.doctor.fix-dry-run-banner"));
497
- }
498
- renderHumanReport(report, dt, args.verbose === true);
499
- renderStoreDiagnostics(storeDiagnostics);
500
- }
501
- await emitDoctorRunEventBestEffort(resolution.target, {
502
- mode: fixKnowledge ? "fix-knowledge" : "lint",
503
- issues: report.fixable_errors.length + report.manual_errors.length + report.warnings.length,
504
- mutations: fixKnowledgeReport !== null ? fixKnowledgeReport.mutations.filter((m) => m.applied).length : void 0
505
- });
506
- if (fixKnowledgeReport !== null) {
507
- if (fixKnowledgeReport.aborted) {
508
- process.exitCode = 1;
509
- return;
510
- }
511
- if (fixKnowledgeReport.mutations.some((m) => !m.applied)) {
512
- process.exitCode = 1;
513
- return;
514
- }
515
- }
516
- if (report.status === "error" || args.strict === true && (report.status === "warn" || report.warnings.length > 0)) {
517
- process.exitCode = 1;
518
- }
519
- }
520
- });
521
- var doctor_default = doctorCommand;
522
- function renderHumanReport(report, dt, verbose) {
523
- writeStdout(`${renderStatus(report.status)} ${paint.ai("fabric doctor")} ${paint.human(report.summary.target)}`);
524
- renderTldrHeader(report, dt, verbose);
525
- for (const check of report.checks) {
526
- if (!verbose && check.status === "ok") {
527
- continue;
528
- }
529
- writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
530
- }
531
- const opts = { verbose, dt };
532
- writeIssueSection(dt("doctor.section.fixable"), report.fixable_errors, opts);
533
- writeIssueSection(dt("doctor.section.manual"), report.manual_errors, opts);
534
- writeIssueSection(dt("doctor.section.warnings"), report.warnings, opts);
535
- renderPayloadLimits(report, dt);
536
- }
537
- function collectStoreDiagnostics(projectRoot) {
538
- try {
539
- return storeDoctorChecks(projectRoot);
540
- } catch {
541
- return [];
542
- }
543
- }
544
- function renderStoreDiagnostics(diagnostics) {
545
- if (diagnostics.length === 0) {
546
- return;
547
- }
548
- writeStdout("");
549
- writeStdout(paint.ai("store health"));
550
- for (const diagnostic of diagnostics) {
551
- const mark = diagnostic.severity === "error" ? symbol.error : diagnostic.severity === "warn" ? symbol.warn : "[info]";
552
- const ref = diagnostic.ref === void 0 ? "" : ` [${diagnostic.ref}]`;
553
- writeStdout(`${mark}${ref} ${diagnostic.message}`);
554
- }
555
- }
556
- function renderPayloadLimits(report, dt) {
557
- const limits = report.summary.payload_limits;
558
- if (limits === void 0) {
559
- return;
560
- }
561
- writeStdout("");
562
- writeStdout(dt("doctor.section.payload-limits"));
563
- writeStdout(
564
- `- ${dt("doctor.payload-limits.line", {
565
- warnKb: String(Math.round(limits.warn_bytes / 1024)),
566
- hardKb: String(Math.round(limits.hard_bytes / 1024)),
567
- source: limits.source
568
- })}`
569
- );
570
- }
571
- function renderFixKnowledgeMutations(fixKnowledgeReport, dt) {
572
- if (fixKnowledgeReport.mutations.length === 0) {
573
- return;
574
- }
575
- writeStdout("");
576
- writeStdout(dt("doctor.section.fix-knowledge-mutations"));
577
- for (const mutation of fixKnowledgeReport.mutations) {
578
- const marker = mutation.applied ? symbol.ok : symbol.error;
579
- const errSuffix = mutation.applied || mutation.error === void 0 ? "" : ` (${mutation.error})`;
580
- writeStdout(`${marker} ${mutation.kind}: ${mutation.path} [${mutation.detail}]${errSuffix}`);
581
- }
582
- }
583
- function writeIssueSection(title, issues, options) {
584
- if (issues.length === 0) {
585
- return;
586
- }
587
- writeStdout("");
588
- writeStdout(title);
589
- for (const issue of issues) {
590
- writeStdout(`- ${issue.code}: ${issue.message}`);
591
- if (issue.actionHint !== void 0 && issue.actionHint.length > 0) {
592
- if (issue.audience === "maintainer" && !options.verbose) {
593
- writeStdout(` \u2192 ${options.dt("doctor.maintainer-hint-folded")}`);
594
- } else {
595
- writeStdout(` \u2192 ${issue.actionHint}`);
596
- }
597
- }
598
- }
599
- }
600
- function renderTldrHeader(report, dt, verbose) {
601
- const ranked = [];
602
- for (const issue of report.fixable_errors) {
603
- ranked.push({ severity: "fixable", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
604
- }
605
- for (const issue of report.manual_errors) {
606
- ranked.push({ severity: "manual", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
607
- }
608
- for (const issue of report.warnings) {
609
- ranked.push({ severity: "warn", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
610
- }
611
- if (ranked.length === 0) {
612
- writeStdout(`${symbol.ok} TL;DR: all 48 checks green \u2014 nothing to fix.`);
613
- return;
614
- }
615
- const top3 = ranked.slice(0, 3);
616
- writeStdout(
617
- `TL;DR (top ${top3.length} of ${ranked.length}, severity order: fixable\u2192manual\u2192warn):`
618
- );
619
- for (const item of top3) {
620
- const marker = item.severity === "fixable" ? symbol.error : item.severity === "manual" ? symbol.error : symbol.warn;
621
- const truncated = item.message.length > 140 ? `${item.message.slice(0, 137)}...` : item.message;
622
- writeStdout(` ${marker} ${item.code}: ${truncated}`);
623
- if (item.actionHint !== void 0 && item.actionHint.length > 0) {
624
- writeStdout(
625
- item.audience === "maintainer" && !verbose ? ` \u2192 ${dt("doctor.maintainer-hint-folded")}` : ` \u2192 ${item.actionHint}`
626
- );
627
- }
628
- }
629
- }
630
- function renderStatus(status) {
631
- if (status === "ok") {
632
- return symbol.ok;
633
- }
634
- if (status === "warn") {
635
- return symbol.warn;
636
- }
637
- return symbol.error;
638
- }
639
- function writeStdout(message) {
640
- process.stdout.write(`${message}
641
- `);
642
- }
643
- async function emitDoctorRunEventBestEffort(projectRoot, payload) {
644
- try {
645
- await appendEventLedgerEvent(projectRoot, {
646
- event_type: "doctor_run",
647
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
648
- mode: payload.mode,
649
- issues: payload.issues,
650
- ...payload.mutations !== void 0 ? { mutations: payload.mutations } : {}
651
- });
652
- } catch {
653
- }
654
- }
655
- function writeStderr(message) {
656
- process.stderr.write(`${message}
657
- `);
658
- }
659
- function computeFixKnowledgePlan(report) {
660
- const buckets = {};
661
- const sources = [
662
- ...report.fixable_errors,
663
- ...report.warnings
664
- ];
665
- for (const issue of sources) {
666
- if (FIX_KNOWLEDGE_CODE_LABELS[issue.code] === void 0) continue;
667
- if (!Array.isArray(buckets[issue.code])) {
668
- buckets[issue.code] = [];
669
- }
670
- buckets[issue.code].push(issue);
671
- }
672
- const codes = Object.keys(buckets).sort(
673
- (a, b) => FIX_KNOWLEDGE_CODE_LABELS[a].localeCompare(FIX_KNOWLEDGE_CODE_LABELS[b])
674
- );
675
- const perCodeLines = [];
676
- let totalCount = 0;
677
- for (const code of codes) {
678
- const items = buckets[code];
679
- totalCount += items.length;
680
- perCodeLines.push(` - ${FIX_KNOWLEDGE_CODE_LABELS[code]}: ${items.length}`);
681
- }
682
- const previewLines = [];
683
- const flattened = codes.flatMap((c) => buckets[c]);
684
- for (const item of flattened.slice(0, PLAN_PREVIEW_LIMIT)) {
685
- const where = item.path !== void 0 && item.path.length > 0 ? `${item.path}` : "(no path)";
686
- previewLines.push(` \u2022 ${where} \u2014 ${item.message}`);
687
- }
688
- if (flattened.length > PLAN_PREVIEW_LIMIT) {
689
- previewLines.push(` \u2022 ... and ${flattened.length - PLAN_PREVIEW_LIMIT} more`);
690
- }
691
- return { totalCount, perCodeLines, previewLines };
692
- }
693
- function renderFixKnowledgePlan(plan) {
694
- writeStdout("");
695
- writeStdout(`${paint.warn("fix-knowledge mutation plan")} (${plan.totalCount} total)`);
696
- for (const line of plan.perCodeLines) {
697
- writeStdout(line);
698
- }
699
- if (plan.previewLines.length > 0) {
700
- writeStdout("");
701
- writeStdout(" preview:");
702
- for (const line of plan.previewLines) {
703
- writeStdout(line);
704
- }
705
- }
706
- }
707
- async function resolveFixKnowledgeConsent(options) {
708
- if (options.yesFlag || options.envBypass) {
709
- return "proceed";
710
- }
711
- if (process.stdin.isTTY !== true) {
712
- writeStderr(
713
- "doctor --fix-knowledge: stdin is not a TTY and neither --yes nor FABRIC_NONINTERACTIVE=1 is set. Refusing to mutate."
714
- );
715
- return "abort";
716
- }
717
- const message = `About to apply ${options.plan.totalCount} mutation(s) to knowledge entries (frontmatter writes + git mv + cache deletes). Proceed?`;
718
- const answer = await confirm({
719
- message,
720
- initialValue: false
721
- });
722
- if (isCancel(answer) || answer !== true) {
723
- writeStderr("doctor --fix-knowledge: aborted by user.");
724
- return "abort";
725
- }
726
- return "proceed";
727
- }
728
- var CITE_COVERAGE_CLIENT_FILTERS = /* @__PURE__ */ new Set([
729
- "cc",
730
- "codex",
731
- "all"
732
- ]);
733
- function isValidClientFilter(input) {
734
- return CITE_COVERAGE_CLIENT_FILTERS.has(input);
735
- }
736
- var CITE_COVERAGE_LAYER_FILTERS = /* @__PURE__ */ new Set([
737
- "team",
738
- "personal",
739
- "all"
740
- ]);
741
- function isValidLayerFilter(input) {
742
- return CITE_COVERAGE_LAYER_FILTERS.has(input);
743
- }
744
- function renderCiteCoverageReport(report, jsonMode, dt) {
745
- if (jsonMode) {
746
- writeStdout(JSON.stringify(report, null, 2));
747
- return;
748
- }
749
- if (report.status === "skipped") {
750
- writeStdout(dt("doctor.cite.status.skipped"));
751
- return;
752
- }
753
- const lines = [];
754
- lines.push(dt("doctor.section.cite-coverage"));
755
- lines.push(
756
- dt("doctor.cite.header", {
757
- since: new Date(report.since_ts).toISOString(),
758
- marker: new Date(report.marker_ts).toISOString()
759
- })
760
- );
761
- if (report.marker_emitted_now) {
762
- lines.push(dt("doctor.cite.warning.justActivated"));
763
- }
764
- lines.push("");
765
- lines.push(` ${dt("doctor.cite.metric.editsTouched")}: ${report.metrics.edits_touched}`);
766
- lines.push(` ${dt("doctor.cite.metric.qualifyingCites")}: ${report.metrics.qualifying_cites}`);
767
- lines.push(` ${dt("doctor.cite.metric.recalledUnverified")}: ${report.metrics.recalled_unverified}`);
768
- lines.push(` ${dt("doctor.cite.metric.expectedButMissed")}: ${report.metrics.expected_but_missed}`);
769
- lines.push(` ${dt("doctor.cite.metric.totalTurns")}: ${report.metrics.total_turns}`);
770
- const complianceRate = report.metrics.cite_compliance_rate;
771
- const complianceStr = complianceRate === null || complianceRate === void 0 ? dt("doctor.cite.metric.complianceNA") : `${(complianceRate * 100).toFixed(1)}% (${report.metrics.compliant_cites ?? 0}/${(report.metrics.compliant_cites ?? 0) + (report.metrics.noncompliant_cites ?? 0)})`;
772
- lines.push(` ${dt("doctor.cite.metric.complianceRate")}: ${complianceStr}`);
773
- const recallRate = report.metrics.recall_coverage_rate;
774
- const recallStr = recallRate === null || recallRate === void 0 ? dt("doctor.cite.metric.recallCoverageNA") : `${(recallRate * 100).toFixed(1)}% (${report.metrics.recall_backed_edits ?? 0}/${report.metrics.edits_touched})`;
775
- lines.push(` ${dt("doctor.cite.metric.recallCoverage")}: ${recallStr}`);
776
- const uncorrelatable = report.metrics.uncorrelatable_edits ?? 0;
777
- if (uncorrelatable > 0) {
778
- lines.push(` ${dt("doctor.cite.metric.uncorrelatableEdits")}: ${uncorrelatable}`);
779
- }
780
- if (report.metrics.exposed_and_mutated !== void 0) {
781
- lines.push(
782
- ` ${dt("doctor.cite.metric.exposedAndMutated")}: ${report.metrics.exposed_and_mutated.count}`
783
- );
784
- }
785
- if (report.metrics.mutations_observed !== void 0) {
786
- lines.push(
787
- ` ${dt("doctor.cite.metric.mutationsObserved")}: ${report.metrics.mutations_observed.count}`
788
- );
789
- }
790
- if (report.metrics.mutation_pool !== void 0) {
791
- lines.push(
792
- ` ${dt("doctor.cite.metric.mutationPool")}: ${report.metrics.mutation_pool.attributed} / ${report.metrics.mutation_pool.unattributed_workspace_dirty} (attributed / unattributed_workspace_dirty)`
793
- );
794
- }
795
- if (report.metrics.sessions_closed !== void 0) {
796
- lines.push(
797
- ` ${dt("doctor.cite.metric.sessionsClosed")}: ${report.metrics.sessions_closed.count}`
798
- );
799
- }
800
- if (report.metrics.by_store !== void 0) {
801
- const storeKeys = Object.keys(report.metrics.by_store).sort();
802
- if (storeKeys.length > 0) {
803
- lines.push(` ${dt("doctor.cite.metric.byStore")}:`);
804
- for (const store of storeKeys) {
805
- lines.push(` ${store}: ${report.metrics.by_store[store].qualifying_cites}`);
806
- }
807
- }
808
- }
809
- if (report.per_client !== void 0 && Object.keys(report.per_client).length > 1) {
810
- lines.push("");
811
- lines.push(`### ${dt("doctor.cite.section.perClient")}`);
812
- for (const [client, metrics] of Object.entries(report.per_client)) {
813
- const summary = Object.entries(metrics).map(([k, v]) => `${k}=${v}`).join(" / ");
814
- lines.push(` ${client}: ${summary}`);
815
- }
816
- }
817
- if (report.dismissed_reason_histogram !== void 0 && Object.keys(report.dismissed_reason_histogram).length > 0) {
818
- lines.push("");
819
- lines.push(`### ${dt("doctor.cite.section.dismissedReasons")}`);
820
- for (const [reason, count] of Object.entries(report.dismissed_reason_histogram)) {
821
- const label = dt(`doctor.cite.dismissed.${reason}`);
822
- lines.push(` ${label}: ${count}`);
823
- }
824
- }
825
- if (report.none_reason_histogram !== void 0 && Object.keys(report.none_reason_histogram).length > 0) {
826
- lines.push("");
827
- lines.push(`### ${dt("doctor.cite.section.noneReasons")}`);
828
- for (const [reason, count] of Object.entries(report.none_reason_histogram)) {
829
- const label = dt(`doctor.cite.none.${reason}`);
830
- lines.push(` ${label}: ${count}`);
831
- }
832
- }
833
- appendContractSection(lines, report, dt);
834
- writeStdout(lines.join("\n"));
835
- }
836
- function appendContractSection(lines, report, dt) {
837
- const status = report.contract_metrics_status;
838
- if (status === void 0) {
839
- return;
840
- }
841
- const metrics = report.contract_metrics;
842
- const perLayerType = report.per_layer_type;
843
- const allCountsZero = metrics === void 0 || metrics.decisions_cited === 0 && metrics.pitfalls_cited === 0 && metrics.contract_with === 0 && metrics.contract_missing === 0 && metrics.hard_violated === 0 && metrics.cite_id_unresolved === 0 && Object.keys(metrics.skip_count).length === 0;
844
- if (status === "awaiting_marker" && allCountsZero) {
845
- return;
846
- }
847
- lines.push("");
848
- lines.push(`### ${dt("cite-coverage.contract.header")}`);
849
- if (status === "skipped:bootstrap_drift") {
850
- lines.push(` ${dt("cite-coverage.contract.status.skipped_bootstrap_drift")}`);
851
- return;
852
- }
853
- const statusKey = status === "ok" ? "cite-coverage.contract.status.ok" : "cite-coverage.contract.status.awaiting_marker";
854
- lines.push(` status: ${dt(statusKey)}`);
855
- if (typeof report.contract_marker_ts === "number" && report.contract_marker_ts > 0) {
856
- lines.push(` since: ${new Date(report.contract_marker_ts).toISOString()}`);
857
- }
858
- if (report.layer_filter !== void 0) {
859
- lines.push(` layer filter: ${report.layer_filter}`);
860
- }
861
- if (metrics !== void 0) {
862
- lines.push(` ${dt("cite-coverage.contract.decisions_cited")}: ${metrics.decisions_cited}`);
863
- lines.push(` ${dt("cite-coverage.contract.pitfalls_cited")}: ${metrics.pitfalls_cited}`);
864
- lines.push(` ${dt("cite-coverage.contract.with")}: ${metrics.contract_with}`);
865
- lines.push(` ${dt("cite-coverage.contract.missing")}: ${metrics.contract_missing}`);
866
- if (metrics.hard_violated > 0) {
867
- const layerSuffix = report.layer_filter === "personal" ? dt("cite-coverage.layer.personal_fyi") : dt("cite-coverage.layer.team_review");
868
- lines.push(
869
- ` ${dt("cite-coverage.contract.hard_violated")} ${layerSuffix}: ${metrics.hard_violated}`
870
- );
871
- }
872
- }
873
- if (perLayerType !== void 0) {
874
- const teamKeys = Object.keys(perLayerType.team).filter(
875
- (k) => perLayerType.team[k] > 0
876
- );
877
- const personalKeys = Object.keys(perLayerType.personal).filter(
878
- (k) => perLayerType.personal[k] > 0
879
- );
880
- if (teamKeys.length > 0 || personalKeys.length > 0) {
881
- lines.push("");
882
- lines.push(`#### ${dt("cite-coverage.layer.team")} \xD7 ${dt("cite-coverage.layer.personal")}`);
883
- for (const key of teamKeys) {
884
- const label = dt(`cite-coverage.contract.type.${key}`);
885
- lines.push(` ${dt("cite-coverage.layer.team")} \u2014 ${label}: ${perLayerType.team[key]}`);
886
- }
887
- for (const key of personalKeys) {
888
- const label = dt(`cite-coverage.contract.type.${key}`);
889
- lines.push(
890
- ` ${dt("cite-coverage.layer.personal")} \u2014 ${label}: ${perLayerType.personal[key]}`
891
- );
892
- }
893
- }
894
- }
895
- if (metrics !== void 0 && Object.keys(metrics.skip_count).length > 0) {
896
- lines.push("");
897
- lines.push(`#### ${dt("cite-coverage.contract.skip_count")}`);
898
- for (const [reason, count] of Object.entries(metrics.skip_count)) {
899
- const label = dt(`cite-coverage.skip.${reason}`);
900
- lines.push(` ${label}: ${count}`);
901
- }
902
- }
903
- if (metrics !== void 0 && metrics.cite_id_unresolved > 0) {
904
- lines.push("");
905
- lines.push(
906
- `${symbol.warn} ${dt("cite-coverage.contract.cite_id_unresolved")}: ${metrics.cite_id_unresolved}`
907
- );
908
- }
909
- }
910
- function renderConflictLintReport(report, deepRequested, dt) {
911
- const lines = [];
912
- lines.push(dt("doctor.conflict.header"));
913
- lines.push("");
914
- if (report.candidate_count === 0) {
915
- lines.push(` ${symbol.ok} ${dt("doctor.conflict.none")}`);
916
- writeStdout(lines.join("\n"));
917
- return;
918
- }
919
- lines.push(
920
- ` ${dt("doctor.conflict.summary", {
921
- candidates: String(report.candidate_count),
922
- conflicts: String(report.conflict_count),
923
- threshold: report.threshold.toFixed(2)
924
- })}`
925
- );
926
- if (deepRequested && !report.deep) {
927
- lines.push(` ${symbol.warn} ${dt("doctor.conflict.deep_no_judge")}`);
928
- }
929
- lines.push("");
930
- for (const pair of report.pairs) {
931
- const sym = pair.verdict === "conflict" ? symbol.error : symbol.warn;
932
- const verdictLabel = dt(`doctor.conflict.verdict.${pair.verdict}`);
933
- const pct = `${(pair.similarity * 100).toFixed(0)}%`;
934
- let line = ` ${sym} [${pair.a} \u2194 ${pair.b}] (${pair.knowledge_type}/${pair.layer}) ${pct} \u2014 ${verdictLabel}`;
935
- if (pair.rationale !== void 0 && pair.rationale.length > 0) {
936
- line += `: ${pair.rationale}`;
937
- }
938
- lines.push(line);
939
- }
940
- writeStdout(lines.join("\n"));
941
- }
942
- function renderEnrichDescriptionsReport(report, dt) {
943
- const header = `${symbol.ok} ${paint.ai("fabric doctor --enrich-descriptions")} mode=${report.mode}${report.dryRun ? " (dry-run)" : ""} scanned=${report.scanned} modified=${report.modified} skipped=${report.skipped}`;
944
- writeStdout(header);
945
- if (report.candidates.length === 0) {
946
- writeStdout(dt("doctor.enrich.allComplete"));
947
- return;
948
- }
949
- writeStdout("");
950
- for (const candidate of report.candidates) {
951
- if (candidate.error !== void 0) {
952
- writeStdout(`${symbol.error} ${candidate.path} \u2014 ${candidate.error}`);
953
- continue;
954
- }
955
- const missing = candidate.missing.join(", ");
956
- if (candidate.modified) {
957
- const added = candidate.added_fields.join(", ");
958
- writeStdout(
959
- `${symbol.ok} ${candidate.path} \u2014 missing: ${missing} \u2192 added: ${added}`
960
- );
961
- } else {
962
- writeStdout(`${symbol.warn} ${candidate.path} \u2014 missing: ${missing}`);
963
- }
964
- }
965
- }
966
- function parseSinceDuration(input) {
967
- const trimmed = input.trim();
968
- if (trimmed.length === 0) {
969
- throw new Error(`invalid --since value: ${input}`);
970
- }
971
- const durationMatch = /^(\d+)([dhm])$/.exec(trimmed);
972
- if (durationMatch !== null) {
973
- const value = Number.parseInt(durationMatch[1], 10);
974
- const unit = durationMatch[2];
975
- if (!Number.isFinite(value) || value <= 0) {
976
- throw new Error(`invalid --since value: ${input}`);
977
- }
978
- const unitMs = unit === "d" ? 864e5 : unit === "h" ? 36e5 : 6e4;
979
- return Date.now() - value * unitMs;
980
- }
981
- if (/^\d+$/.test(trimmed)) {
982
- const value = Number.parseInt(trimmed, 10);
983
- if (!Number.isFinite(value) || value < 0) {
984
- throw new Error(`invalid --since value: ${input}`);
985
- }
986
- return value;
987
- }
988
- throw new Error(`invalid --since value: ${input}`);
989
- }
990
- function renderArchiveHistoryReport(report, sinceLabel, dt) {
991
- if (report.entries.length === 0) {
992
- writeStdout(dt("doctor.archive-history.empty", { sinceLabel }));
993
- return;
994
- }
995
- const lines = [];
996
- lines.push(
997
- dt("doctor.archive-history.header", {
998
- sinceLabel,
999
- count: String(report.total),
1000
- plural: report.total === 1 ? "" : "s"
1001
- })
1002
- );
1003
- lines.push("");
1004
- lines.push(
1005
- `| ${dt("doctor.archive-history.table.session")} | ${dt(
1006
- "doctor.archive-history.table.lastAttempt"
1007
- )} | ${dt("doctor.archive-history.table.outcome")} | ${dt(
1008
- "doctor.archive-history.table.candidates"
1009
- )} | ${dt("doctor.archive-history.table.coveredGap")} |`
1010
- );
1011
- lines.push("| ------- | ---------------- | -------- | ---------- | ----------- |");
1012
- for (const entry of report.entries) {
1013
- const lastAttempt = formatTimestampForTable(entry.last_attempted_at);
1014
- lines.push(
1015
- `| ${entry.session_id_short} | ${lastAttempt} | ${entry.outcome} | ${entry.candidates_proposed} | ${entry.age_since_covered_hours}h |`
1016
- );
1017
- }
1018
- writeStdout(lines.join("\n"));
1019
- }
1020
- function renderHistoryAllReport(report, sinceLabel, mode, dt) {
1021
- if (report.rows.length === 0) {
1022
- writeStdout(dt("doctor.history.empty", { sinceLabel, mode }));
1023
- return;
1024
- }
1025
- const lines = [];
1026
- lines.push(
1027
- dt("doctor.history.header", {
1028
- sinceLabel,
1029
- mode,
1030
- days: String(report.rows.length)
1031
- })
1032
- );
1033
- lines.push("");
1034
- if (mode === "fix") {
1035
- lines.push("| date | lint | fix | issues | mutations |");
1036
- lines.push("| ---------- | ---- | --- | ------ | --------- |");
1037
- for (const row of report.rows) {
1038
- lines.push(
1039
- `| ${row.date} | ${row.doctor_runs_lint} | ${row.doctor_runs_fix} | ${row.doctor_total_issues} | ${row.doctor_total_mutations} |`
1040
- );
1041
- }
1042
- } else {
1043
- lines.push("| date | lint | fix | issues | mutations | archive | proposed |");
1044
- lines.push("| ---------- | ---- | --- | ------ | --------- | ------- | -------- |");
1045
- for (const row of report.rows) {
1046
- lines.push(
1047
- `| ${row.date} | ${row.doctor_runs_lint} | ${row.doctor_runs_fix} | ${row.doctor_total_issues} | ${row.doctor_total_mutations} | ${row.archive_attempts} | ${row.archive_proposed} |`
1048
- );
1049
- }
1050
- }
1051
- writeStdout(lines.join("\n"));
1052
- }
1053
- function formatTimestampForTable(iso) {
1054
- const d = new Date(iso);
1055
- if (Number.isNaN(d.getTime())) return iso;
1056
- const pad = (n) => n < 10 ? `0${n}` : `${n}`;
1057
- return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(
1058
- d.getUTCHours()
1059
- )}:${pad(d.getUTCMinutes())}`;
1060
- }
1061
- function renderDoctorFilteredHelp() {
1062
- const lines = [];
1063
- lines.push(paint.ai("fabric doctor") + " \u2014 Diagnose and fix Fabric workspace issues");
1064
- lines.push("");
1065
- lines.push(`${paint.human("USAGE")}`);
1066
- lines.push(` fabric doctor [OPTIONS]`);
1067
- lines.push("");
1068
- lines.push(`${paint.human("OPTIONS")}`);
1069
- lines.push("");
1070
- const exposedOptions = [
1071
- ["--target <path>", "Override project root (defaults to cwd)"],
1072
- ["--fix", "Auto-fix derived-state issues (agents.meta.json)"],
1073
- ["--fix-knowledge", "Auto-fix knowledge entry issues (frontmatter + git mv)"],
1074
- ["--json", "Output as JSON for programmatic consumption"],
1075
- ["--verbose", "Show maintainer-audience action hints"]
1076
- ];
1077
- for (const [flag, desc] of exposedOptions) {
1078
- lines.push(` ${paint.ai(flag)} ${desc}`);
1079
- }
1080
- lines.push("");
1081
- lines.push(`${paint.human("EXAMPLES")}`);
1082
- lines.push(` ${paint.ai("fabric doctor")} # Run diagnostics`);
1083
- lines.push(` ${paint.ai("fabric doctor --fix")} # Fix derived-state issues`);
1084
- lines.push(` ${paint.ai("fabric doctor --fix-knowledge")} # Fix knowledge entry issues`);
1085
- lines.push("");
1086
- lines.push(paint.human("Run `fabric doctor` to see a full diagnostic report with 48 checks."));
1087
- writeStdout(lines.join("\n"));
1088
- }
1089
-
1090
- export {
1091
- doctorCommand,
1092
- doctor_default,
1093
- renderTldrHeader,
1094
- parseSinceDuration,
1095
- renderDoctorFilteredHelp
1096
- };