@fenglimg/fabric-cli 2.2.0-rc.1 → 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 (83) hide show
  1. package/README.md +8 -5
  2. package/dist/chunk-27HK6H5Y.js +69 -0
  3. package/dist/{chunk-AOE6AYI7.js → chunk-2KBCTMID.js} +31 -8
  4. package/dist/chunk-3D7B2UAZ.js +149 -0
  5. package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
  6. package/dist/{plan-context-hint-FC6P3WFE.js → chunk-722JU5BP.js} +52 -12
  7. package/dist/{chunk-2R55HNVD.js → chunk-7ZDXBOOU.js} +234 -206
  8. package/dist/{doctor-YONYXDX6.js → chunk-E7HJUU34.js} +215 -52
  9. package/dist/chunk-EOT63RDH.js +36 -0
  10. package/dist/chunk-FNHDQTPC.js +16 -0
  11. package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
  12. package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
  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-XYRBZJDU.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 +133 -22
  22. package/dist/info-7FKBTMVO.js +139 -0
  23. package/dist/install-v2-RINEA24K.js +3279 -0
  24. package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
  25. package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
  26. package/dist/plan-context-hint-5TNGH3R4.js +12 -0
  27. package/dist/{scope-explain-CDIZESP5.js → scope-explain-HLJZ2M33.js} +17 -6
  28. package/dist/status-4R3TM4FJ.js +37 -0
  29. package/dist/store-HOCORVL3.js +563 -0
  30. package/dist/{sync-UJ4BBCZJ.js → sync-DT5UJMMR.js} +197 -30
  31. package/dist/{uninstall-C3QXKOO6.js → uninstall-IFN2KYBK.js} +97 -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 +477 -176
  39. package/templates/hooks/knowledge-hint-broad.cjs +577 -274
  40. package/templates/hooks/knowledge-hint-narrow.cjs +113 -73
  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/nudge-policy.cjs +117 -0
  46. package/templates/hooks/lib/state-store.cjs +60 -0
  47. package/templates/hooks/post-tooluse-mutation.cjs +386 -0
  48. package/templates/hooks/session-end-marker.cjs +140 -0
  49. package/templates/skills/fabric/SKILL.md +100 -0
  50. package/templates/skills/fabric-archive/SKILL.md +35 -24
  51. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  52. package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
  53. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
  54. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
  55. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  56. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
  57. package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
  58. package/templates/skills/fabric-audit/SKILL.md +13 -3
  59. package/templates/skills/fabric-connect/SKILL.md +3 -3
  60. package/templates/skills/fabric-import/SKILL.md +7 -7
  61. package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
  62. package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
  63. package/templates/skills/fabric-review/SKILL.md +14 -5
  64. package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
  65. package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
  66. package/templates/skills/fabric-review/ref/output-contract.md +1 -1
  67. package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
  68. package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
  69. package/templates/skills/fabric-store/SKILL.md +1 -1
  70. package/templates/skills/fabric-sync/SKILL.md +1 -1
  71. package/templates/skills/lib/shared-policy.md +2 -2
  72. package/dist/chunk-4R2CYEA4.js +0 -116
  73. package/dist/chunk-L4Q55UC4.js +0 -52
  74. package/dist/chunk-LFIKMVY7.js +0 -27
  75. package/dist/chunk-RYAFBNES.js +0 -33
  76. package/dist/chunk-T5RPGCCM.js +0 -40
  77. package/dist/install-74ANPCCP.js +0 -2737
  78. package/dist/status-GLQWLWH6.js +0 -23
  79. package/dist/store-XB3ADT65.js +0 -144
  80. package/dist/whoami-2MLO4Y37.js +0 -36
  81. package/templates/hooks/configs/cursor-hooks.json +0 -18
  82. package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
  83. 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-BO4XIZWZ.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
- missingRequiredStores
11
- } from "./chunk-4R2CYEA4.js";
10
+ paint,
11
+ symbol
12
+ } from "./chunk-NLNH64A3.js";
12
13
  import {
13
- getDoctorTranslator,
14
- t
15
- } from "./chunk-2CY4BMTH.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,6 +413,8 @@ 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);
@@ -401,8 +447,11 @@ var doctorCommand = defineCommand({
401
447
  if (args["dry-run"] === true) {
402
448
  report = await runDoctorReport(resolution.target);
403
449
  } else {
450
+ rootConfigMigration = migrateRootConfig(resolution.target);
451
+ unboundProjectFix = await backfillUnboundProject(resolution.target);
404
452
  fixReport = await runDoctorFix(resolution.target);
405
453
  report = fixReport.report;
454
+ syncStoreAliasLinks();
406
455
  }
407
456
  } else {
408
457
  report = await runDoctorReport(resolution.target);
@@ -411,7 +460,12 @@ var doctorCommand = defineCommand({
411
460
  if (args.json === true) {
412
461
  writeStdout(
413
462
  JSON.stringify(
414
- { ...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
+ },
415
469
  null,
416
470
  2
417
471
  )
@@ -425,6 +479,19 @@ var doctorCommand = defineCommand({
425
479
  renderFixKnowledgeMutations(fixKnowledgeReport, dt);
426
480
  } else if (fixReport !== null) {
427
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
+ }
428
495
  } else if ((fix || fixKnowledge) && args["dry-run"] === true) {
429
496
  writeStdout(dt("cli.doctor.fix-dry-run-banner"));
430
497
  }
@@ -456,6 +523,9 @@ function renderHumanReport(report, dt, verbose) {
456
523
  writeStdout(`${renderStatus(report.status)} ${paint.ai("fabric doctor")} ${paint.human(report.summary.target)}`);
457
524
  renderTldrHeader(report, dt, verbose);
458
525
  for (const check of report.checks) {
526
+ if (!verbose && check.status === "ok") {
527
+ continue;
528
+ }
459
529
  writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
460
530
  }
461
531
  const opts = { verbose, dt };
@@ -478,7 +548,7 @@ function renderStoreDiagnostics(diagnostics) {
478
548
  writeStdout("");
479
549
  writeStdout(paint.ai("store health"));
480
550
  for (const diagnostic of diagnostics) {
481
- const mark = diagnostic.severity === "warn" ? symbol.warn : "[info]";
551
+ const mark = diagnostic.severity === "error" ? symbol.error : diagnostic.severity === "warn" ? symbol.warn : "[info]";
482
552
  const ref = diagnostic.ref === void 0 ? "" : ` [${diagnostic.ref}]`;
483
553
  writeStdout(`${mark}${ref} ${diagnostic.message}`);
484
554
  }
@@ -658,7 +728,6 @@ async function resolveFixKnowledgeConsent(options) {
658
728
  var CITE_COVERAGE_CLIENT_FILTERS = /* @__PURE__ */ new Set([
659
729
  "cc",
660
730
  "codex",
661
- "cursor",
662
731
  "all"
663
732
  ]);
664
733
  function isValidClientFilter(input) {
@@ -701,10 +770,42 @@ function renderCiteCoverageReport(report, jsonMode, dt) {
701
770
  const complianceRate = report.metrics.cite_compliance_rate;
702
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)})`;
703
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}`);
704
776
  const uncorrelatable = report.metrics.uncorrelatable_edits ?? 0;
705
777
  if (uncorrelatable > 0) {
706
778
  lines.push(` ${dt("doctor.cite.metric.uncorrelatableEdits")}: ${uncorrelatable}`);
707
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
+ }
708
809
  if (report.per_client !== void 0 && Object.keys(report.per_client).length > 1) {
709
810
  lines.push("");
710
811
  lines.push(`### ${dt("doctor.cite.section.perClient")}`);
@@ -806,6 +907,38 @@ function appendContractSection(lines, report, dt) {
806
907
  );
807
908
  }
808
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
+ }
809
942
  function renderEnrichDescriptionsReport(report, dt) {
810
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}`;
811
944
  writeStdout(header);
@@ -925,9 +1058,39 @@ function formatTimestampForTable(iso) {
925
1058
  d.getUTCHours()
926
1059
  )}:${pad(d.getUTCMinutes())}`;
927
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
+
928
1090
  export {
929
- doctor_default as default,
930
1091
  doctorCommand,
1092
+ doctor_default,
1093
+ renderTldrHeader,
931
1094
  parseSinceDuration,
932
- renderTldrHeader
1095
+ renderDoctorFilteredHelp
933
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
+ };
@@ -3,13 +3,16 @@
3
3
  // src/i18n.ts
4
4
  import {
5
5
  createTranslator,
6
- detectNodeLocale,
7
- resolveFabricLocale
6
+ resolveGlobalLocale
8
7
  } from "@fenglimg/fabric-shared";
9
- var locale = detectNodeLocale();
8
+ var locale = resolveGlobalLocale();
10
9
  var t = createTranslator(locale);
11
- function getProjectTranslator(projectRoot = process.cwd()) {
12
- return createTranslator(resolveFabricLocale(projectRoot));
10
+ function refreshLocale() {
11
+ locale = resolveGlobalLocale();
12
+ t = createTranslator(locale);
13
+ }
14
+ function getProjectTranslator(_projectRoot = process.cwd()) {
15
+ return createTranslator(resolveGlobalLocale());
13
16
  }
14
17
  function getDoctorTranslator(projectRoot) {
15
18
  return getProjectTranslator(projectRoot);
@@ -17,6 +20,7 @@ function getDoctorTranslator(projectRoot) {
17
20
 
18
21
  export {
19
22
  t,
23
+ refreshLocale,
20
24
  getProjectTranslator,
21
25
  getDoctorTranslator
22
26
  };
@@ -11,7 +11,7 @@ function isColorEnabled() {
11
11
  if (force !== void 0) {
12
12
  return force !== "0" && force.toLowerCase() !== "false";
13
13
  }
14
- return Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
14
+ return Boolean(process.stdout.isTTY);
15
15
  }
16
16
  function colorize(painter) {
17
17
  return (value) => isColorEnabled() ? painter(value) : value;
@@ -27,30 +27,17 @@ var paint = {
27
27
  };
28
28
  var symbol = {
29
29
  get ok() {
30
- return isColorEnabled() ? paint.success("\u2713") : "[ok]";
30
+ return isColorEnabled() ? paint.success("[ok] \u2713") : "[ok]";
31
31
  },
32
32
  get warn() {
33
- return isColorEnabled() ? paint.warn("!") : "[warn]";
33
+ return isColorEnabled() ? paint.warn("[warn] !") : "[warn]";
34
34
  },
35
35
  get error() {
36
- return isColorEnabled() ? paint.error("x") : "[error]";
36
+ return isColorEnabled() ? paint.error("[error] x") : "[error]";
37
37
  }
38
38
  };
39
- function displayWidth(value) {
40
- return stringWidth(value);
41
- }
42
- function padEnd(value, width, char = " ") {
43
- const fill = char.length > 0 ? char : " ";
44
- let result = value;
45
- while (displayWidth(result) < width) {
46
- result += fill;
47
- }
48
- return result;
49
- }
50
39
 
51
40
  export {
52
41
  paint,
53
- symbol,
54
- displayWidth,
55
- padEnd
42
+ symbol
56
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
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/store/project-config-io.ts
4
+ import {
5
+ projectConfigPath,
6
+ loadProjectConfig,
7
+ saveProjectConfig
8
+ } from "@fenglimg/fabric-shared";
9
+
10
+ export {
11
+ loadProjectConfig,
12
+ saveProjectConfig
13
+ };