@fenglimg/fabric-cli 2.0.0-rc.37 → 2.0.0-rc.38

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.
@@ -576,6 +576,13 @@ function renderCiteCoverageReport(report, jsonMode, dt) {
576
576
  lines.push(` ${dt("doctor.cite.metric.recalledUnverified")}: ${report.metrics.recalled_unverified}`);
577
577
  lines.push(` ${dt("doctor.cite.metric.expectedButMissed")}: ${report.metrics.expected_but_missed}`);
578
578
  lines.push(` ${dt("doctor.cite.metric.totalTurns")}: ${report.metrics.total_turns}`);
579
+ const complianceRate = report.metrics.cite_compliance_rate;
580
+ 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)})`;
581
+ lines.push(` ${dt("doctor.cite.metric.complianceRate")}: ${complianceStr}`);
582
+ const uncorrelatable = report.metrics.uncorrelatable_edits ?? 0;
583
+ if (uncorrelatable > 0) {
584
+ lines.push(` ${dt("doctor.cite.metric.uncorrelatableEdits")}: ${uncorrelatable}`);
585
+ }
579
586
  if (report.per_client !== void 0 && Object.keys(report.per_client).length > 1) {
580
587
  lines.push("");
581
588
  lines.push(`### ${dt("doctor.cite.section.perClient")}`);
package/dist/index.js CHANGED
@@ -11,11 +11,11 @@ import { defineCommand, runMain } from "citty";
11
11
 
12
12
  // src/commands/index.ts
13
13
  var allCommands = {
14
- install: () => import("./install-U7MGIJ2L.js").then((module) => module.default),
15
- doctor: () => import("./doctor-764NFF3X.js").then((module) => module.default),
14
+ install: () => import("./install-E6OEB3V2.js").then((module) => module.default),
15
+ doctor: () => import("./doctor-EJDSEJSS.js").then((module) => module.default),
16
16
  uninstall: () => import("./uninstall-MH7ZIB6M.js").then((module) => module.default),
17
17
  config: () => import("./config-XJIPZNUP.js").then((module) => module.default),
18
- "plan-context-hint": () => import("./plan-context-hint-UQLRKGBZ.js").then((module) => module.default),
18
+ "plan-context-hint": () => import("./plan-context-hint-FC6P3WFE.js").then((module) => module.default),
19
19
  // v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
20
20
  // fabric-archive Skill's first-run phase to detect unclaimed slots.
21
21
  "onboard-coverage": () => import("./onboard-coverage-MFCAEBDO.js").then((module) => module.default),
@@ -27,7 +27,7 @@ var allCommands = {
27
27
  var main = defineCommand({
28
28
  meta: {
29
29
  name: "fabric",
30
- version: "2.0.0-rc.37",
30
+ version: "2.0.0-rc.38",
31
31
  description: t("cli.main.description")
32
32
  },
33
33
  subCommands: allCommands
@@ -1350,7 +1350,7 @@ function readProjectName(target) {
1350
1350
  return basename(target);
1351
1351
  }
1352
1352
  function getCliVersion() {
1353
- return true ? "2.0.0-rc.37" : "unknown";
1353
+ return true ? "2.0.0-rc.38" : "unknown";
1354
1354
  }
1355
1355
  function sortRecord(record) {
1356
1356
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -1508,6 +1508,9 @@ async function runInitCommand(args) {
1508
1508
  }
1509
1509
  const result = await executeInitExecutionPlan(plan);
1510
1510
  if (!intent.options.planOnly) {
1511
+ console.log("");
1512
+ console.log(t("cli.install.next-steps"));
1513
+ console.log("");
1511
1514
  console.log(paint.muted("More: docs/surfaces.md explains when to use CLI vs Skill vs MCP."));
1512
1515
  }
1513
1516
  return result;
@@ -58,24 +58,13 @@ async function runPlanContextHint(opts) {
58
58
  const result = await planContext(resolution.target, {
59
59
  paths: targetPaths
60
60
  });
61
- const sharedIndex = result.shared.description_index;
62
- const narrowSource = all ? sharedIndex : (
63
- // Path mode: union of per-entry description_index across requested
64
- // paths, deduped by stable_id. This is identical to `shared` for L0/L1
65
- // entries (always included) and additionally captures L2 entries whose
66
- // scope_glob matches the requested path.
67
- dedupeByStableId(result.entries.flatMap((entry) => entry.description_index))
68
- );
69
- const entries = narrowSource.map((item) => ({
61
+ const candidates = result.candidates;
62
+ const entries = candidates.map((item) => ({
70
63
  id: item.stable_id,
71
- type: item.type ?? item.description.knowledge_type ?? "",
72
- maturity: item.maturity ?? item.description.maturity ?? "",
64
+ type: item.description.knowledge_type ?? "",
65
+ maturity: item.description.maturity ?? "",
73
66
  summary: item.description.summary,
74
- // v2.0.0-rc.27 TASK-002 (§2.5/§2.7): forward the server-side scope.
75
- // RuleDescriptionIndexItem already carries this field — knowledge-meta-
76
- // builder defaults to "broad" for entries without an explicit
77
- // relevance_scope frontmatter, so this read is total and never undefined.
78
- relevance_scope: item.relevance_scope ?? "broad"
67
+ relevance_scope: item.description.relevance_scope ?? "broad"
79
68
  }));
80
69
  let narrow_count = 0;
81
70
  let broad_only_count = 0;
@@ -89,8 +78,8 @@ async function runPlanContextHint(opts) {
89
78
  target_paths: targetPaths,
90
79
  entries,
91
80
  // Legacy field — preserved for v2 consumers that haven't migrated. Value
92
- // semantics unchanged from rc.18 (sharedIndex total).
93
- broad_count: sharedIndex.length,
81
+ // semantics unchanged from rc.18 (total candidate count).
82
+ broad_count: candidates.length,
94
83
  narrow_count,
95
84
  broad_only_count
96
85
  };
@@ -108,16 +97,6 @@ function parsePathsArg(raw) {
108
97
  }
109
98
  return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
110
99
  }
111
- function dedupeByStableId(items) {
112
- const seen = /* @__PURE__ */ new Set();
113
- const result = [];
114
- for (const item of items) {
115
- if (seen.has(item.stable_id)) continue;
116
- seen.add(item.stable_id);
117
- result.push(item);
118
- }
119
- return result;
120
- }
121
100
  export {
122
101
  plan_context_hint_default as default,
123
102
  planContextHintCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "2.0.0-rc.37",
3
+ "version": "2.0.0-rc.38",
4
4
  "description": "Fabric CLI — installs the MCP server + skills + hooks for Claude Code, Cursor, and Codex CLI; runs doctor / knowledge maintenance.",
5
5
  "license": "MIT",
6
6
  "author": "wangzhichao <fenglimg90@gmail.com>",
@@ -45,8 +45,8 @@
45
45
  "tree-sitter-javascript": "^0.25.0",
46
46
  "tree-sitter-typescript": "^0.23.2",
47
47
  "web-tree-sitter": "^0.26.8",
48
- "@fenglimg/fabric-server": "2.0.0-rc.37",
49
- "@fenglimg/fabric-shared": "2.0.0-rc.37"
48
+ "@fenglimg/fabric-server": "2.0.0-rc.38",
49
+ "@fenglimg/fabric-shared": "2.0.0-rc.38"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^22.15.0",
@@ -358,7 +358,7 @@ function extractPaths(toolInput) {
358
358
  * POSIX) is atomic at the OS level, so concurrent PreToolUse fires
359
359
  * from parallel sessions interleave cleanly without partial writes.
360
360
  */
361
- function appendEditIntentToLedger(projectRoot, now, paths, toolName) {
361
+ function appendEditIntentToLedger(projectRoot, now, paths, toolName, sessionId) {
362
362
  try {
363
363
  const fabricDir = join(projectRoot, EVENTS_LEDGER_DIR_REL);
364
364
  // No .fabric/ → project not initialised. Bail before any write.
@@ -383,12 +383,20 @@ function appendEditIntentToLedger(projectRoot, now, paths, toolName) {
383
383
  const tsMs = now instanceof Date ? now.getTime() : Number(now);
384
384
  const ledgerEntryId = `hook:${randomUUID()}`;
385
385
  const intent = typeof toolName === "string" && toolName.length > 0 ? toolName : "edit";
386
+ // rc.38 UX-8 (C): thread the REAL payload session_id (never the synthetic
387
+ // fallback) so doctor cite-coverage's expected_but_missed arm can correlate
388
+ // this edit against the same session's assistant_turn cite lines. Omitting
389
+ // it (the rc.35 oversight) left the correlation key undefined → missed
390
+ // permanently 0 → cite_compliance_rate structurally pinned at 100%.
391
+ const validSessionId =
392
+ typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
386
393
  const lines = pathList
387
394
  .map((p) => JSON.stringify({
388
395
  kind: "fabric-event",
389
396
  id: `event:${randomUUID()}`,
390
397
  ts: tsMs,
391
398
  schema_version: 1,
399
+ ...(validSessionId ? { session_id: validSessionId } : {}),
392
400
  event_type: "edit_intent_checked",
393
401
  path: p,
394
402
  compliant: true,
@@ -1249,7 +1257,15 @@ function main(env, stdio) {
1249
1257
  // events.jsonl ledger so doctor cite-coverage's editsTouched metric
1250
1258
  // sees actual edit signals. Best-effort — failure is swallowed inside
1251
1259
  // appendEditIntentToLedger and does not block the hook.
1252
- appendEditIntentToLedger(cwd, now, paths, toolName);
1260
+ // rc.38 UX-8 (C): pass the REAL payload session_id (not resolveSessionId,
1261
+ // which would substitute a synthetic per-process id that matches no
1262
+ // assistant_turn and would inflate expected_but_missed with false
1263
+ // positives under --client=all). null when the client omits session_id.
1264
+ const payloadSessionId =
1265
+ payload && typeof payload === "object" && typeof payload.session_id === "string"
1266
+ ? payload.session_id
1267
+ : null;
1268
+ appendEditIntentToLedger(cwd, now, paths, toolName, payloadSessionId);
1253
1269
  }
1254
1270
 
1255
1271
  // E2 path is conditional on a recognized tool + extractable paths.