@fenglimg/fabric-server 2.0.0-rc.13 → 2.0.0-rc.21
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.
|
@@ -1281,7 +1281,12 @@ import {
|
|
|
1281
1281
|
AgentsMetaCountersSchema,
|
|
1282
1282
|
forensicReportSchema,
|
|
1283
1283
|
parseKnowledgeId as parseKnowledgeId2,
|
|
1284
|
-
knowledgeTestIndexSchema as knowledgeTestIndexSchema2
|
|
1284
|
+
knowledgeTestIndexSchema as knowledgeTestIndexSchema2,
|
|
1285
|
+
LEGACY_KB_REGEX,
|
|
1286
|
+
BOOTSTRAP_CANONICAL,
|
|
1287
|
+
BOOTSTRAP_MARKER_BEGIN,
|
|
1288
|
+
BOOTSTRAP_MARKER_END,
|
|
1289
|
+
BOOTSTRAP_REGEX
|
|
1285
1290
|
} from "@fenglimg/fabric-shared";
|
|
1286
1291
|
import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
1287
1292
|
import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
@@ -1342,7 +1347,13 @@ var TARGET_FILE_PATHS = [
|
|
|
1342
1347
|
".fabric/agents.meta.json",
|
|
1343
1348
|
".fabric/.cache/knowledge-test.index.json",
|
|
1344
1349
|
".fabric/events.jsonl",
|
|
1345
|
-
".fabric/knowledge"
|
|
1350
|
+
".fabric/knowledge",
|
|
1351
|
+
// v2.0.0-rc.19 bootstrap-consolidation TASK-005: L1 canonical snapshot
|
|
1352
|
+
// (.fabric/AGENTS.md) and optional project-rules concat source
|
|
1353
|
+
// (.fabric/project-rules.md). Surfaced in summary.targetFiles so --json
|
|
1354
|
+
// consumers can confirm L1 presence at a glance.
|
|
1355
|
+
".fabric/AGENTS.md",
|
|
1356
|
+
".fabric/project-rules.md"
|
|
1346
1357
|
];
|
|
1347
1358
|
async function runDoctorReport(target) {
|
|
1348
1359
|
const projectRoot = normalizeTarget(target);
|
|
@@ -1352,12 +1363,24 @@ async function runDoctorReport(target) {
|
|
|
1352
1363
|
forensic,
|
|
1353
1364
|
meta,
|
|
1354
1365
|
eventLedger,
|
|
1355
|
-
knowledgeTestIndex
|
|
1366
|
+
knowledgeTestIndex,
|
|
1367
|
+
bootstrapMarkerMigration,
|
|
1368
|
+
l1BootstrapSnapshotDrift,
|
|
1369
|
+
l2ManagedBlockDrift
|
|
1356
1370
|
] = await Promise.all([
|
|
1357
1371
|
inspectForensic(projectRoot),
|
|
1358
1372
|
inspectMeta(projectRoot),
|
|
1359
1373
|
inspectEventLedger(projectRoot),
|
|
1360
|
-
inspectKnowledgeTestIndex(projectRoot)
|
|
1374
|
+
inspectKnowledgeTestIndex(projectRoot),
|
|
1375
|
+
// v2.0.0-rc.19 TASK-004: one-time fabric:knowledge-base → fabric:bootstrap
|
|
1376
|
+
// marker migration scan. Inspect runs in this Promise.all block to keep
|
|
1377
|
+
// performance parity with the other I/O-bound inspections.
|
|
1378
|
+
inspectBootstrapMarkerMigration(projectRoot),
|
|
1379
|
+
// v2.0.0-rc.19 TASK-005: L1 + L2 byte-level drift detection. Both are
|
|
1380
|
+
// I/O-bound (small file reads + buffer compare) so they live in the same
|
|
1381
|
+
// Promise.all block as the other bootstrap inspections.
|
|
1382
|
+
inspectL1BootstrapSnapshotDrift(projectRoot),
|
|
1383
|
+
inspectL2ManagedBlockDrift(projectRoot)
|
|
1361
1384
|
]);
|
|
1362
1385
|
const mcpConfigInWrongFile = inspectMcpConfigInWrongFile(projectRoot);
|
|
1363
1386
|
const metaManuallyDiverged = await inspectMetaManuallyDiverged(projectRoot);
|
|
@@ -1385,6 +1408,15 @@ async function runDoctorReport(target) {
|
|
|
1385
1408
|
const skillMdYamlInvalid = inspectSkillMdYamlInvalid(projectRoot);
|
|
1386
1409
|
const checks = [
|
|
1387
1410
|
createBootstrapAnchorCheck(bootstrapAnchor),
|
|
1411
|
+
// v2.0.0-rc.19 TASK-004: bootstrap marker migration check sits adjacent to
|
|
1412
|
+
// the anchor check — both are bootstrap-file invariants. fixable_error
|
|
1413
|
+
// when any of the four target paths still carries the legacy marker.
|
|
1414
|
+
createBootstrapMarkerMigrationCheck(bootstrapMarkerMigration),
|
|
1415
|
+
// v2.0.0-rc.19 TASK-005: L1 + L2 byte-level drift detection sit immediately
|
|
1416
|
+
// after the marker migration check. Order: anchor existence → migration →
|
|
1417
|
+
// L1 (canonical ↔ snapshot) → L2 (snapshot+rules ↔ three-end blocks).
|
|
1418
|
+
createL1BootstrapSnapshotDriftCheck(l1BootstrapSnapshotDrift),
|
|
1419
|
+
createL2ManagedBlockDriftCheck(l2ManagedBlockDrift),
|
|
1388
1420
|
createKnowledgeDirMissingCheck(knowledgeDirMissing),
|
|
1389
1421
|
createForensicCheck(forensic, framework.kind, entryPoints.length),
|
|
1390
1422
|
// v2.0: removed `createInitContextCheck` — `.fabric/init-context.json`
|
|
@@ -1494,6 +1526,33 @@ async function runDoctorFix(target) {
|
|
|
1494
1526
|
const projectRoot = normalizeTarget(target);
|
|
1495
1527
|
const before = await runDoctorReport(projectRoot);
|
|
1496
1528
|
const fixed = [];
|
|
1529
|
+
if (before.fixable_errors.some(
|
|
1530
|
+
(issue) => issue.code === "bootstrap_marker_migration_required"
|
|
1531
|
+
)) {
|
|
1532
|
+
const migrated = await migrateBootstrapMarkers(projectRoot);
|
|
1533
|
+
fixed.push(findIssue(before.fixable_errors, "bootstrap_marker_migration_required"));
|
|
1534
|
+
for (const path of migrated.paths) {
|
|
1535
|
+
await appendEventLedgerEvent(projectRoot, {
|
|
1536
|
+
event_type: "bootstrap_marker_migrated",
|
|
1537
|
+
path,
|
|
1538
|
+
migrated_count: migrated.countPerPath[path] ?? 1,
|
|
1539
|
+
legacy_marker: "fabric:knowledge-base",
|
|
1540
|
+
new_marker: "fabric:bootstrap",
|
|
1541
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1542
|
+
}).catch(() => {
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
1547
|
+
const snapshotPath = join5(projectRoot, ".fabric", "AGENTS.md");
|
|
1548
|
+
await ensureParentDirectory(snapshotPath);
|
|
1549
|
+
await atomicWriteText3(snapshotPath, BOOTSTRAP_CANONICAL);
|
|
1550
|
+
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
1551
|
+
}
|
|
1552
|
+
if (before.fixable_errors.some((issue) => issue.code === "managed_block_drift")) {
|
|
1553
|
+
await rewriteThreeEndManagedBlocks(projectRoot);
|
|
1554
|
+
fixed.push(findIssue(before.fixable_errors, "managed_block_drift"));
|
|
1555
|
+
}
|
|
1497
1556
|
if (before.fixable_errors.some((issue) => issue.code === "knowledge_dir_missing")) {
|
|
1498
1557
|
await ensureKnowledgeSubdirs(projectRoot);
|
|
1499
1558
|
fixed.push(findIssue(before.fixable_errors, "knowledge_dir_missing"));
|
|
@@ -2124,6 +2183,186 @@ function inspectBootstrapAnchor(projectRoot) {
|
|
|
2124
2183
|
hasClaudeMd: existsSync4(join5(projectRoot, "CLAUDE.md"))
|
|
2125
2184
|
};
|
|
2126
2185
|
}
|
|
2186
|
+
var BOOTSTRAP_MARKER_MIGRATION_TARGETS = [
|
|
2187
|
+
"CLAUDE.md",
|
|
2188
|
+
"AGENTS.md",
|
|
2189
|
+
".cursor/rules",
|
|
2190
|
+
".cursor/rules/fabric-bootstrap.mdc"
|
|
2191
|
+
];
|
|
2192
|
+
async function inspectBootstrapMarkerMigration(target) {
|
|
2193
|
+
const filesNeedingMigration = [];
|
|
2194
|
+
for (const rel of BOOTSTRAP_MARKER_MIGRATION_TARGETS) {
|
|
2195
|
+
const abs = join5(target, rel);
|
|
2196
|
+
if (!existsSync4(abs)) {
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
let content;
|
|
2200
|
+
try {
|
|
2201
|
+
content = await readFile5(abs, "utf8");
|
|
2202
|
+
} catch {
|
|
2203
|
+
continue;
|
|
2204
|
+
}
|
|
2205
|
+
if (LEGACY_KB_REGEX.test(content)) {
|
|
2206
|
+
filesNeedingMigration.push(abs);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return { filesNeedingMigration };
|
|
2210
|
+
}
|
|
2211
|
+
function createBootstrapMarkerMigrationCheck(inspection) {
|
|
2212
|
+
if (inspection.filesNeedingMigration.length === 0) {
|
|
2213
|
+
return okCheck(
|
|
2214
|
+
"Bootstrap marker migration",
|
|
2215
|
+
"No legacy fabric:knowledge-base markers detected in bootstrap target files."
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
const list = inspection.filesNeedingMigration.join(", ");
|
|
2219
|
+
return issueCheck(
|
|
2220
|
+
"Bootstrap marker migration",
|
|
2221
|
+
"error",
|
|
2222
|
+
"fixable_error",
|
|
2223
|
+
"bootstrap_marker_migration_required",
|
|
2224
|
+
`${inspection.filesNeedingMigration.length} file${inspection.filesNeedingMigration.length === 1 ? "" : "s"} still carry the legacy fabric:knowledge-base bootstrap marker: ${list}.`,
|
|
2225
|
+
"Run `fab doctor --fix` to migrate to fabric:bootstrap marker"
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
2229
|
+
const abs = join5(target, ".fabric", "AGENTS.md");
|
|
2230
|
+
if (!existsSync4(abs)) {
|
|
2231
|
+
return { status: "missing", canonical: BOOTSTRAP_CANONICAL, onDisk: null };
|
|
2232
|
+
}
|
|
2233
|
+
let onDisk;
|
|
2234
|
+
try {
|
|
2235
|
+
onDisk = await readFile5(abs, "utf8");
|
|
2236
|
+
} catch {
|
|
2237
|
+
return { status: "missing", canonical: BOOTSTRAP_CANONICAL, onDisk: null };
|
|
2238
|
+
}
|
|
2239
|
+
if (onDisk === BOOTSTRAP_CANONICAL) {
|
|
2240
|
+
return { status: "ok", canonical: BOOTSTRAP_CANONICAL, onDisk };
|
|
2241
|
+
}
|
|
2242
|
+
return { status: "drift", canonical: BOOTSTRAP_CANONICAL, onDisk };
|
|
2243
|
+
}
|
|
2244
|
+
function createL1BootstrapSnapshotDriftCheck(inspection) {
|
|
2245
|
+
if (inspection.status === "drift") {
|
|
2246
|
+
return issueCheck(
|
|
2247
|
+
"Bootstrap snapshot drift",
|
|
2248
|
+
"error",
|
|
2249
|
+
"fixable_error",
|
|
2250
|
+
"bootstrap_snapshot_drift",
|
|
2251
|
+
".fabric/AGENTS.md content diverges byte-for-byte from BOOTSTRAP_CANONICAL.",
|
|
2252
|
+
"Run `fab doctor --fix` to restore canonical bootstrap snapshot"
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
return okCheck(
|
|
2256
|
+
"Bootstrap snapshot drift",
|
|
2257
|
+
inspection.status === "ok" ? ".fabric/AGENTS.md byte-equals BOOTSTRAP_CANONICAL." : ".fabric/AGENTS.md absent \u2014 delegated to bootstrap_anchor_missing."
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
async function inspectL2ManagedBlockDrift(target) {
|
|
2261
|
+
const snapshotPath = join5(target, ".fabric", "AGENTS.md");
|
|
2262
|
+
if (!existsSync4(snapshotPath)) {
|
|
2263
|
+
return { status: "ok", drifted: [] };
|
|
2264
|
+
}
|
|
2265
|
+
let snapshot;
|
|
2266
|
+
try {
|
|
2267
|
+
snapshot = await readFile5(snapshotPath, "utf8");
|
|
2268
|
+
} catch {
|
|
2269
|
+
return { status: "ok", drifted: [] };
|
|
2270
|
+
}
|
|
2271
|
+
const projectRulesPath = join5(target, ".fabric", "project-rules.md");
|
|
2272
|
+
let expectedBody = snapshot;
|
|
2273
|
+
if (existsSync4(projectRulesPath)) {
|
|
2274
|
+
try {
|
|
2275
|
+
const projectRules = await readFile5(projectRulesPath, "utf8");
|
|
2276
|
+
expectedBody = `${snapshot}
|
|
2277
|
+
---
|
|
2278
|
+
${projectRules}`;
|
|
2279
|
+
} catch {
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
const drifted = [];
|
|
2283
|
+
let anyManagedBlockFound = false;
|
|
2284
|
+
const blockTargets = [
|
|
2285
|
+
join5(target, "AGENTS.md"),
|
|
2286
|
+
join5(target, ".cursor", "rules", "fabric-bootstrap.mdc")
|
|
2287
|
+
];
|
|
2288
|
+
for (const abs of blockTargets) {
|
|
2289
|
+
if (!existsSync4(abs)) {
|
|
2290
|
+
continue;
|
|
2291
|
+
}
|
|
2292
|
+
let content;
|
|
2293
|
+
try {
|
|
2294
|
+
content = await readFile5(abs, "utf8");
|
|
2295
|
+
} catch {
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
if (!BOOTSTRAP_REGEX.test(content) && LEGACY_KB_REGEX.test(content)) {
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
const match = content.match(BOOTSTRAP_REGEX);
|
|
2302
|
+
if (match === null) {
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
anyManagedBlockFound = true;
|
|
2306
|
+
const region = match[0];
|
|
2307
|
+
const beginIdx = region.indexOf(BOOTSTRAP_MARKER_BEGIN);
|
|
2308
|
+
const bodyStart = beginIdx + BOOTSTRAP_MARKER_BEGIN.length;
|
|
2309
|
+
const endIdx = region.indexOf(BOOTSTRAP_MARKER_END, bodyStart);
|
|
2310
|
+
if (bodyStart < 0 || endIdx < 0) {
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
let body = region.slice(bodyStart, endIdx);
|
|
2314
|
+
if (body.startsWith("\n")) body = body.slice(1);
|
|
2315
|
+
if (body.endsWith("\n")) body = body.slice(0, -1);
|
|
2316
|
+
if (body !== expectedBody) {
|
|
2317
|
+
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const claudeMdPath = join5(target, "CLAUDE.md");
|
|
2321
|
+
if (existsSync4(claudeMdPath)) {
|
|
2322
|
+
let claudeContent;
|
|
2323
|
+
try {
|
|
2324
|
+
claudeContent = await readFile5(claudeMdPath, "utf8");
|
|
2325
|
+
if (!BOOTSTRAP_REGEX.test(claudeContent) && LEGACY_KB_REGEX.test(claudeContent)) {
|
|
2326
|
+
} else {
|
|
2327
|
+
anyManagedBlockFound = true;
|
|
2328
|
+
const lines = claudeContent.split(/\r?\n/u);
|
|
2329
|
+
const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
|
|
2330
|
+
if (!hasAtImport) {
|
|
2331
|
+
drifted.push({
|
|
2332
|
+
path: claudeMdPath,
|
|
2333
|
+
expected: "@.fabric/AGENTS.md",
|
|
2334
|
+
actual: "(line missing)"
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
} catch {
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
if (!anyManagedBlockFound) {
|
|
2342
|
+
return { status: "no-managed-block", drifted: [] };
|
|
2343
|
+
}
|
|
2344
|
+
if (drifted.length === 0) {
|
|
2345
|
+
return { status: "ok", drifted: [] };
|
|
2346
|
+
}
|
|
2347
|
+
return { status: "drift", drifted };
|
|
2348
|
+
}
|
|
2349
|
+
function createL2ManagedBlockDriftCheck(inspection) {
|
|
2350
|
+
if (inspection.status === "drift") {
|
|
2351
|
+
const list = inspection.drifted.map((d) => d.path).join(", ");
|
|
2352
|
+
return issueCheck(
|
|
2353
|
+
"Managed block drift",
|
|
2354
|
+
"error",
|
|
2355
|
+
"fixable_error",
|
|
2356
|
+
"managed_block_drift",
|
|
2357
|
+
`${inspection.drifted.length} three-end managed block${inspection.drifted.length === 1 ? "" : "s"} diverge from expected body (snapshot + optional project-rules concat): ${list}.`,
|
|
2358
|
+
"Run `fab doctor --fix` to restore three-end managed blocks from canonical"
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
return okCheck(
|
|
2362
|
+
"Managed block drift",
|
|
2363
|
+
inspection.status === "ok" ? "Three-end managed blocks byte-equal expectedBody." : "No three-end managed blocks detected \u2014 propagation pending or legacy-marker state."
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2127
2366
|
function createBootstrapAnchorCheck(inspection) {
|
|
2128
2367
|
if (!inspection.hasAgentsMd && !inspection.hasClaudeMd) {
|
|
2129
2368
|
return issueCheck(
|
|
@@ -3953,6 +4192,125 @@ function createIndexDriftCheck(inspection) {
|
|
|
3953
4192
|
"Run `fab doctor --apply-lint` (rc.4 TASK-003) to bump agents.meta.json counters to max_observed + 1."
|
|
3954
4193
|
);
|
|
3955
4194
|
}
|
|
4195
|
+
async function migrateBootstrapMarkers(projectRoot) {
|
|
4196
|
+
const paths = [];
|
|
4197
|
+
const countPerPath = {};
|
|
4198
|
+
for (const rel of BOOTSTRAP_MARKER_MIGRATION_TARGETS) {
|
|
4199
|
+
const abs = join5(projectRoot, rel);
|
|
4200
|
+
if (!existsSync4(abs)) {
|
|
4201
|
+
continue;
|
|
4202
|
+
}
|
|
4203
|
+
let original;
|
|
4204
|
+
try {
|
|
4205
|
+
original = await readFile5(abs, "utf8");
|
|
4206
|
+
} catch {
|
|
4207
|
+
continue;
|
|
4208
|
+
}
|
|
4209
|
+
const beginMatches = original.match(/<!-- fabric:knowledge-base:begin -->/g);
|
|
4210
|
+
const endMatches = original.match(/<!-- fabric:knowledge-base:end -->/g);
|
|
4211
|
+
const replacedCount = (beginMatches?.length ?? 0) + (endMatches?.length ?? 0);
|
|
4212
|
+
if (replacedCount === 0) {
|
|
4213
|
+
continue;
|
|
4214
|
+
}
|
|
4215
|
+
const rewritten = original.replace(/<!-- fabric:knowledge-base:begin -->/g, BOOTSTRAP_MARKER_BEGIN).replace(/<!-- fabric:knowledge-base:end -->/g, BOOTSTRAP_MARKER_END);
|
|
4216
|
+
if (rewritten === original) {
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
await atomicWriteText3(abs, rewritten);
|
|
4220
|
+
paths.push(abs);
|
|
4221
|
+
countPerPath[abs] = replacedCount;
|
|
4222
|
+
}
|
|
4223
|
+
return { paths, countPerPath };
|
|
4224
|
+
}
|
|
4225
|
+
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
4226
|
+
const snapshotPath = join5(projectRoot, ".fabric", "AGENTS.md");
|
|
4227
|
+
if (!existsSync4(snapshotPath)) {
|
|
4228
|
+
return;
|
|
4229
|
+
}
|
|
4230
|
+
let snapshot;
|
|
4231
|
+
try {
|
|
4232
|
+
snapshot = await readFile5(snapshotPath, "utf8");
|
|
4233
|
+
} catch {
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
const projectRulesPath = join5(projectRoot, ".fabric", "project-rules.md");
|
|
4237
|
+
const hasProjectRules = existsSync4(projectRulesPath);
|
|
4238
|
+
let expectedBody = snapshot;
|
|
4239
|
+
if (hasProjectRules) {
|
|
4240
|
+
try {
|
|
4241
|
+
const projectRules = await readFile5(projectRulesPath, "utf8");
|
|
4242
|
+
expectedBody = `${snapshot}
|
|
4243
|
+
---
|
|
4244
|
+
${projectRules}`;
|
|
4245
|
+
} catch {
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
const managedBlock = `${BOOTSTRAP_MARKER_BEGIN}
|
|
4249
|
+
${expectedBody}
|
|
4250
|
+
${BOOTSTRAP_MARKER_END}`;
|
|
4251
|
+
const blockTargets = [
|
|
4252
|
+
join5(projectRoot, "AGENTS.md"),
|
|
4253
|
+
join5(projectRoot, ".cursor", "rules", "fabric-bootstrap.mdc")
|
|
4254
|
+
];
|
|
4255
|
+
for (const abs of blockTargets) {
|
|
4256
|
+
if (!existsSync4(abs)) {
|
|
4257
|
+
continue;
|
|
4258
|
+
}
|
|
4259
|
+
let existing;
|
|
4260
|
+
try {
|
|
4261
|
+
existing = await readFile5(abs, "utf8");
|
|
4262
|
+
} catch {
|
|
4263
|
+
continue;
|
|
4264
|
+
}
|
|
4265
|
+
let next;
|
|
4266
|
+
const match = existing.match(BOOTSTRAP_REGEX);
|
|
4267
|
+
if (match !== null) {
|
|
4268
|
+
const before = existing.slice(0, match.index ?? 0);
|
|
4269
|
+
const after = existing.slice((match.index ?? 0) + match[0].length);
|
|
4270
|
+
const stripped = `${before}${after.replace(/^\r?\n/, "")}`;
|
|
4271
|
+
const trailingNewline = stripped.length === 0 || stripped.endsWith("\n") ? "" : "\n";
|
|
4272
|
+
next = `${stripped}${trailingNewline}
|
|
4273
|
+
${managedBlock}
|
|
4274
|
+
`;
|
|
4275
|
+
} else {
|
|
4276
|
+
const trailingNewline = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
4277
|
+
next = `${existing}${trailingNewline}
|
|
4278
|
+
${managedBlock}
|
|
4279
|
+
`;
|
|
4280
|
+
}
|
|
4281
|
+
if (next === existing) {
|
|
4282
|
+
continue;
|
|
4283
|
+
}
|
|
4284
|
+
await atomicWriteText3(abs, next);
|
|
4285
|
+
}
|
|
4286
|
+
const claudeMdPath = join5(projectRoot, "CLAUDE.md");
|
|
4287
|
+
if (existsSync4(claudeMdPath)) {
|
|
4288
|
+
let claudeContent;
|
|
4289
|
+
try {
|
|
4290
|
+
claudeContent = await readFile5(claudeMdPath, "utf8");
|
|
4291
|
+
} catch {
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
const lines = claudeContent.split(/\r?\n/u);
|
|
4295
|
+
let updated = claudeContent;
|
|
4296
|
+
const ensureLine = (line) => {
|
|
4297
|
+
if (lines.some((existingLine) => existingLine.trim() === line)) {
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
const trailingNewline = updated.length === 0 || updated.endsWith("\n") ? "" : "\n";
|
|
4301
|
+
updated = `${updated}${trailingNewline}${line}
|
|
4302
|
+
`;
|
|
4303
|
+
lines.push(line);
|
|
4304
|
+
};
|
|
4305
|
+
ensureLine("@.fabric/AGENTS.md");
|
|
4306
|
+
if (hasProjectRules) {
|
|
4307
|
+
ensureLine("@.fabric/project-rules.md");
|
|
4308
|
+
}
|
|
4309
|
+
if (updated !== claudeContent) {
|
|
4310
|
+
await atomicWriteText3(claudeMdPath, updated);
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
3956
4314
|
async function fixMcpConfigInWrongFile(projectRoot) {
|
|
3957
4315
|
const settingsPath = join5(projectRoot, ".claude", "settings.json");
|
|
3958
4316
|
if (!existsSync4(settingsPath)) {
|
|
@@ -4026,6 +4384,275 @@ async function ensureEventLedger(projectRoot) {
|
|
|
4026
4384
|
await ensureParentDirectory(path);
|
|
4027
4385
|
await writeFile2(path, "", { encoding: "utf8", flag: "a" });
|
|
4028
4386
|
}
|
|
4387
|
+
var CITE_POLICY_VERSION = "2.0.0-rc.20";
|
|
4388
|
+
async function ensureCitePolicyActivatedMarker(projectRoot) {
|
|
4389
|
+
let existing;
|
|
4390
|
+
try {
|
|
4391
|
+
const { events } = await readEventLedger(projectRoot, { event_type: "cite_policy_activated" });
|
|
4392
|
+
if (events.length > 0) {
|
|
4393
|
+
existing = events[0];
|
|
4394
|
+
}
|
|
4395
|
+
} catch {
|
|
4396
|
+
return { marker_ts: 0, emitted_now: false };
|
|
4397
|
+
}
|
|
4398
|
+
if (existing !== void 0) {
|
|
4399
|
+
return { marker_ts: existing.ts, emitted_now: false };
|
|
4400
|
+
}
|
|
4401
|
+
try {
|
|
4402
|
+
const stored = await appendEventLedgerEvent(projectRoot, {
|
|
4403
|
+
event_type: "cite_policy_activated",
|
|
4404
|
+
policy_version: CITE_POLICY_VERSION,
|
|
4405
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4406
|
+
});
|
|
4407
|
+
return { marker_ts: stored.ts, emitted_now: true };
|
|
4408
|
+
} catch {
|
|
4409
|
+
return { marker_ts: 0, emitted_now: false };
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
function categorizeCiteTag(tag) {
|
|
4413
|
+
if (tag === "planned" || tag === "recalled" || tag === "chained-from" || tag === "none") {
|
|
4414
|
+
return { category: tag };
|
|
4415
|
+
}
|
|
4416
|
+
if (tag === "dismissed") {
|
|
4417
|
+
return { category: "dismissed", reason: "unspecified" };
|
|
4418
|
+
}
|
|
4419
|
+
if (tag.startsWith("dismissed:")) {
|
|
4420
|
+
const remainder = tag.slice("dismissed:".length);
|
|
4421
|
+
if (remainder.startsWith("other:")) {
|
|
4422
|
+
return { category: "dismissed", reason: remainder.slice("other:".length) || "other" };
|
|
4423
|
+
}
|
|
4424
|
+
return { category: "dismissed", reason: remainder || "unspecified" };
|
|
4425
|
+
}
|
|
4426
|
+
return { category: "none" };
|
|
4427
|
+
}
|
|
4428
|
+
function matchesRelevancePath(editPath, relevancePaths) {
|
|
4429
|
+
if (relevancePaths.length === 0) {
|
|
4430
|
+
return false;
|
|
4431
|
+
}
|
|
4432
|
+
const normalized = normalizePath(editPath);
|
|
4433
|
+
for (const glob of relevancePaths) {
|
|
4434
|
+
if (minimatch(normalized, glob, { dot: true, matchBase: false })) {
|
|
4435
|
+
return true;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
return false;
|
|
4439
|
+
}
|
|
4440
|
+
async function runDoctorCiteCoverage(projectRoot, options) {
|
|
4441
|
+
const marker = await ensureCitePolicyActivatedMarker(projectRoot);
|
|
4442
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4443
|
+
const zeroMetrics = {
|
|
4444
|
+
edits_touched: 0,
|
|
4445
|
+
qualifying_cites: 0,
|
|
4446
|
+
recalled_unverified: 0,
|
|
4447
|
+
expected_but_missed: 0,
|
|
4448
|
+
total_turns: 0
|
|
4449
|
+
};
|
|
4450
|
+
if (marker.marker_ts === 0) {
|
|
4451
|
+
return {
|
|
4452
|
+
status: "skipped",
|
|
4453
|
+
marker_ts: 0,
|
|
4454
|
+
marker_emitted_now: false,
|
|
4455
|
+
since_ts: options.since,
|
|
4456
|
+
client_filter: options.client,
|
|
4457
|
+
metrics: zeroMetrics,
|
|
4458
|
+
generated_at: generatedAt
|
|
4459
|
+
};
|
|
4460
|
+
}
|
|
4461
|
+
const effectiveSince = Math.max(marker.marker_ts, options.since);
|
|
4462
|
+
let ledgerEvents = [];
|
|
4463
|
+
try {
|
|
4464
|
+
const result = await readEventLedger(projectRoot, { since: effectiveSince });
|
|
4465
|
+
ledgerEvents = result.events;
|
|
4466
|
+
} catch {
|
|
4467
|
+
return {
|
|
4468
|
+
status: "ok",
|
|
4469
|
+
marker_ts: marker.marker_ts,
|
|
4470
|
+
marker_emitted_now: marker.emitted_now,
|
|
4471
|
+
since_ts: effectiveSince,
|
|
4472
|
+
client_filter: options.client,
|
|
4473
|
+
metrics: zeroMetrics,
|
|
4474
|
+
generated_at: generatedAt
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
const assistantTurns = [];
|
|
4478
|
+
const editEvents = [];
|
|
4479
|
+
const fetchEvents = [];
|
|
4480
|
+
for (const event of ledgerEvents) {
|
|
4481
|
+
switch (event.event_type) {
|
|
4482
|
+
case "assistant_turn_observed":
|
|
4483
|
+
assistantTurns.push(event);
|
|
4484
|
+
break;
|
|
4485
|
+
case "edit_intent_checked":
|
|
4486
|
+
editEvents.push(event);
|
|
4487
|
+
break;
|
|
4488
|
+
case "knowledge_sections_fetched":
|
|
4489
|
+
fetchEvents.push(event);
|
|
4490
|
+
break;
|
|
4491
|
+
default:
|
|
4492
|
+
break;
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
const filteredTurns = options.client === "all" ? assistantTurns : assistantTurns.filter((t) => t.client === options.client);
|
|
4496
|
+
let clientSessionIds = null;
|
|
4497
|
+
if (options.client !== "all") {
|
|
4498
|
+
clientSessionIds = /* @__PURE__ */ new Set();
|
|
4499
|
+
for (const turn of assistantTurns) {
|
|
4500
|
+
if (turn.client === options.client) {
|
|
4501
|
+
const sid = turn.session_id;
|
|
4502
|
+
if (typeof sid === "string" && sid.length > 0) {
|
|
4503
|
+
clientSessionIds.add(sid);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
const kbIndex = /* @__PURE__ */ new Map();
|
|
4509
|
+
try {
|
|
4510
|
+
const meta = await readAgentsMeta(projectRoot);
|
|
4511
|
+
for (const node of Object.values(meta.nodes)) {
|
|
4512
|
+
const stableId = node.stable_id;
|
|
4513
|
+
if (typeof stableId !== "string" || stableId.length === 0) continue;
|
|
4514
|
+
const description = node.description;
|
|
4515
|
+
if (description === void 0) continue;
|
|
4516
|
+
const paths = description.relevance_paths ?? [];
|
|
4517
|
+
const scope = description.relevance_scope ?? "broad";
|
|
4518
|
+
kbIndex.set(stableId, {
|
|
4519
|
+
relevance_paths: paths,
|
|
4520
|
+
// A broad entry with no paths is the safe default. A narrow entry must
|
|
4521
|
+
// carry at least one path; an empty-paths narrow is treated as broad.
|
|
4522
|
+
relevance_scope: scope === "narrow" && paths.length > 0 ? "narrow" : "broad"
|
|
4523
|
+
});
|
|
4524
|
+
}
|
|
4525
|
+
} catch {
|
|
4526
|
+
}
|
|
4527
|
+
const fetchesBySession = /* @__PURE__ */ new Map();
|
|
4528
|
+
for (const fetch of fetchEvents) {
|
|
4529
|
+
const sid = fetch.session_id;
|
|
4530
|
+
if (typeof sid !== "string" || sid.length === 0) continue;
|
|
4531
|
+
const list = fetchesBySession.get(sid) ?? [];
|
|
4532
|
+
list.push(fetch.ts);
|
|
4533
|
+
fetchesBySession.set(sid, list);
|
|
4534
|
+
}
|
|
4535
|
+
for (const list of fetchesBySession.values()) {
|
|
4536
|
+
list.sort((a, b) => a - b);
|
|
4537
|
+
}
|
|
4538
|
+
const RECALL_WINDOW_MS = 6e4;
|
|
4539
|
+
const isRecallVerified = (turn) => {
|
|
4540
|
+
const sid = turn.session_id;
|
|
4541
|
+
if (typeof sid !== "string" || sid.length === 0) return false;
|
|
4542
|
+
const fetches = fetchesBySession.get(sid);
|
|
4543
|
+
if (fetches === void 0 || fetches.length === 0) return false;
|
|
4544
|
+
for (const ft of fetches) {
|
|
4545
|
+
if (Math.abs(ft - turn.ts) <= RECALL_WINDOW_MS) return true;
|
|
4546
|
+
}
|
|
4547
|
+
return false;
|
|
4548
|
+
};
|
|
4549
|
+
const dismissedHistogram = {};
|
|
4550
|
+
const perClientAccum = /* @__PURE__ */ new Map();
|
|
4551
|
+
const emptyMetrics = () => ({
|
|
4552
|
+
edits_touched: 0,
|
|
4553
|
+
qualifying_cites: 0,
|
|
4554
|
+
recalled_unverified: 0,
|
|
4555
|
+
expected_but_missed: 0,
|
|
4556
|
+
total_turns: 0
|
|
4557
|
+
});
|
|
4558
|
+
const bumpClient = (client, mut) => {
|
|
4559
|
+
if (typeof client !== "string" || client.length === 0) return;
|
|
4560
|
+
const existing = perClientAccum.get(client) ?? emptyMetrics();
|
|
4561
|
+
mut(existing);
|
|
4562
|
+
perClientAccum.set(client, existing);
|
|
4563
|
+
};
|
|
4564
|
+
const sessionCitedKbs = /* @__PURE__ */ new Map();
|
|
4565
|
+
let totalTurns = 0;
|
|
4566
|
+
let qualifyingCites = 0;
|
|
4567
|
+
let recalledUnverified = 0;
|
|
4568
|
+
for (const turn of filteredTurns) {
|
|
4569
|
+
totalTurns += 1;
|
|
4570
|
+
bumpClient(turn.client, (m) => {
|
|
4571
|
+
m.total_turns += 1;
|
|
4572
|
+
});
|
|
4573
|
+
const sid = turn.session_id;
|
|
4574
|
+
if (typeof sid === "string" && sid.length > 0) {
|
|
4575
|
+
const set = sessionCitedKbs.get(sid) ?? /* @__PURE__ */ new Set();
|
|
4576
|
+
for (const id of turn.cite_ids) {
|
|
4577
|
+
set.add(id);
|
|
4578
|
+
}
|
|
4579
|
+
sessionCitedKbs.set(sid, set);
|
|
4580
|
+
}
|
|
4581
|
+
let turnHadRecalled = false;
|
|
4582
|
+
for (const tag of turn.cite_tags) {
|
|
4583
|
+
const { category, reason } = categorizeCiteTag(tag);
|
|
4584
|
+
switch (category) {
|
|
4585
|
+
case "planned":
|
|
4586
|
+
case "recalled":
|
|
4587
|
+
case "chained-from":
|
|
4588
|
+
qualifyingCites += 1;
|
|
4589
|
+
bumpClient(turn.client, (m) => {
|
|
4590
|
+
m.qualifying_cites += 1;
|
|
4591
|
+
});
|
|
4592
|
+
if (category === "recalled") turnHadRecalled = true;
|
|
4593
|
+
break;
|
|
4594
|
+
case "dismissed": {
|
|
4595
|
+
const key = reason ?? "unspecified";
|
|
4596
|
+
dismissedHistogram[key] = (dismissedHistogram[key] ?? 0) + 1;
|
|
4597
|
+
break;
|
|
4598
|
+
}
|
|
4599
|
+
case "none":
|
|
4600
|
+
default:
|
|
4601
|
+
break;
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
if (turnHadRecalled && !isRecallVerified(turn)) {
|
|
4605
|
+
recalledUnverified += 1;
|
|
4606
|
+
bumpClient(turn.client, (m) => {
|
|
4607
|
+
m.recalled_unverified += 1;
|
|
4608
|
+
});
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
let editsTouched = 0;
|
|
4612
|
+
let expectedButMissed = 0;
|
|
4613
|
+
for (const edit of editEvents) {
|
|
4614
|
+
const sid = edit.session_id;
|
|
4615
|
+
if (clientSessionIds !== null) {
|
|
4616
|
+
if (typeof sid !== "string" || sid.length === 0) continue;
|
|
4617
|
+
if (!clientSessionIds.has(sid)) continue;
|
|
4618
|
+
}
|
|
4619
|
+
editsTouched += 1;
|
|
4620
|
+
if (typeof sid !== "string" || sid.length === 0) continue;
|
|
4621
|
+
const citedSet = sessionCitedKbs.get(sid) ?? /* @__PURE__ */ new Set();
|
|
4622
|
+
for (const [kbId, kb] of kbIndex) {
|
|
4623
|
+
if (kb.relevance_scope !== "narrow") continue;
|
|
4624
|
+
if (!matchesRelevancePath(edit.path, kb.relevance_paths)) continue;
|
|
4625
|
+
if (!citedSet.has(kbId)) {
|
|
4626
|
+
expectedButMissed += 1;
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
const metrics = {
|
|
4631
|
+
edits_touched: editsTouched,
|
|
4632
|
+
qualifying_cites: qualifyingCites,
|
|
4633
|
+
recalled_unverified: recalledUnverified,
|
|
4634
|
+
expected_but_missed: expectedButMissed,
|
|
4635
|
+
total_turns: totalTurns
|
|
4636
|
+
};
|
|
4637
|
+
let perClient;
|
|
4638
|
+
if (options.client === "all" && perClientAccum.size > 0) {
|
|
4639
|
+
perClient = {};
|
|
4640
|
+
for (const [client, m] of perClientAccum) {
|
|
4641
|
+
perClient[client] = m;
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
return {
|
|
4645
|
+
status: "ok",
|
|
4646
|
+
marker_ts: marker.marker_ts,
|
|
4647
|
+
marker_emitted_now: marker.emitted_now,
|
|
4648
|
+
since_ts: effectiveSince,
|
|
4649
|
+
client_filter: options.client,
|
|
4650
|
+
metrics,
|
|
4651
|
+
...perClient !== void 0 ? { per_client: perClient } : {},
|
|
4652
|
+
...Object.keys(dismissedHistogram).length > 0 ? { dismissed_reason_histogram: dismissedHistogram } : {},
|
|
4653
|
+
generated_at: generatedAt
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4029
4656
|
function createFixMessage(fixed, report) {
|
|
4030
4657
|
const fixedText = fixed.length === 0 ? "No deterministic doctor fixes were needed." : `Applied ${fixed.length} deterministic doctor fix${fixed.length === 1 ? "" : "es"}.`;
|
|
4031
4658
|
const manualText = report.manual_errors.length === 0 ? "No manual errors remain." : `${report.manual_errors.length} manual error${report.manual_errors.length === 1 ? "" : "s"} remain.`;
|
|
@@ -4332,5 +4959,6 @@ export {
|
|
|
4332
4959
|
normalizeKnowledgePath,
|
|
4333
4960
|
runDoctorReport,
|
|
4334
4961
|
runDoctorFix,
|
|
4335
|
-
runDoctorApplyLint
|
|
4962
|
+
runDoctorApplyLint,
|
|
4963
|
+
runDoctorCiteCoverage
|
|
4336
4964
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -97,6 +97,27 @@ type DoctorApplyLintReport = {
|
|
|
97
97
|
declare function runDoctorReport(target: string): Promise<DoctorReport>;
|
|
98
98
|
declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
|
|
99
99
|
declare function runDoctorApplyLint(target: string): Promise<DoctorApplyLintReport>;
|
|
100
|
+
type CiteCoverageReport = {
|
|
101
|
+
status: "ok" | "skipped";
|
|
102
|
+
marker_ts: number;
|
|
103
|
+
marker_emitted_now: boolean;
|
|
104
|
+
since_ts: number;
|
|
105
|
+
client_filter: "cc" | "codex" | "cursor" | "all";
|
|
106
|
+
metrics: {
|
|
107
|
+
edits_touched: number;
|
|
108
|
+
qualifying_cites: number;
|
|
109
|
+
recalled_unverified: number;
|
|
110
|
+
expected_but_missed: number;
|
|
111
|
+
total_turns: number;
|
|
112
|
+
};
|
|
113
|
+
per_client?: Record<string, Partial<CiteCoverageReport["metrics"]>>;
|
|
114
|
+
dismissed_reason_histogram?: Record<string, number>;
|
|
115
|
+
generated_at: string;
|
|
116
|
+
};
|
|
117
|
+
declare function runDoctorCiteCoverage(projectRoot: string, options: {
|
|
118
|
+
since: number;
|
|
119
|
+
client: "cc" | "codex" | "cursor" | "all";
|
|
120
|
+
}): Promise<CiteCoverageReport>;
|
|
100
121
|
|
|
101
122
|
type KnowledgeMetaBuildSource = "doctor_fix" | "sync_meta";
|
|
102
123
|
type KnowledgeMetaBuildResult = {
|
|
@@ -382,4 +403,4 @@ declare function startHttpServer(options: {
|
|
|
382
403
|
authToken?: string;
|
|
383
404
|
}): Promise<Server>;
|
|
384
405
|
|
|
385
|
-
export { AGENTS_MD_RESOURCE_URI, type AcquireOptions, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type InFlightTracker, KnowledgeIdAllocator, type KnowledgeMetaBuildResult, type KnowledgeMetaBuildSource, type KnowledgeSyncLedgerEvent, type KnowledgeSyncOptions, type KnowledgeSyncReport, LEDGER_PATH, LEGACY_LEDGER_PATH, type LedgerEvent, type LockState, type PlanContextInput, type PlanContextResult, type ReconcileKnowledgeOptions, type RequirementProfile, type SelectionTokenState, ServeLockHeldError, type ShutdownHandlerDeps, type StructuredWarning, type WriteKnowledgeMetaOptions, acquireLock, appendEventLedgerEvent, buildKnowledgeMeta, checkLockOrThrow, computeKnowledgeBasedAgentsMeta, computeKnowledgeTestIndex, createFabricServer, createInFlightTracker, createShutdownHandler, deriveKnowledgeMetaLayer, deriveKnowledgeMetaTopologyType, ensureKnowledgeFresh, extractKnowledge, flushAndSyncEventLedger, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, isSameKnowledgeTestIndex, planContext, readLockState, readSelectionToken, reconcileKnowledge, releaseLock, reviewKnowledge, runDoctorApplyLint, runDoctorFix, runDoctorReport, stableStringify, startHttpServer, startStdioServer, writeKnowledgeMeta };
|
|
406
|
+
export { AGENTS_MD_RESOURCE_URI, type AcquireOptions, type CiteCoverageReport, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type InFlightTracker, KnowledgeIdAllocator, type KnowledgeMetaBuildResult, type KnowledgeMetaBuildSource, type KnowledgeSyncLedgerEvent, type KnowledgeSyncOptions, type KnowledgeSyncReport, LEDGER_PATH, LEGACY_LEDGER_PATH, type LedgerEvent, type LockState, type PlanContextInput, type PlanContextResult, type ReconcileKnowledgeOptions, type RequirementProfile, type SelectionTokenState, ServeLockHeldError, type ShutdownHandlerDeps, type StructuredWarning, type WriteKnowledgeMetaOptions, acquireLock, appendEventLedgerEvent, buildKnowledgeMeta, checkLockOrThrow, computeKnowledgeBasedAgentsMeta, computeKnowledgeTestIndex, createFabricServer, createInFlightTracker, createShutdownHandler, deriveKnowledgeMetaLayer, deriveKnowledgeMetaTopologyType, ensureKnowledgeFresh, extractKnowledge, flushAndSyncEventLedger, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, isSameKnowledgeTestIndex, planContext, readLockState, readSelectionToken, reconcileKnowledge, releaseLock, reviewKnowledge, runDoctorApplyLint, runDoctorCiteCoverage, runDoctorFix, runDoctorReport, stableStringify, startHttpServer, startStdioServer, writeKnowledgeMeta };
|
package/dist/index.js
CHANGED
|
@@ -21,12 +21,13 @@ import {
|
|
|
21
21
|
reconcileKnowledge,
|
|
22
22
|
resolveProjectRoot,
|
|
23
23
|
runDoctorApplyLint,
|
|
24
|
+
runDoctorCiteCoverage,
|
|
24
25
|
runDoctorFix,
|
|
25
26
|
runDoctorReport,
|
|
26
27
|
sha256,
|
|
27
28
|
stableStringify,
|
|
28
29
|
writeKnowledgeMeta
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-7R6MFA7Y.js";
|
|
30
31
|
|
|
31
32
|
// src/index.ts
|
|
32
33
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -318,9 +319,9 @@ ${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
|
318
319
|
const pathSection = /Recent paths:\s*\n([\s\S]*?)(?:\n\s*Notes:|$)/u.exec(block);
|
|
319
320
|
if (pathSection !== null) {
|
|
320
321
|
for (const rawLine of (pathSection[1] ?? "").split(/\r?\n/u)) {
|
|
321
|
-
const
|
|
322
|
-
if (
|
|
323
|
-
existingPaths.push(
|
|
322
|
+
const t2 = rawLine.trim();
|
|
323
|
+
if (t2.startsWith("- ")) {
|
|
324
|
+
existingPaths.push(t2.slice(2).trim());
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
}
|
|
@@ -329,16 +330,16 @@ ${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
|
329
330
|
const bulletLines = [];
|
|
330
331
|
let prose = [];
|
|
331
332
|
for (const rawLine of noteBody.split(/\r?\n/u)) {
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
if (
|
|
333
|
+
const t2 = rawLine.trim();
|
|
334
|
+
if (t2.length === 0) continue;
|
|
335
|
+
if (t2.startsWith("- ")) {
|
|
335
336
|
if (prose.length > 0) {
|
|
336
337
|
existingNotes.push(prose.join(" ").trim());
|
|
337
338
|
prose = [];
|
|
338
339
|
}
|
|
339
|
-
bulletLines.push(
|
|
340
|
+
bulletLines.push(t2.slice(2).trim());
|
|
340
341
|
} else {
|
|
341
|
-
prose.push(
|
|
342
|
+
prose.push(t2);
|
|
342
343
|
}
|
|
343
344
|
}
|
|
344
345
|
if (prose.length > 0) existingNotes.push(prose.join(" ").trim());
|
|
@@ -934,7 +935,7 @@ async function listPending(projectRoot, filters) {
|
|
|
934
935
|
}
|
|
935
936
|
if (filters?.tags !== void 0 && filters.tags.length > 0) {
|
|
936
937
|
const itemTags = fm.tags ?? [];
|
|
937
|
-
const hasAll = filters.tags.every((
|
|
938
|
+
const hasAll = filters.tags.every((t2) => itemTags.includes(t2));
|
|
938
939
|
if (!hasAll) continue;
|
|
939
940
|
}
|
|
940
941
|
if (filters?.created_after !== void 0) {
|
|
@@ -1234,7 +1235,7 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
1234
1235
|
}
|
|
1235
1236
|
if (filters?.tags !== void 0 && filters.tags.length > 0) {
|
|
1236
1237
|
const itemTags = fm.tags ?? [];
|
|
1237
|
-
const hasAll = filters.tags.every((
|
|
1238
|
+
const hasAll = filters.tags.every((t2) => itemTags.includes(t2));
|
|
1238
1239
|
if (!hasAll) continue;
|
|
1239
1240
|
}
|
|
1240
1241
|
if (filters?.created_after !== void 0) {
|
|
@@ -1781,8 +1782,10 @@ function registerKnowledgeSections(server, tracker) {
|
|
|
1781
1782
|
// src/services/serve-lock.ts
|
|
1782
1783
|
import fs from "fs";
|
|
1783
1784
|
import path from "path";
|
|
1785
|
+
import { createTranslator, detectNodeLocale } from "@fenglimg/fabric-shared";
|
|
1784
1786
|
import { IOFabricError } from "@fenglimg/fabric-shared/errors";
|
|
1785
1787
|
var LOCK_FILENAME = ".serve.lock";
|
|
1788
|
+
var t = createTranslator(detectNodeLocale());
|
|
1786
1789
|
var ServeLockHeldError = class extends IOFabricError {
|
|
1787
1790
|
code = "SERVE_LOCK_HELD";
|
|
1788
1791
|
httpStatus = 423;
|
|
@@ -1813,7 +1816,7 @@ function acquireLock(projectRoot, opts) {
|
|
|
1813
1816
|
throw new ServeLockHeldError(
|
|
1814
1817
|
`serve lock held by live PID ${state.pid}`,
|
|
1815
1818
|
{
|
|
1816
|
-
actionHint:
|
|
1819
|
+
actionHint: t("cli.serve.lock-held.action-hint", { pid: String(state.pid) }),
|
|
1817
1820
|
details: state
|
|
1818
1821
|
}
|
|
1819
1822
|
);
|
|
@@ -1863,7 +1866,7 @@ function checkLockOrThrow(projectRoot, opts) {
|
|
|
1863
1866
|
throw new ServeLockHeldError(
|
|
1864
1867
|
`serve lock held by live PID ${state.pid}`,
|
|
1865
1868
|
{
|
|
1866
|
-
actionHint:
|
|
1869
|
+
actionHint: t("cli.serve.lock-held.action-hint", { pid: String(state.pid) }),
|
|
1867
1870
|
details: state
|
|
1868
1871
|
}
|
|
1869
1872
|
);
|
|
@@ -1890,7 +1893,7 @@ function formatPreexistingRootMessage(projectRoot) {
|
|
|
1890
1893
|
function createFabricServer(tracker) {
|
|
1891
1894
|
const server = new McpServer({
|
|
1892
1895
|
name: "fabric-knowledge-server",
|
|
1893
|
-
version: "2.0.0-rc.
|
|
1896
|
+
version: "2.0.0-rc.21"
|
|
1894
1897
|
});
|
|
1895
1898
|
registerPlanContext(server, tracker);
|
|
1896
1899
|
registerKnowledgeSections(server, tracker);
|
|
@@ -1990,7 +1993,7 @@ function createShutdownHandler(deps) {
|
|
|
1990
1993
|
};
|
|
1991
1994
|
}
|
|
1992
1995
|
async function startHttpServer(options) {
|
|
1993
|
-
const { createFabricHttpApp } = await import("./http-
|
|
1996
|
+
const { createFabricHttpApp } = await import("./http-JGWQGUZS.js");
|
|
1994
1997
|
const { port, projectRoot, host = "127.0.0.1", authToken } = options;
|
|
1995
1998
|
const app = createFabricHttpApp({ projectRoot, host, authToken });
|
|
1996
1999
|
return await new Promise((resolveServer, rejectServer) => {
|
|
@@ -2048,6 +2051,7 @@ export {
|
|
|
2048
2051
|
releaseLock,
|
|
2049
2052
|
reviewKnowledge,
|
|
2050
2053
|
runDoctorApplyLint,
|
|
2054
|
+
runDoctorCiteCoverage,
|
|
2051
2055
|
runDoctorFix,
|
|
2052
2056
|
runDoctorReport,
|
|
2053
2057
|
stableStringify,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-server",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"express": "^5.2.1",
|
|
14
14
|
"minimatch": "^10.0.1",
|
|
15
15
|
"zod": "^3.25.0",
|
|
16
|
-
"@fenglimg/fabric-shared": "2.0.0-rc.
|
|
16
|
+
"@fenglimg/fabric-shared": "2.0.0-rc.21"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/express": "^5.0.6",
|