@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.
- package/dist/{doctor-764NFF3X.js → doctor-EJDSEJSS.js} +7 -0
- package/dist/index.js +4 -4
- package/dist/{install-U7MGIJ2L.js → install-E6OEB3V2.js} +4 -1
- package/dist/{plan-context-hint-UQLRKGBZ.js → plan-context-hint-FC6P3WFE.js} +7 -28
- package/package.json +3 -3
- package/templates/hooks/knowledge-hint-narrow.cjs +18 -2
|
@@ -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-
|
|
15
|
-
doctor: () => import("./doctor-
|
|
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-
|
|
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.
|
|
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.
|
|
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
|
|
62
|
-
const
|
|
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.
|
|
72
|
-
maturity: item.
|
|
64
|
+
type: item.description.knowledge_type ?? "",
|
|
65
|
+
maturity: item.description.maturity ?? "",
|
|
73
66
|
summary: item.description.summary,
|
|
74
|
-
|
|
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 (
|
|
93
|
-
broad_count:
|
|
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.
|
|
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.
|
|
49
|
-
"@fenglimg/fabric-shared": "2.0.0-rc.
|
|
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
|
-
|
|
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.
|