@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,25 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- paint,
4
- symbol
5
- } from "./chunk-WWNXR34K.js";
3
+ ensureStoreProjectBinding,
4
+ migrateRootConfig
5
+ } from "./chunk-3D7B2UAZ.js";
6
6
  import {
7
7
  resolveDevMode
8
- } from "./chunk-COI5VDFU.js";
8
+ } from "./chunk-WA3DYGSY.js";
9
9
  import {
10
- getDoctorTranslator,
11
- t
12
- } from "./chunk-PWLW3B57.js";
10
+ paint,
11
+ symbol
12
+ } from "./chunk-NLNH64A3.js";
13
13
  import {
14
- missingRequiredStores
15
- } from "./chunk-HFQVXY6P.js";
14
+ detectAliasLinkDrift,
15
+ missingRequiredStores,
16
+ syncStoreAliasLinks,
17
+ unboundAvailableStores
18
+ } from "./chunk-QPAW6IYT.js";
16
19
  import {
17
20
  loadProjectConfig
18
- } from "./chunk-LFIKMVY7.js";
21
+ } from "./chunk-QFIVFZRH.js";
19
22
  import {
20
23
  loadGlobalConfig,
21
24
  resolveGlobalRoot
22
- } from "./chunk-RYAFBNES.js";
25
+ } from "./chunk-FNHDQTPC.js";
26
+ import {
27
+ getDoctorTranslator,
28
+ t
29
+ } from "./chunk-HORSMSZL.js";
23
30
 
24
31
  // src/commands/doctor.ts
25
32
  import { confirm, isCancel } from "@clack/prompts";
@@ -32,12 +39,28 @@ import {
32
39
  runDoctorCiteCoverage,
33
40
  runDoctorFix,
34
41
  runDoctorHistoryAll,
35
- runDoctorReport
42
+ runDoctorReport,
43
+ runDoctorConflictLint
36
44
  } from "@fenglimg/fabric-server";
37
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
+
38
61
  // src/store/doctor-checks.ts
39
62
  import { join } from "path";
40
- import { findStoreExecutableViolations, storeRelativePath } from "@fenglimg/fabric-shared";
63
+ import { findStoreExecutableViolations, storeRelativePathForMount } from "@fenglimg/fabric-shared";
41
64
  function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
42
65
  const diagnostics = [];
43
66
  const global = loadGlobalConfig(globalRoot);
@@ -57,6 +80,23 @@ function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
57
80
  message: `required store '${missing.id}' is not mounted; run \`fabric store add\``
58
81
  });
59
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
+ }
60
100
  for (const store of global.stores) {
61
101
  if (store.remote === void 0 && store.personal !== true) {
62
102
  diagnostics.push({
@@ -66,7 +106,7 @@ function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
66
106
  message: `store '${store.alias}' is local-only; add a git remote to back it up`
67
107
  });
68
108
  }
69
- const violations = findStoreExecutableViolations(join(globalRoot, storeRelativePath(store.store_uuid)));
109
+ const violations = findStoreExecutableViolations(join(globalRoot, storeRelativePathForMount(store)));
70
110
  if (violations.length > 0) {
71
111
  diagnostics.push({
72
112
  code: "executable_in_store",
@@ -82,8 +122,6 @@ function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
82
122
  // src/commands/doctor.ts
83
123
  import { buildDebugBundle } from "@fenglimg/fabric-shared";
84
124
  var FIX_KNOWLEDGE_CODE_LABELS = {
85
- knowledge_orphan_demote_required: "demote (maturity)",
86
- knowledge_stale_archive_required: "archive (git mv)",
87
125
  knowledge_pending_auto_archive: "archive (git mv, pending)",
88
126
  knowledge_index_drift: "counter bump (agents.meta)",
89
127
  knowledge_session_hints_stale: "cache delete"
@@ -114,40 +152,28 @@ var doctorCommand = defineCommand({
114
152
  description: t("cli.doctor.args.json.description"),
115
153
  default: false
116
154
  },
117
- strict: {
118
- type: "boolean",
119
- description: t("cli.doctor.args.strict.description"),
120
- default: false
121
- },
122
155
  // v2.1.0-rc.1 P6 (S40): emit a redacted diagnostic bundle (config + store
123
156
  // diagnostics; events excluded by default). Every string is secret-redacted
124
157
  // so the bundle is safe to paste into a bug report. Read-only.
158
+ // EPIC-009: hidden flag (internal debug tool).
125
159
  "debug-bundle": {
126
160
  type: "boolean",
127
161
  description: "Emit a redacted diagnostic bundle (config + store health) for bug reports",
128
162
  default: false
129
163
  },
130
- // rc.7 T11: skip the safety confirm before mutations. Required for any
131
- // non-tty invocation that wants to run --fix-knowledge without setting
132
- // FABRIC_NONINTERACTIVE=1 in the environment.
164
+ // EPIC-009: hidden flag (CI automation).
133
165
  yes: {
134
166
  type: "boolean",
135
167
  description: t("cli.doctor.args.yes.description"),
136
168
  default: false
137
169
  },
138
- // rc.35 TASK-12 (P0-11): expose maintainer-audience actionHints. By
139
- // default the renderer folds remediation strings that target Fabric
140
- // contributors (edit `packages/cli/templates/...`, interpret G1-G5
141
- // cite-goodhart codes, etc.) since npm end users have no actionable
142
- // lever for them. --verbose shows them.
170
+ // EPIC-009: hidden flag (advanced output).
143
171
  verbose: {
144
172
  type: "boolean",
145
173
  description: t("cli.doctor.args.verbose.description"),
146
174
  default: false
147
175
  },
148
- // rc.20 TASK-05: cite policy adherence report (read-only). Skips standard
149
- // inspections entirely — different output surface. Mutually exclusive
150
- // with --fix / --fix-knowledge (enforced in run()).
176
+ // EPIC-009: hidden flags (report surfaces).
151
177
  "cite-coverage": {
152
178
  type: "boolean",
153
179
  description: t("cli.doctor.args.cite-coverage.description"),
@@ -162,20 +188,14 @@ var doctorCommand = defineCommand({
162
188
  type: "string",
163
189
  description: t("cli.doctor.args.client.description"),
164
190
  default: "all",
165
- valueHint: "cc|codex|cursor|all"
191
+ valueHint: "cc|codex|all"
166
192
  },
167
- // v2.0.0-rc.24 TASK-10: --layer filter for the cite contract audit. Pairs
168
- // with --cite-coverage. Validated against {'team','personal','all'} at
169
- // command entry; rejects 'both' (rc.20 plan-context vocabulary) explicitly.
170
193
  layer: {
171
194
  type: "string",
172
195
  description: t("cli.doctor.args.layer.description"),
173
196
  default: "all",
174
197
  valueHint: "team|personal|all"
175
198
  },
176
- // rc.23 TASK-007 (a-C2): description-grade back-fill flag set. Read-side
177
- // by default; `--auto` flips the writer arm on. Mutually exclusive with
178
- // --fix / --fix-knowledge / --cite-coverage (different mutation surfaces).
179
199
  "enrich-descriptions": {
180
200
  type: "boolean",
181
201
  description: t("cli.doctor.args.enrich-descriptions.description"),
@@ -191,23 +211,31 @@ var doctorCommand = defineCommand({
191
211
  description: t("cli.doctor.args.dry-run.description"),
192
212
  default: false
193
213
  },
194
- // v2.0.0-rc.25 TASK-10: --archive-history flag (parallel to rc.20
195
- // --cite-coverage). Read-only; reads session_archive_attempted events
196
- // and renders a per-session table. Pairs with the shared `--since` flag.
197
214
  "archive-history": {
198
215
  type: "boolean",
199
216
  description: t("cli.doctor.args.archive-history.description"),
200
217
  default: false
201
218
  },
202
- // rc.37 NEW-33: unified history view across archive / fix / all surfaces.
203
- // Mode = `archive | fix | all` (the `archive` mode delegates to the
204
- // existing runDoctorArchiveHistory; `fix` aggregates doctor_run events;
205
- // `all` rolls up both into a per-day count table). Read-only; mutex
206
- // with the mutation arms.
207
219
  history: {
208
220
  type: "string",
209
221
  description: t("cli.doctor.args.history.description"),
210
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
211
239
  }
212
240
  },
213
241
  async run({ args }) {
@@ -362,6 +390,22 @@ var doctorCommand = defineCommand({
362
390
  renderCiteCoverageReport(report2, args.json === true, dt);
363
391
  return;
364
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
+ }
365
409
  if (fixKnowledge && fix) {
366
410
  writeStderr(dt("cli.doctor.errors.fix-knowledge-fix-mutually-exclusive"));
367
411
  process.exitCode = 1;
@@ -369,33 +413,45 @@ var doctorCommand = defineCommand({
369
413
  }
370
414
  let fixKnowledgeReport = null;
371
415
  let fixReport = null;
416
+ let unboundProjectFix = null;
417
+ let rootConfigMigration = null;
372
418
  let report;
373
419
  if (fixKnowledge) {
374
420
  const preReport = await runDoctorReport(resolution.target);
375
421
  const plan = computeFixKnowledgePlan(preReport);
376
- const yesFlag = args.yes === true;
377
- const envBypass = process.env.FABRIC_NONINTERACTIVE === "1";
378
- if (plan.totalCount === 0) {
422
+ if (args["dry-run"] === true) {
423
+ if (plan.totalCount > 0) {
424
+ renderFixKnowledgePlan(plan);
425
+ }
426
+ report = preReport;
379
427
  } else {
380
- renderFixKnowledgePlan(plan);
381
- const decision = await resolveFixKnowledgeConsent({
382
- yesFlag,
383
- envBypass,
384
- plan
385
- });
386
- if (decision === "abort") {
387
- process.exitCode = 1;
388
- return;
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
+ }
389
442
  }
443
+ fixKnowledgeReport = await runDoctorFixKnowledge(resolution.target);
444
+ report = fixKnowledgeReport.report;
390
445
  }
391
- fixKnowledgeReport = await runDoctorFixKnowledge(resolution.target);
392
- report = fixKnowledgeReport.report;
393
446
  } else if (fix) {
394
447
  if (args["dry-run"] === true) {
395
448
  report = await runDoctorReport(resolution.target);
396
449
  } else {
450
+ rootConfigMigration = migrateRootConfig(resolution.target);
451
+ unboundProjectFix = await backfillUnboundProject(resolution.target);
397
452
  fixReport = await runDoctorFix(resolution.target);
398
453
  report = fixReport.report;
454
+ syncStoreAliasLinks();
399
455
  }
400
456
  } else {
401
457
  report = await runDoctorReport(resolution.target);
@@ -404,7 +460,12 @@ var doctorCommand = defineCommand({
404
460
  if (args.json === true) {
405
461
  writeStdout(
406
462
  JSON.stringify(
407
- { ...fixKnowledgeReport ?? fixReport ?? report, store_diagnostics: storeDiagnostics },
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
+ },
408
469
  null,
409
470
  2
410
471
  )
@@ -418,7 +479,20 @@ var doctorCommand = defineCommand({
418
479
  renderFixKnowledgeMutations(fixKnowledgeReport, dt);
419
480
  } else if (fixReport !== null) {
420
481
  writeStdout(fixReport.message);
421
- } else if (fix && args["dry-run"] === true) {
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) {
422
496
  writeStdout(dt("cli.doctor.fix-dry-run-banner"));
423
497
  }
424
498
  renderHumanReport(report, dt, args.verbose === true);
@@ -447,8 +521,11 @@ var doctorCommand = defineCommand({
447
521
  var doctor_default = doctorCommand;
448
522
  function renderHumanReport(report, dt, verbose) {
449
523
  writeStdout(`${renderStatus(report.status)} ${paint.ai("fabric doctor")} ${paint.human(report.summary.target)}`);
450
- renderTldrHeader(report);
524
+ renderTldrHeader(report, dt, verbose);
451
525
  for (const check of report.checks) {
526
+ if (!verbose && check.status === "ok") {
527
+ continue;
528
+ }
452
529
  writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
453
530
  }
454
531
  const opts = { verbose, dt };
@@ -471,7 +548,7 @@ function renderStoreDiagnostics(diagnostics) {
471
548
  writeStdout("");
472
549
  writeStdout(paint.ai("store health"));
473
550
  for (const diagnostic of diagnostics) {
474
- const mark = diagnostic.severity === "warn" ? symbol.warn : "[info]";
551
+ const mark = diagnostic.severity === "error" ? symbol.error : diagnostic.severity === "warn" ? symbol.warn : "[info]";
475
552
  const ref = diagnostic.ref === void 0 ? "" : ` [${diagnostic.ref}]`;
476
553
  writeStdout(`${mark}${ref} ${diagnostic.message}`);
477
554
  }
@@ -520,16 +597,16 @@ function writeIssueSection(title, issues, options) {
520
597
  }
521
598
  }
522
599
  }
523
- function renderTldrHeader(report) {
600
+ function renderTldrHeader(report, dt, verbose) {
524
601
  const ranked = [];
525
602
  for (const issue of report.fixable_errors) {
526
- ranked.push({ severity: "fixable", code: issue.code, message: issue.message });
603
+ ranked.push({ severity: "fixable", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
527
604
  }
528
605
  for (const issue of report.manual_errors) {
529
- ranked.push({ severity: "manual", code: issue.code, message: issue.message });
606
+ ranked.push({ severity: "manual", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
530
607
  }
531
608
  for (const issue of report.warnings) {
532
- ranked.push({ severity: "warn", code: issue.code, message: issue.message });
609
+ ranked.push({ severity: "warn", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
533
610
  }
534
611
  if (ranked.length === 0) {
535
612
  writeStdout(`${symbol.ok} TL;DR: all 48 checks green \u2014 nothing to fix.`);
@@ -543,6 +620,11 @@ function renderTldrHeader(report) {
543
620
  const marker = item.severity === "fixable" ? symbol.error : item.severity === "manual" ? symbol.error : symbol.warn;
544
621
  const truncated = item.message.length > 140 ? `${item.message.slice(0, 137)}...` : item.message;
545
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
+ }
546
628
  }
547
629
  }
548
630
  function renderStatus(status) {
@@ -646,7 +728,6 @@ async function resolveFixKnowledgeConsent(options) {
646
728
  var CITE_COVERAGE_CLIENT_FILTERS = /* @__PURE__ */ new Set([
647
729
  "cc",
648
730
  "codex",
649
- "cursor",
650
731
  "all"
651
732
  ]);
652
733
  function isValidClientFilter(input) {
@@ -689,10 +770,42 @@ function renderCiteCoverageReport(report, jsonMode, dt) {
689
770
  const complianceRate = report.metrics.cite_compliance_rate;
690
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)})`;
691
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}`);
692
776
  const uncorrelatable = report.metrics.uncorrelatable_edits ?? 0;
693
777
  if (uncorrelatable > 0) {
694
778
  lines.push(` ${dt("doctor.cite.metric.uncorrelatableEdits")}: ${uncorrelatable}`);
695
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
+ }
696
809
  if (report.per_client !== void 0 && Object.keys(report.per_client).length > 1) {
697
810
  lines.push("");
698
811
  lines.push(`### ${dt("doctor.cite.section.perClient")}`);
@@ -794,6 +907,38 @@ function appendContractSection(lines, report, dt) {
794
907
  );
795
908
  }
796
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
+ }
797
942
  function renderEnrichDescriptionsReport(report, dt) {
798
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}`;
799
944
  writeStdout(header);
@@ -913,8 +1058,39 @@ function formatTimestampForTable(iso) {
913
1058
  d.getUTCHours()
914
1059
  )}:${pad(d.getUTCMinutes())}`;
915
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
+
916
1090
  export {
917
- doctor_default as default,
918
1091
  doctorCommand,
919
- parseSinceDuration
1092
+ doctor_default,
1093
+ renderTldrHeader,
1094
+ parseSinceDuration,
1095
+ renderDoctorFilteredHelp
920
1096
  };
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/store/scope-explain.ts
4
+ import {
5
+ SCOPE_COORDINATE_PATTERN,
6
+ buildStoreResolveInput,
7
+ createStoreResolver,
8
+ resolveGlobalRoot
9
+ } from "@fenglimg/fabric-shared";
10
+ import { GenericConfigError } from "@fenglimg/fabric-shared/errors";
11
+ function buildResolveInput(projectRoot, globalRoot = resolveGlobalRoot()) {
12
+ return buildStoreResolveInput(projectRoot, globalRoot);
13
+ }
14
+ function scopeExplain(projectRoot, scope, globalRoot = resolveGlobalRoot()) {
15
+ if (!SCOPE_COORDINATE_PATTERN.test(scope)) {
16
+ throw new GenericConfigError(`invalid scope coordinate '${scope}'`, {
17
+ actionHint: "use ':'-joined lowercase [a-z0-9_-] segments, e.g. `team`, `personal`, `project:fabric-v2`, `org:acme:team:platform`",
18
+ details: { scope }
19
+ });
20
+ }
21
+ const input = buildResolveInput(projectRoot, globalRoot);
22
+ if (input === null) {
23
+ return null;
24
+ }
25
+ const resolver = createStoreResolver();
26
+ return {
27
+ scope,
28
+ readSet: resolver.resolveReadSet(input),
29
+ writeTarget: resolver.resolveWriteTarget(input, scope).target
30
+ };
31
+ }
32
+
33
+ export {
34
+ buildResolveInput,
35
+ scopeExplain
36
+ };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/store/global-config-io.ts
4
+ import {
5
+ resolveGlobalRoot,
6
+ globalConfigPath,
7
+ loadGlobalConfig,
8
+ saveGlobalConfig
9
+ } from "@fenglimg/fabric-shared";
10
+
11
+ export {
12
+ resolveGlobalRoot,
13
+ globalConfigPath,
14
+ loadGlobalConfig,
15
+ saveGlobalConfig
16
+ };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/i18n.ts
4
+ import {
5
+ createTranslator,
6
+ resolveGlobalLocale
7
+ } from "@fenglimg/fabric-shared";
8
+ var locale = resolveGlobalLocale();
9
+ var t = createTranslator(locale);
10
+ function refreshLocale() {
11
+ locale = resolveGlobalLocale();
12
+ t = createTranslator(locale);
13
+ }
14
+ function getProjectTranslator(_projectRoot = process.cwd()) {
15
+ return createTranslator(resolveGlobalLocale());
16
+ }
17
+ function getDoctorTranslator(projectRoot) {
18
+ return getProjectTranslator(projectRoot);
19
+ }
20
+
21
+ export {
22
+ t,
23
+ refreshLocale,
24
+ getProjectTranslator,
25
+ getDoctorTranslator
26
+ };
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/colors.ts
4
+ import pc from "picocolors";
5
+ import stringWidth from "string-width";
6
+ function isColorEnabled() {
7
+ if (process.env.NO_COLOR) {
8
+ return false;
9
+ }
10
+ const force = process.env.FORCE_COLOR;
11
+ if (force !== void 0) {
12
+ return force !== "0" && force.toLowerCase() !== "false";
13
+ }
14
+ return Boolean(process.stdout.isTTY);
15
+ }
16
+ function colorize(painter) {
17
+ return (value) => isColorEnabled() ? painter(value) : value;
18
+ }
19
+ var paint = {
20
+ success: colorize(pc.green),
21
+ warn: colorize(pc.yellow),
22
+ error: colorize(pc.red),
23
+ drift: colorize(pc.magenta),
24
+ ai: colorize(pc.blue),
25
+ human: colorize(pc.cyan),
26
+ muted: colorize(pc.dim)
27
+ };
28
+ var symbol = {
29
+ get ok() {
30
+ return isColorEnabled() ? paint.success("[ok] \u2713") : "[ok]";
31
+ },
32
+ get warn() {
33
+ return isColorEnabled() ? paint.warn("[warn] !") : "[warn]";
34
+ },
35
+ get error() {
36
+ return isColorEnabled() ? paint.error("[error] x") : "[error]";
37
+ }
38
+ };
39
+
40
+ export {
41
+ paint,
42
+ symbol
43
+ };
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildResolveInput
4
- } from "./chunk-L4Q55UC4.js";
4
+ } from "./chunk-EOT63RDH.js";
5
5
  import {
6
6
  loadProjectConfig
7
- } from "./chunk-LFIKMVY7.js";
7
+ } from "./chunk-QFIVFZRH.js";
8
8
  import {
9
9
  resolveGlobalRoot
10
- } from "./chunk-RYAFBNES.js";
10
+ } from "./chunk-FNHDQTPC.js";
11
11
 
12
12
  // src/store/bindings-io.ts
13
- import { writeBindingsSnapshot } from "@fenglimg/fabric-shared";
13
+ import {
14
+ resolveWorkspaceBindingId,
15
+ writeBindingsSnapshot
16
+ } from "@fenglimg/fabric-shared";
14
17
  var DEFAULT_WRITE_SCOPE = "team";
15
18
  function regenerateBindingsSnapshot(projectRoot, options) {
16
19
  const globalRoot = options.globalRoot ?? resolveGlobalRoot();
@@ -22,9 +25,14 @@ function regenerateBindingsSnapshot(projectRoot, options) {
22
25
  if (project?.project_id === void 0) {
23
26
  return null;
24
27
  }
28
+ const workspaceBindingId = resolveWorkspaceBindingId(project);
29
+ if (workspaceBindingId === void 0) {
30
+ return null;
31
+ }
25
32
  return writeBindingsSnapshot({
26
33
  globalRoot,
27
34
  projectId: project.project_id,
35
+ workspaceBindingId,
28
36
  resolveInput,
29
37
  writeScope: options.writeScope ?? DEFAULT_WRITE_SCOPE,
30
38
  now: options.now