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

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 (49) hide show
  1. package/dist/{chunk-PWLW3B57.js → chunk-2CY4BMTH.js} +5 -1
  2. package/dist/chunk-5LQIHYFC.js +64 -0
  3. package/dist/chunk-5ZUMLCD5.js +248 -0
  4. package/dist/{chunk-WWNXR34K.js → chunk-BO4XIZWZ.js} +8 -1
  5. package/dist/chunk-EOT63RDH.js +36 -0
  6. package/dist/{chunk-BATF4PEJ.js → chunk-F6ITRM7T.js} +4 -4
  7. package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
  8. package/dist/{chunk-MF3OTILQ.js → chunk-XC5RUHLK.js} +29 -8
  9. package/dist/chunk-XCBVSGCS.js +25 -0
  10. package/dist/{chunk-F46ORPOA.js → chunk-XHHCRDIR.js} +149 -7
  11. package/dist/{config-XJIPZNUP.js → config-VJMXCLXW.js} +3 -3
  12. package/dist/{doctor-QVNPHLJK.js → doctor-J4O3X54I.js} +154 -30
  13. package/dist/index.js +57 -16
  14. package/dist/{install-2HDO5FTQ.js → install-BULNDUIM.js} +241 -108
  15. package/dist/{metrics-ACEQFPDU.js → metrics-RER6NLFC.js} +22 -9
  16. package/dist/{onboard-coverage-MFCAEBDO.js → onboard-coverage-JWQWDZW7.js} +1 -1
  17. package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
  18. package/dist/{scope-explain-2F2R5URO.js → scope-explain-BWRWBCCP.js} +19 -5
  19. package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
  20. package/dist/store-66NK2FTQ.js +443 -0
  21. package/dist/sync-EA5HZMXM.js +395 -0
  22. package/dist/{uninstall-TAXSUSKH.js → uninstall-F75MPKQC.js} +61 -4
  23. package/dist/whoami-66YKY5DZ.js +47 -0
  24. package/package.json +3 -3
  25. package/templates/hooks/cite-policy-evict.cjs +412 -160
  26. package/templates/hooks/configs/claude-code.json +17 -2
  27. package/templates/hooks/configs/codex-hooks.json +14 -2
  28. package/templates/hooks/configs/cursor-hooks.json +14 -2
  29. package/templates/hooks/fabric-hint.cjs +247 -19
  30. package/templates/hooks/knowledge-hint-broad.cjs +176 -10
  31. package/templates/hooks/knowledge-hint-narrow.cjs +64 -5
  32. package/templates/hooks/lib/injection-log.cjs +91 -0
  33. package/templates/hooks/lib/state-store.cjs +30 -11
  34. package/templates/hooks/post-tooluse-mutation.cjs +285 -0
  35. package/templates/hooks/session-end-marker.cjs +140 -0
  36. package/templates/skills/fabric-archive/SKILL.md +7 -1
  37. package/templates/skills/fabric-audit/SKILL.md +53 -0
  38. package/templates/skills/fabric-connect/SKILL.md +48 -0
  39. package/templates/skills/fabric-review/SKILL.md +2 -0
  40. package/templates/skills/fabric-review/ref/cite-contract.md +56 -0
  41. package/templates/skills/fabric-store/SKILL.md +44 -0
  42. package/dist/chunk-HFQVXY6P.js +0 -86
  43. package/dist/chunk-L4Q55UC4.js +0 -52
  44. package/dist/chunk-LFIKMVY7.js +0 -27
  45. package/dist/chunk-RYAFBNES.js +0 -33
  46. package/dist/chunk-T5RPGCCM.js +0 -40
  47. package/dist/store-XTSE5TY6.js +0 -105
  48. package/dist/sync-BJCWDPNC.js +0 -245
  49. package/dist/whoami-B6AEMSEV.js +0 -31
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  deepMerge
4
- } from "./chunk-MF3OTILQ.js";
4
+ } from "./chunk-XC5RUHLK.js";
5
5
 
6
6
  // src/install/write-bootstrap-snapshot.ts
7
7
  import { existsSync, readFileSync } from "fs";
@@ -55,10 +55,15 @@ var SKILL_TEMPLATE_REL = "skills/fabric-archive/SKILL.md";
55
55
  var SKILL_REVIEW_TEMPLATE_REL = "skills/fabric-review/SKILL.md";
56
56
  var SKILL_IMPORT_TEMPLATE_REL = "skills/fabric-import/SKILL.md";
57
57
  var SKILL_SYNC_TEMPLATE_REL = "skills/fabric-sync/SKILL.md";
58
+ var SKILL_STORE_TEMPLATE_REL = "skills/fabric-store/SKILL.md";
59
+ var SKILL_AUDIT_TEMPLATE_REL = "skills/fabric-audit/SKILL.md";
60
+ var SKILL_CONNECT_TEMPLATE_REL = "skills/fabric-connect/SKILL.md";
58
61
  var HOOK_SCRIPT_TEMPLATE_REL = "hooks/fabric-hint.cjs";
59
62
  var HOOK_BROAD_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-broad.cjs";
60
63
  var HOOK_NARROW_SCRIPT_TEMPLATE_REL = "hooks/knowledge-hint-narrow.cjs";
61
64
  var HOOK_CITE_EVICT_SCRIPT_TEMPLATE_REL = "hooks/cite-policy-evict.cjs";
65
+ var HOOK_SESSION_END_SCRIPT_TEMPLATE_REL = "hooks/session-end-marker.cjs";
66
+ var HOOK_POST_TOOLUSE_SCRIPT_TEMPLATE_REL = "hooks/post-tooluse-mutation.cjs";
62
67
  var HOOK_LIB_TEMPLATE_DIR_REL = "hooks/lib";
63
68
  var CLAUDE_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/claude-code.json";
64
69
  var CODEX_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/codex-hooks.json";
@@ -81,6 +86,23 @@ var SKILL_DESTINATIONS = {
81
86
  fabricSync: [
82
87
  ".claude/skills/fabric-sync/SKILL.md",
83
88
  ".codex/skills/fabric-sync/SKILL.md"
89
+ ],
90
+ // v2.1 ADJ-NEWN-1/#4: fabric-store knowledge-store ops skill, same 2-client
91
+ // coverage as the sibling skills.
92
+ fabricStore: [
93
+ ".claude/skills/fabric-store/SKILL.md",
94
+ ".codex/skills/fabric-store/SKILL.md"
95
+ ],
96
+ // v2.2 SK1-audit (W2-T5): fabric-audit semantic-deprecation skill, same
97
+ // 2-client coverage as the sibling skills.
98
+ fabricAudit: [
99
+ ".claude/skills/fabric-audit/SKILL.md",
100
+ ".codex/skills/fabric-audit/SKILL.md"
101
+ ],
102
+ // v2.2 SK2-connect (W3-T2): fabric-connect knowledge-graph relation skill.
103
+ fabricConnect: [
104
+ ".claude/skills/fabric-connect/SKILL.md",
105
+ ".codex/skills/fabric-connect/SKILL.md"
84
106
  ]
85
107
  };
86
108
  var DEPRECATED_SKILL_DIRS = [
@@ -115,6 +137,18 @@ var HOOK_SCRIPT_DESTINATIONS = {
115
137
  ".claude/hooks/cite-policy-evict.cjs",
116
138
  ".codex/hooks/cite-policy-evict.cjs",
117
139
  ".cursor/hooks/cite-policy-evict.cjs"
140
+ ],
141
+ // lifecycle-refactor W2-T2: SessionEnd marker hook — all three clients.
142
+ sessionEndMarker: [
143
+ ".claude/hooks/session-end-marker.cjs",
144
+ ".codex/hooks/session-end-marker.cjs",
145
+ ".cursor/hooks/session-end-marker.cjs"
146
+ ],
147
+ // lifecycle-refactor W2-T3: PostToolUse mutation marker hook — all three.
148
+ postTooluseMutation: [
149
+ ".claude/hooks/post-tooluse-mutation.cjs",
150
+ ".codex/hooks/post-tooluse-mutation.cjs",
151
+ ".cursor/hooks/post-tooluse-mutation.cjs"
118
152
  ]
119
153
  };
120
154
  var HOOK_LIB_DESTINATIONS = [
@@ -128,25 +162,51 @@ var HOOK_CONFIG_TARGETS = {
128
162
  cursor: ".cursor/hooks.json"
129
163
  };
130
164
  var HOOK_CONFIG_ARRAY_PATHS = {
131
- claudeCode: ["hooks.Stop", "hooks.SessionStart", "hooks.PreToolUse"],
132
- codex: ["events.Stop", "events.SessionStart", "events.PreToolUse"],
133
- cursor: ["hooks.stop", "hooks.sessionStart", "hooks.preToolUse"]
165
+ // F2: "hooks.UserPromptSubmit" MUST be listed — the Claude Code template
166
+ // ships a UserPromptSubmit cite-policy hook, so without this path deepMerge
167
+ // array-REPLACEs (instead of append-with-dedupe) on re-install, silently
168
+ // clobbering any user-defined UserPromptSubmit hook.
169
+ // lifecycle-refactor W2-T2/T3: PostToolUse + SessionEnd arrays added so
170
+ // deepMerge append-with-dedupes them on re-install (omitting them would
171
+ // array-REPLACE, clobbering any user-defined entries in those slots).
172
+ claudeCode: [
173
+ "hooks.Stop",
174
+ "hooks.SessionStart",
175
+ "hooks.PreToolUse",
176
+ "hooks.UserPromptSubmit",
177
+ "hooks.PostToolUse",
178
+ "hooks.SessionEnd"
179
+ ],
180
+ codex: ["events.Stop", "events.SessionStart", "events.PreToolUse", "events.PostToolUse", "events.SessionEnd"],
181
+ cursor: ["hooks.stop", "hooks.sessionStart", "hooks.preToolUse", "hooks.postToolUse", "hooks.sessionEnd"]
134
182
  };
135
183
  var FABRIC_HOOK_COMMAND_PATHS = {
136
184
  claudeCode: {
137
185
  fabricHint: "${CLAUDE_PROJECT_DIR}/.claude/hooks/fabric-hint.cjs",
138
186
  knowledgeHintBroad: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-broad.cjs",
139
- knowledgeHintNarrow: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-narrow.cjs"
187
+ knowledgeHintNarrow: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-narrow.cjs",
188
+ // F3: the UserPromptSubmit cite-policy-evict hook must be a known fabric
189
+ // command so uninstall prunes it (matches the literal in claude-code.json).
190
+ citePolicyEvict: "${CLAUDE_PROJECT_DIR}/.claude/hooks/cite-policy-evict.cjs",
191
+ // lifecycle-refactor W2-T2/T3: SessionEnd + PostToolUse marker hooks.
192
+ sessionEndMarker: "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-end-marker.cjs",
193
+ postTooluseMutation: "${CLAUDE_PROJECT_DIR}/.claude/hooks/post-tooluse-mutation.cjs"
140
194
  },
141
195
  codex: {
142
196
  fabricHint: '"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs"',
143
197
  knowledgeHintBroad: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs"',
144
- knowledgeHintNarrow: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs"'
198
+ knowledgeHintNarrow: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs"',
199
+ citePolicyEvict: '"$(git rev-parse --show-toplevel)/.codex/hooks/cite-policy-evict.cjs"',
200
+ sessionEndMarker: '"$(git rev-parse --show-toplevel)/.codex/hooks/session-end-marker.cjs"',
201
+ postTooluseMutation: '"$(git rev-parse --show-toplevel)/.codex/hooks/post-tooluse-mutation.cjs"'
145
202
  },
146
203
  cursor: {
147
204
  fabricHint: ".cursor/hooks/fabric-hint.cjs",
148
205
  knowledgeHintBroad: ".cursor/hooks/knowledge-hint-broad.cjs",
149
- knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs"
206
+ knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs",
207
+ citePolicyEvict: ".cursor/hooks/cite-policy-evict.cjs",
208
+ sessionEndMarker: ".cursor/hooks/session-end-marker.cjs",
209
+ postTooluseMutation: ".cursor/hooks/post-tooluse-mutation.cjs"
150
210
  }
151
211
  };
152
212
  function readFabricLanguagePreference(projectRoot) {
@@ -257,6 +317,51 @@ async function installFabricSyncSkill(projectRoot, _options = {}) {
257
317
  }
258
318
  return results;
259
319
  }
320
+ async function installFabricStoreSkill(projectRoot, _options = {}) {
321
+ const source = await readTemplate(SKILL_STORE_TEMPLATE_REL);
322
+ validateSkillCanonicalSize(source, "fabric-store");
323
+ const targets = SKILL_DESTINATIONS.fabricStore.map((rel) => join2(projectRoot, rel));
324
+ const results = [];
325
+ for (const target of targets) {
326
+ const staleMsg = inspectStaleInstall(target, source);
327
+ const result = await copyTextIdempotent("skill-store", source, target);
328
+ if (staleMsg && result.status === "written") {
329
+ result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
330
+ }
331
+ results.push(result);
332
+ }
333
+ return results;
334
+ }
335
+ async function installFabricAuditSkill(projectRoot, _options = {}) {
336
+ const source = await readTemplate(SKILL_AUDIT_TEMPLATE_REL);
337
+ validateSkillCanonicalSize(source, "fabric-audit");
338
+ const targets = SKILL_DESTINATIONS.fabricAudit.map((rel) => join2(projectRoot, rel));
339
+ const results = [];
340
+ for (const target of targets) {
341
+ const staleMsg = inspectStaleInstall(target, source);
342
+ const result = await copyTextIdempotent("skill-audit", source, target);
343
+ if (staleMsg && result.status === "written") {
344
+ result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
345
+ }
346
+ results.push(result);
347
+ }
348
+ return results;
349
+ }
350
+ async function installFabricConnectSkill(projectRoot, _options = {}) {
351
+ const source = await readTemplate(SKILL_CONNECT_TEMPLATE_REL);
352
+ validateSkillCanonicalSize(source, "fabric-connect");
353
+ const targets = SKILL_DESTINATIONS.fabricConnect.map((rel) => join2(projectRoot, rel));
354
+ const results = [];
355
+ for (const target of targets) {
356
+ const staleMsg = inspectStaleInstall(target, source);
357
+ const result = await copyTextIdempotent("skill-connect", source, target);
358
+ if (staleMsg && result.status === "written") {
359
+ result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
360
+ }
361
+ results.push(result);
362
+ }
363
+ return results;
364
+ }
260
365
  async function cleanupDeprecatedSkills(projectRoot) {
261
366
  const results = [];
262
367
  for (const rel of DEPRECATED_SKILL_DIRS) {
@@ -443,6 +548,38 @@ async function installCitePolicyEvictHook(projectRoot, _options = {}) {
443
548
  }
444
549
  return results;
445
550
  }
551
+ async function installSessionEndMarkerHook(projectRoot, _options = {}) {
552
+ const source = await readTemplate(HOOK_SESSION_END_SCRIPT_TEMPLATE_REL);
553
+ const targets = HOOK_SCRIPT_DESTINATIONS.sessionEndMarker.map((rel) => join2(projectRoot, rel));
554
+ const results = [];
555
+ for (const target of targets) {
556
+ const result = await copyTextIdempotent("hook-session-end-script", source, target);
557
+ if (result.status === "written" && process.platform !== "win32") {
558
+ try {
559
+ chmodSync(target, 493);
560
+ } catch {
561
+ }
562
+ }
563
+ results.push(result);
564
+ }
565
+ return results;
566
+ }
567
+ async function installPostTooluseMutationHook(projectRoot, _options = {}) {
568
+ const source = await readTemplate(HOOK_POST_TOOLUSE_SCRIPT_TEMPLATE_REL);
569
+ const targets = HOOK_SCRIPT_DESTINATIONS.postTooluseMutation.map((rel) => join2(projectRoot, rel));
570
+ const results = [];
571
+ for (const target of targets) {
572
+ const result = await copyTextIdempotent("hook-post-tooluse-script", source, target);
573
+ if (result.status === "written" && process.platform !== "win32") {
574
+ try {
575
+ chmodSync(target, 493);
576
+ } catch {
577
+ }
578
+ }
579
+ results.push(result);
580
+ }
581
+ return results;
582
+ }
446
583
  async function installHookLibs(projectRoot, _options = {}) {
447
584
  const libTemplateDir = findTemplatePath(HOOK_LIB_TEMPLATE_DIR_REL);
448
585
  let libFiles;
@@ -887,12 +1024,17 @@ export {
887
1024
  installFabricReviewSkill,
888
1025
  installFabricImportSkill,
889
1026
  installFabricSyncSkill,
1027
+ installFabricStoreSkill,
1028
+ installFabricAuditSkill,
1029
+ installFabricConnectSkill,
890
1030
  cleanupDeprecatedSkills,
891
1031
  installSharedSkillLib,
892
1032
  installArchiveHintHook,
893
1033
  installKnowledgeHintBroadHook,
894
1034
  installKnowledgeHintNarrowHook,
895
1035
  installCitePolicyEvictHook,
1036
+ installSessionEndMarkerHook,
1037
+ installPostTooluseMutationHook,
896
1038
  installHookLibs,
897
1039
  mergeClaudeCodeHookConfig,
898
1040
  mergeCodexHookConfig,
@@ -3,9 +3,9 @@ import {
3
3
  configCmd,
4
4
  config_default,
5
5
  installMcpClients
6
- } from "./chunk-BATF4PEJ.js";
7
- import "./chunk-MF3OTILQ.js";
8
- import "./chunk-PWLW3B57.js";
6
+ } from "./chunk-F6ITRM7T.js";
7
+ import "./chunk-XC5RUHLK.js";
8
+ import "./chunk-2CY4BMTH.js";
9
9
  export {
10
10
  configCmd,
11
11
  config_default as default,
@@ -2,24 +2,25 @@
2
2
  import {
3
3
  paint,
4
4
  symbol
5
- } from "./chunk-WWNXR34K.js";
5
+ } from "./chunk-BO4XIZWZ.js";
6
6
  import {
7
7
  resolveDevMode
8
8
  } from "./chunk-COI5VDFU.js";
9
9
  import {
10
10
  getDoctorTranslator,
11
11
  t
12
- } from "./chunk-PWLW3B57.js";
12
+ } from "./chunk-2CY4BMTH.js";
13
13
  import {
14
- missingRequiredStores
15
- } from "./chunk-HFQVXY6P.js";
16
- import {
17
- loadProjectConfig
18
- } from "./chunk-LFIKMVY7.js";
14
+ detectAliasLinkDrift,
15
+ missingRequiredStores,
16
+ syncStoreAliasLinks,
17
+ unboundAvailableStores
18
+ } from "./chunk-5ZUMLCD5.js";
19
19
  import {
20
20
  loadGlobalConfig,
21
+ loadProjectConfig,
21
22
  resolveGlobalRoot
22
- } from "./chunk-RYAFBNES.js";
23
+ } from "./chunk-XCBVSGCS.js";
23
24
 
24
25
  // src/commands/doctor.ts
25
26
  import { confirm, isCancel } from "@clack/prompts";
@@ -32,7 +33,8 @@ import {
32
33
  runDoctorCiteCoverage,
33
34
  runDoctorFix,
34
35
  runDoctorHistoryAll,
35
- runDoctorReport
36
+ runDoctorReport,
37
+ runDoctorConflictLint
36
38
  } from "@fenglimg/fabric-server";
37
39
 
38
40
  // src/store/doctor-checks.ts
@@ -57,6 +59,23 @@ function storeDoctorChecks(projectRoot, globalRoot = resolveGlobalRoot()) {
57
59
  message: `required store '${missing.id}' is not mounted; run \`fabric store add\``
58
60
  });
59
61
  }
62
+ for (const store of unboundAvailableStores(projectRoot, globalRoot)) {
63
+ diagnostics.push({
64
+ code: "unbound_available_store",
65
+ severity: "info",
66
+ ref: store.alias,
67
+ 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)`
68
+ });
69
+ }
70
+ const aliasDrift = detectAliasLinkDrift(globalRoot);
71
+ if (aliasDrift.length > 0) {
72
+ diagnostics.push({
73
+ code: "store_alias_link_drift",
74
+ severity: "info",
75
+ ref: aliasDrift.join(", "),
76
+ message: `by-alias readability link(s) out of sync for ${aliasDrift.join(", ")}; run \`fabric doctor --fix\` to repair ~/.fabric/stores/by-alias/`
77
+ });
78
+ }
60
79
  for (const store of global.stores) {
61
80
  if (store.remote === void 0 && store.personal !== true) {
62
81
  diagnostics.push({
@@ -208,6 +227,17 @@ var doctorCommand = defineCommand({
208
227
  type: "string",
209
228
  description: t("cli.doctor.args.history.description"),
210
229
  valueHint: "archive|fix|all"
230
+ },
231
+ // v2.1 ④ conflict-detection (P4): knowledge-conflict lint surface.
232
+ "lint-conflicts": {
233
+ type: "boolean",
234
+ description: t("cli.doctor.args.lint-conflicts.description"),
235
+ default: false
236
+ },
237
+ deep: {
238
+ type: "boolean",
239
+ description: t("cli.doctor.args.deep.description"),
240
+ default: false
211
241
  }
212
242
  },
213
243
  async run({ args }) {
@@ -362,6 +392,22 @@ var doctorCommand = defineCommand({
362
392
  renderCiteCoverageReport(report2, args.json === true, dt);
363
393
  return;
364
394
  }
395
+ if (args["lint-conflicts"] === true) {
396
+ if (fix || fixKnowledge || citeCoverage) {
397
+ writeStderr(dt("cli.doctor.errors.lint-conflicts-mutex"));
398
+ process.exitCode = 1;
399
+ return;
400
+ }
401
+ const report2 = await runDoctorConflictLint(resolution.target, {
402
+ deep: args.deep === true
403
+ });
404
+ if (args.json === true) {
405
+ writeStdout(JSON.stringify(report2, null, 2));
406
+ } else {
407
+ renderConflictLintReport(report2, args.deep === true, dt);
408
+ }
409
+ return;
410
+ }
365
411
  if (fixKnowledge && fix) {
366
412
  writeStderr(dt("cli.doctor.errors.fix-knowledge-fix-mutually-exclusive"));
367
413
  process.exitCode = 1;
@@ -373,29 +419,37 @@ var doctorCommand = defineCommand({
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 {
397
450
  fixReport = await runDoctorFix(resolution.target);
398
451
  report = fixReport.report;
452
+ syncStoreAliasLinks();
399
453
  }
400
454
  } else {
401
455
  report = await runDoctorReport(resolution.target);
@@ -418,7 +472,7 @@ var doctorCommand = defineCommand({
418
472
  renderFixKnowledgeMutations(fixKnowledgeReport, dt);
419
473
  } else if (fixReport !== null) {
420
474
  writeStdout(fixReport.message);
421
- } else if (fix && args["dry-run"] === true) {
475
+ } else if ((fix || fixKnowledge) && args["dry-run"] === true) {
422
476
  writeStdout(dt("cli.doctor.fix-dry-run-banner"));
423
477
  }
424
478
  renderHumanReport(report, dt, args.verbose === true);
@@ -447,7 +501,7 @@ var doctorCommand = defineCommand({
447
501
  var doctor_default = doctorCommand;
448
502
  function renderHumanReport(report, dt, verbose) {
449
503
  writeStdout(`${renderStatus(report.status)} ${paint.ai("fabric doctor")} ${paint.human(report.summary.target)}`);
450
- renderTldrHeader(report);
504
+ renderTldrHeader(report, dt, verbose);
451
505
  for (const check of report.checks) {
452
506
  writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
453
507
  }
@@ -520,16 +574,16 @@ function writeIssueSection(title, issues, options) {
520
574
  }
521
575
  }
522
576
  }
523
- function renderTldrHeader(report) {
577
+ function renderTldrHeader(report, dt, verbose) {
524
578
  const ranked = [];
525
579
  for (const issue of report.fixable_errors) {
526
- ranked.push({ severity: "fixable", code: issue.code, message: issue.message });
580
+ ranked.push({ severity: "fixable", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
527
581
  }
528
582
  for (const issue of report.manual_errors) {
529
- ranked.push({ severity: "manual", code: issue.code, message: issue.message });
583
+ ranked.push({ severity: "manual", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
530
584
  }
531
585
  for (const issue of report.warnings) {
532
- ranked.push({ severity: "warn", code: issue.code, message: issue.message });
586
+ ranked.push({ severity: "warn", code: issue.code, message: issue.message, actionHint: issue.actionHint, audience: issue.audience });
533
587
  }
534
588
  if (ranked.length === 0) {
535
589
  writeStdout(`${symbol.ok} TL;DR: all 48 checks green \u2014 nothing to fix.`);
@@ -543,6 +597,11 @@ function renderTldrHeader(report) {
543
597
  const marker = item.severity === "fixable" ? symbol.error : item.severity === "manual" ? symbol.error : symbol.warn;
544
598
  const truncated = item.message.length > 140 ? `${item.message.slice(0, 137)}...` : item.message;
545
599
  writeStdout(` ${marker} ${item.code}: ${truncated}`);
600
+ if (item.actionHint !== void 0 && item.actionHint.length > 0) {
601
+ writeStdout(
602
+ item.audience === "maintainer" && !verbose ? ` \u2192 ${dt("doctor.maintainer-hint-folded")}` : ` \u2192 ${item.actionHint}`
603
+ );
604
+ }
546
605
  }
547
606
  }
548
607
  function renderStatus(status) {
@@ -689,10 +748,42 @@ function renderCiteCoverageReport(report, jsonMode, dt) {
689
748
  const complianceRate = report.metrics.cite_compliance_rate;
690
749
  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
750
  lines.push(` ${dt("doctor.cite.metric.complianceRate")}: ${complianceStr}`);
751
+ const recallRate = report.metrics.recall_coverage_rate;
752
+ 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})`;
753
+ lines.push(` ${dt("doctor.cite.metric.recallCoverage")}: ${recallStr}`);
692
754
  const uncorrelatable = report.metrics.uncorrelatable_edits ?? 0;
693
755
  if (uncorrelatable > 0) {
694
756
  lines.push(` ${dt("doctor.cite.metric.uncorrelatableEdits")}: ${uncorrelatable}`);
695
757
  }
758
+ if (report.metrics.exposed_and_mutated !== void 0) {
759
+ lines.push(
760
+ ` ${dt("doctor.cite.metric.exposedAndMutated")}: ${report.metrics.exposed_and_mutated.count}`
761
+ );
762
+ }
763
+ if (report.metrics.mutations_observed !== void 0) {
764
+ lines.push(
765
+ ` ${dt("doctor.cite.metric.mutationsObserved")}: ${report.metrics.mutations_observed.count}`
766
+ );
767
+ }
768
+ if (report.metrics.mutation_pool !== void 0) {
769
+ lines.push(
770
+ ` ${dt("doctor.cite.metric.mutationPool")}: ${report.metrics.mutation_pool.attributed} / ${report.metrics.mutation_pool.unattributed_workspace_dirty} (attributed / unattributed_workspace_dirty)`
771
+ );
772
+ }
773
+ if (report.metrics.sessions_closed !== void 0) {
774
+ lines.push(
775
+ ` ${dt("doctor.cite.metric.sessionsClosed")}: ${report.metrics.sessions_closed.count}`
776
+ );
777
+ }
778
+ if (report.metrics.by_store !== void 0) {
779
+ const storeKeys = Object.keys(report.metrics.by_store).sort();
780
+ if (storeKeys.length > 0) {
781
+ lines.push(` ${dt("doctor.cite.metric.byStore")}:`);
782
+ for (const store of storeKeys) {
783
+ lines.push(` ${store}: ${report.metrics.by_store[store].qualifying_cites}`);
784
+ }
785
+ }
786
+ }
696
787
  if (report.per_client !== void 0 && Object.keys(report.per_client).length > 1) {
697
788
  lines.push("");
698
789
  lines.push(`### ${dt("doctor.cite.section.perClient")}`);
@@ -794,6 +885,38 @@ function appendContractSection(lines, report, dt) {
794
885
  );
795
886
  }
796
887
  }
888
+ function renderConflictLintReport(report, deepRequested, dt) {
889
+ const lines = [];
890
+ lines.push(dt("doctor.conflict.header"));
891
+ lines.push("");
892
+ if (report.candidate_count === 0) {
893
+ lines.push(` ${symbol.ok} ${dt("doctor.conflict.none")}`);
894
+ writeStdout(lines.join("\n"));
895
+ return;
896
+ }
897
+ lines.push(
898
+ ` ${dt("doctor.conflict.summary", {
899
+ candidates: String(report.candidate_count),
900
+ conflicts: String(report.conflict_count),
901
+ threshold: report.threshold.toFixed(2)
902
+ })}`
903
+ );
904
+ if (deepRequested && !report.deep) {
905
+ lines.push(` ${symbol.warn} ${dt("doctor.conflict.deep_no_judge")}`);
906
+ }
907
+ lines.push("");
908
+ for (const pair of report.pairs) {
909
+ const sym = pair.verdict === "conflict" ? symbol.error : symbol.warn;
910
+ const verdictLabel = dt(`doctor.conflict.verdict.${pair.verdict}`);
911
+ const pct = `${(pair.similarity * 100).toFixed(0)}%`;
912
+ let line = ` ${sym} [${pair.a} \u2194 ${pair.b}] (${pair.knowledge_type}/${pair.layer}) ${pct} \u2014 ${verdictLabel}`;
913
+ if (pair.rationale !== void 0 && pair.rationale.length > 0) {
914
+ line += `: ${pair.rationale}`;
915
+ }
916
+ lines.push(line);
917
+ }
918
+ writeStdout(lines.join("\n"));
919
+ }
797
920
  function renderEnrichDescriptionsReport(report, dt) {
798
921
  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
922
  writeStdout(header);
@@ -916,5 +1039,6 @@ function formatTimestampForTable(iso) {
916
1039
  export {
917
1040
  doctor_default as default,
918
1041
  doctorCommand,
919
- parseSinceDuration
1042
+ parseSinceDuration,
1043
+ renderTldrHeader
920
1044
  };
package/dist/index.js CHANGED
@@ -1,47 +1,88 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  t
4
- } from "./chunk-PWLW3B57.js";
4
+ } from "./chunk-2CY4BMTH.js";
5
5
 
6
6
  // src/index.ts
7
7
  import { realpathSync } from "fs";
8
8
  import { resolve } from "path";
9
9
  import { fileURLToPath } from "url";
10
- import { defineCommand, runMain } from "citty";
10
+ import { defineCommand, runCommand, runMain } from "citty";
11
11
 
12
12
  // src/commands/index.ts
13
13
  var allCommands = {
14
- install: () => import("./install-2HDO5FTQ.js").then((module) => module.default),
14
+ install: () => import("./install-BULNDUIM.js").then((module) => module.default),
15
15
  // v2.1.0-rc.1 P3: multi-store lifecycle command group (list/add/remove/explain).
16
- store: () => import("./store-XTSE5TY6.js").then((module) => module.default),
16
+ store: () => import("./store-66NK2FTQ.js").then((module) => module.default),
17
17
  // v2.1.0-rc.1 P3 (S9/S17/S37): multi-store pull --rebase + push, conflict resume.
18
- sync: () => import("./sync-BJCWDPNC.js").then((module) => module.default),
18
+ sync: () => import("./sync-EA5HZMXM.js").then((module) => module.default),
19
19
  // v2.1.0-rc.1 P3 (F5): read-only identity/status info commands.
20
- whoami: () => import("./whoami-B6AEMSEV.js").then((module) => module.default),
21
- status: () => import("./status-GLQWLWH6.js").then((module) => module.default),
22
- "scope-explain": () => import("./scope-explain-2F2R5URO.js").then((module) => module.default),
23
- doctor: () => import("./doctor-QVNPHLJK.js").then((module) => module.default),
24
- uninstall: () => import("./uninstall-TAXSUSKH.js").then((module) => module.default),
25
- config: () => import("./config-XJIPZNUP.js").then((module) => module.default),
26
- "plan-context-hint": () => import("./plan-context-hint-FC6P3WFE.js").then((module) => module.default),
20
+ whoami: () => import("./whoami-66YKY5DZ.js").then((module) => module.default),
21
+ status: () => import("./status-PANEGKU2.js").then((module) => module.default),
22
+ "scope-explain": () => import("./scope-explain-BWRWBCCP.js").then((module) => module.default),
23
+ doctor: () => import("./doctor-J4O3X54I.js").then((module) => module.default),
24
+ uninstall: () => import("./uninstall-F75MPKQC.js").then((module) => module.default),
25
+ config: () => import("./config-VJMXCLXW.js").then((module) => module.default),
26
+ "plan-context-hint": () => import("./plan-context-hint-CHVZGOZ5.js").then((module) => module.default),
27
27
  // v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
28
28
  // fabric-archive Skill's first-run phase to detect unclaimed slots.
29
- "onboard-coverage": () => import("./onboard-coverage-MFCAEBDO.js").then((module) => module.default),
29
+ "onboard-coverage": () => import("./onboard-coverage-JWQWDZW7.js").then((module) => module.default),
30
30
  // v2.0.0-rc.37 NEW-34: text dashboard over .fabric/metrics.jsonl.
31
- metrics: () => import("./metrics-ACEQFPDU.js").then((module) => module.default)
31
+ metrics: () => import("./metrics-RER6NLFC.js").then((module) => module.default)
32
32
  };
33
33
 
34
+ // src/lib/error-render.ts
35
+ function hasActionHint(err) {
36
+ if (err === null || typeof err !== "object") return false;
37
+ const candidate = err;
38
+ return typeof candidate.message === "string" && candidate.message.length > 0 && typeof candidate.actionHint === "string" && candidate.actionHint.length > 0;
39
+ }
40
+ function renderFabricError(err, stream = process.stderr) {
41
+ stream.write(`${err.message}
42
+ `);
43
+ stream.write(` -> ${err.actionHint}
44
+ `);
45
+ }
46
+ function renderTopLevelError(err, stream = process.stderr) {
47
+ if (hasActionHint(err)) {
48
+ renderFabricError(err, stream);
49
+ return "fabric-error";
50
+ }
51
+ return "other";
52
+ }
53
+
34
54
  // src/index.ts
35
55
  var main = defineCommand({
36
56
  meta: {
37
57
  name: "fabric",
38
- version: "2.1.0-rc.2",
58
+ version: "2.2.0-rc.3",
39
59
  description: t("cli.main.description")
40
60
  },
41
61
  subCommands: allCommands
42
62
  });
43
63
  async function run() {
44
- await runMain(main);
64
+ const rawArgs = process.argv.slice(2);
65
+ const wantsHelp = rawArgs.some((arg) => arg === "--help" || arg === "-h");
66
+ const wantsVersion = rawArgs.length === 1 && (rawArgs[0] === "--version" || rawArgs[0] === "-v");
67
+ if (wantsHelp || wantsVersion) {
68
+ await runMain(main, { rawArgs });
69
+ return;
70
+ }
71
+ try {
72
+ await runCommand(main, { rawArgs });
73
+ } catch (err) {
74
+ if (renderTopLevelError(err) === "fabric-error") {
75
+ process.exit(1);
76
+ }
77
+ const code = err !== null && typeof err === "object" ? err.code : void 0;
78
+ const isCittyUsageError = err instanceof Error && err.name === "CLIError" || typeof code === "string" && (code.startsWith("E_") || code.startsWith("EARG") || code === "EUSAGE");
79
+ if (isCittyUsageError) {
80
+ await runMain(main, { rawArgs });
81
+ return;
82
+ }
83
+ console.error(err, "\n");
84
+ process.exit(1);
85
+ }
45
86
  }
46
87
  var entrypoint = process.argv[1];
47
88
  var currentFilePath = fileURLToPath(import.meta.url);