@fenglimg/fabric-server 2.2.0-rc.10 → 2.2.0-rc.11
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/index.js +298 -150
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { existsSync as
|
|
3
|
-
import { readFile as
|
|
4
|
-
import { join as
|
|
2
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
4
|
+
import { join as join21, resolve as resolve4 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -3415,11 +3415,155 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
|
|
|
3415
3415
|
|
|
3416
3416
|
// src/services/review.ts
|
|
3417
3417
|
import { execFileSync } from "child_process";
|
|
3418
|
-
import { existsSync as
|
|
3419
|
-
import { readFile as
|
|
3418
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3419
|
+
import { readFile as readFile6, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
3420
3420
|
import { homedir } from "os";
|
|
3421
|
-
import { basename, isAbsolute, join as
|
|
3421
|
+
import { basename, isAbsolute, join as join10, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
3422
3422
|
import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@fenglimg/fabric-shared";
|
|
3423
|
+
|
|
3424
|
+
// src/services/pending-dedupe.ts
|
|
3425
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3426
|
+
import { readdir, readFile as readFile5, unlink } from "fs/promises";
|
|
3427
|
+
import { join as join9 } from "path";
|
|
3428
|
+
var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
|
|
3429
|
+
var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
|
|
3430
|
+
var SOURCE_SESSIONS_LINE = /^source_sessions:\s*\[(.*)\]\s*$/mu;
|
|
3431
|
+
var CREATED_AT_LINE = /^created_at:\s*(.+)$/mu;
|
|
3432
|
+
function parseSourceSessions(content) {
|
|
3433
|
+
const m = SOURCE_SESSIONS_LINE.exec(content);
|
|
3434
|
+
if (!m) return [];
|
|
3435
|
+
try {
|
|
3436
|
+
const arr = JSON.parse(`[${m[1]}]`);
|
|
3437
|
+
return Array.isArray(arr) ? arr.filter((s) => typeof s === "string") : [];
|
|
3438
|
+
} catch {
|
|
3439
|
+
return [];
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function parseCreatedAt(content) {
|
|
3443
|
+
const m = CREATED_AT_LINE.exec(content);
|
|
3444
|
+
return m ? m[1].trim() : "";
|
|
3445
|
+
}
|
|
3446
|
+
function resolveBaseSlug(name, present) {
|
|
3447
|
+
const m = DISAMBIGUATION_SUFFIX.exec(name);
|
|
3448
|
+
if (m && present.has(`${m[1]}.md`)) return m[1];
|
|
3449
|
+
return name.replace(/\.md$/u, "");
|
|
3450
|
+
}
|
|
3451
|
+
function unionSessions(survivor, twins) {
|
|
3452
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3453
|
+
const out = [];
|
|
3454
|
+
for (const s of [survivor, ...twins]) {
|
|
3455
|
+
for (const sid of s.sourceSessions) {
|
|
3456
|
+
if (sid.length > 0 && !seen.has(sid)) {
|
|
3457
|
+
seen.add(sid);
|
|
3458
|
+
out.push(sid);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
return out;
|
|
3463
|
+
}
|
|
3464
|
+
function buildMergedContent(survivor, twins) {
|
|
3465
|
+
const union = unionSessions(survivor, twins);
|
|
3466
|
+
let merged = survivor.content.replace(
|
|
3467
|
+
SOURCE_SESSIONS_LINE,
|
|
3468
|
+
`source_sessions: [${union.map((s) => JSON.stringify(s)).join(", ")}]`
|
|
3469
|
+
);
|
|
3470
|
+
if (!merged.endsWith("\n")) merged += "\n";
|
|
3471
|
+
for (const twin of twins) {
|
|
3472
|
+
const body = extractBody(twin.content).trim();
|
|
3473
|
+
if (body.length === 0) continue;
|
|
3474
|
+
merged += `
|
|
3475
|
+
## Evidence (merged from session ${twin.primarySession || "unknown"})
|
|
3476
|
+
|
|
3477
|
+
${body}
|
|
3478
|
+
`;
|
|
3479
|
+
}
|
|
3480
|
+
return merged;
|
|
3481
|
+
}
|
|
3482
|
+
function chooseSurvivor(baseSlug, group) {
|
|
3483
|
+
const exact = group.find((p) => p.name === `${baseSlug}.md`);
|
|
3484
|
+
if (exact) return exact;
|
|
3485
|
+
return [...group].sort((a, b) => {
|
|
3486
|
+
if (a.createdAt !== b.createdAt) return a.createdAt < b.createdAt ? -1 : 1;
|
|
3487
|
+
return a.name < b.name ? -1 : 1;
|
|
3488
|
+
})[0];
|
|
3489
|
+
}
|
|
3490
|
+
async function mergePendingTwins(projectRoot) {
|
|
3491
|
+
const merged = [];
|
|
3492
|
+
for (const layer of ["team", "personal"]) {
|
|
3493
|
+
let pendingBase2;
|
|
3494
|
+
try {
|
|
3495
|
+
pendingBase2 = resolveStorePendingBase(layer, projectRoot);
|
|
3496
|
+
} catch {
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
for (const type of PENDING_TYPES) {
|
|
3500
|
+
const dir = join9(pendingBase2, type);
|
|
3501
|
+
if (!existsSync4(dir)) continue;
|
|
3502
|
+
let names;
|
|
3503
|
+
try {
|
|
3504
|
+
names = (await readdir(dir)).filter((n) => n.endsWith(".md"));
|
|
3505
|
+
} catch {
|
|
3506
|
+
continue;
|
|
3507
|
+
}
|
|
3508
|
+
if (names.length < 2) continue;
|
|
3509
|
+
const present = new Set(names);
|
|
3510
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3511
|
+
for (const name of names) {
|
|
3512
|
+
const base = resolveBaseSlug(name, present);
|
|
3513
|
+
const arr = groups.get(base);
|
|
3514
|
+
if (arr) arr.push(name);
|
|
3515
|
+
else groups.set(base, [name]);
|
|
3516
|
+
}
|
|
3517
|
+
for (const [baseSlug, groupNames] of groups) {
|
|
3518
|
+
if (groupNames.length < 2) continue;
|
|
3519
|
+
const parsed = [];
|
|
3520
|
+
for (const name of groupNames) {
|
|
3521
|
+
const abs = join9(dir, name);
|
|
3522
|
+
let content;
|
|
3523
|
+
try {
|
|
3524
|
+
content = await readFile5(abs, "utf8");
|
|
3525
|
+
} catch {
|
|
3526
|
+
continue;
|
|
3527
|
+
}
|
|
3528
|
+
const sourceSessions = parseSourceSessions(content);
|
|
3529
|
+
parsed.push({
|
|
3530
|
+
name,
|
|
3531
|
+
abs,
|
|
3532
|
+
content,
|
|
3533
|
+
sourceSessions,
|
|
3534
|
+
primarySession: sourceSessions[0] ?? "",
|
|
3535
|
+
createdAt: parseCreatedAt(content)
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
if (parsed.length < 2) continue;
|
|
3539
|
+
const distinctPrimaries = new Set(parsed.map((p) => p.primarySession).filter((s) => s.length > 0));
|
|
3540
|
+
if (distinctPrimaries.size < 2) continue;
|
|
3541
|
+
const survivor = chooseSurvivor(baseSlug, parsed);
|
|
3542
|
+
const twins = parsed.filter((p) => p.abs !== survivor.abs);
|
|
3543
|
+
const mergedContent = buildMergedContent(survivor, twins);
|
|
3544
|
+
try {
|
|
3545
|
+
await atomicWriteText(survivor.abs, mergedContent);
|
|
3546
|
+
for (const twin of twins) {
|
|
3547
|
+
await unlink(twin.abs);
|
|
3548
|
+
}
|
|
3549
|
+
} catch {
|
|
3550
|
+
continue;
|
|
3551
|
+
}
|
|
3552
|
+
merged.push({
|
|
3553
|
+
layer,
|
|
3554
|
+
type,
|
|
3555
|
+
base_slug: baseSlug,
|
|
3556
|
+
survivor: survivor.abs,
|
|
3557
|
+
removed: twins.map((t) => t.abs),
|
|
3558
|
+
source_sessions: unionSessions(survivor, twins)
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
return { merged };
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
// src/services/review.ts
|
|
3423
3567
|
var PLURAL_TYPES = [
|
|
3424
3568
|
"decisions",
|
|
3425
3569
|
"pitfalls",
|
|
@@ -3526,6 +3670,10 @@ function isVisibleByLifecycle(fm, filters) {
|
|
|
3526
3670
|
return true;
|
|
3527
3671
|
}
|
|
3528
3672
|
async function listPending(projectRoot, filters) {
|
|
3673
|
+
try {
|
|
3674
|
+
await mergePendingTwins(projectRoot);
|
|
3675
|
+
} catch {
|
|
3676
|
+
}
|
|
3529
3677
|
const items = [];
|
|
3530
3678
|
const typesToScan = filters?.type !== void 0 ? [filters.type] : PLURAL_TYPES;
|
|
3531
3679
|
const sources = [];
|
|
@@ -3545,22 +3693,22 @@ async function listPending(projectRoot, filters) {
|
|
|
3545
3693
|
}
|
|
3546
3694
|
for (const source of sources) {
|
|
3547
3695
|
for (const type of typesToScan) {
|
|
3548
|
-
const dir =
|
|
3549
|
-
if (!
|
|
3696
|
+
const dir = join10(source.root, type);
|
|
3697
|
+
if (!existsSync5(dir)) {
|
|
3550
3698
|
continue;
|
|
3551
3699
|
}
|
|
3552
3700
|
let entries;
|
|
3553
3701
|
try {
|
|
3554
|
-
entries = await
|
|
3702
|
+
entries = await readdir2(dir);
|
|
3555
3703
|
} catch {
|
|
3556
3704
|
continue;
|
|
3557
3705
|
}
|
|
3558
3706
|
for (const name of entries) {
|
|
3559
3707
|
if (!name.endsWith(".md")) continue;
|
|
3560
|
-
const absolutePath =
|
|
3708
|
+
const absolutePath = join10(dir, name);
|
|
3561
3709
|
let content;
|
|
3562
3710
|
try {
|
|
3563
|
-
content = await
|
|
3711
|
+
content = await readFile6(absolutePath, "utf8");
|
|
3564
3712
|
} catch {
|
|
3565
3713
|
continue;
|
|
3566
3714
|
}
|
|
@@ -3670,7 +3818,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3670
3818
|
let targetAbs;
|
|
3671
3819
|
let writtenTarget = false;
|
|
3672
3820
|
try {
|
|
3673
|
-
const content = await
|
|
3821
|
+
const content = await readFile6(sourceAbs, "utf8");
|
|
3674
3822
|
const fm = parseFrontmatter(content);
|
|
3675
3823
|
const pluralType = fm.type;
|
|
3676
3824
|
if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
|
|
@@ -3684,14 +3832,14 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3684
3832
|
);
|
|
3685
3833
|
allocatedId = stableId;
|
|
3686
3834
|
const newFilename = `${stableId}--${slug}.md`;
|
|
3687
|
-
targetAbs =
|
|
3835
|
+
targetAbs = join10(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
|
|
3688
3836
|
await ensureParentDirectory(targetAbs);
|
|
3689
3837
|
const rewritten = rewriteFrontmatterForPromote(content, stableId);
|
|
3690
3838
|
await atomicWriteText(targetAbs, rewritten);
|
|
3691
3839
|
writtenTarget = true;
|
|
3692
3840
|
if (sourceIsStore) {
|
|
3693
|
-
if (
|
|
3694
|
-
await
|
|
3841
|
+
if (existsSync5(sourceAbs)) {
|
|
3842
|
+
await unlink2(sourceAbs);
|
|
3695
3843
|
}
|
|
3696
3844
|
} else if (sourceOrigin === "team") {
|
|
3697
3845
|
try {
|
|
@@ -3700,13 +3848,13 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3700
3848
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3701
3849
|
});
|
|
3702
3850
|
} catch {
|
|
3703
|
-
if (
|
|
3704
|
-
await
|
|
3851
|
+
if (existsSync5(sourceAbs)) {
|
|
3852
|
+
await unlink2(sourceAbs);
|
|
3705
3853
|
}
|
|
3706
3854
|
}
|
|
3707
3855
|
} else {
|
|
3708
|
-
if (
|
|
3709
|
-
await
|
|
3856
|
+
if (existsSync5(sourceAbs)) {
|
|
3857
|
+
await unlink2(sourceAbs);
|
|
3710
3858
|
}
|
|
3711
3859
|
}
|
|
3712
3860
|
await emitEventBestEffort2(projectRoot, {
|
|
@@ -3717,9 +3865,9 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3717
3865
|
});
|
|
3718
3866
|
return { pending_path: pendingPath, stable_id: stableId };
|
|
3719
3867
|
} catch (err) {
|
|
3720
|
-
if (writtenTarget && targetAbs !== void 0 &&
|
|
3868
|
+
if (writtenTarget && targetAbs !== void 0 && existsSync5(targetAbs)) {
|
|
3721
3869
|
try {
|
|
3722
|
-
await
|
|
3870
|
+
await unlink2(targetAbs);
|
|
3723
3871
|
} catch {
|
|
3724
3872
|
}
|
|
3725
3873
|
}
|
|
@@ -3738,14 +3886,14 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
|
|
|
3738
3886
|
for (const pendingPath of pendingPaths) {
|
|
3739
3887
|
try {
|
|
3740
3888
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
3741
|
-
if (
|
|
3742
|
-
const content = await
|
|
3889
|
+
if (existsSync5(sandboxed.abs)) {
|
|
3890
|
+
const content = await readFile6(sandboxed.abs, "utf8");
|
|
3743
3891
|
const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
|
|
3744
3892
|
const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
|
|
3745
3893
|
if (rejectedAbs !== null) {
|
|
3746
3894
|
await ensureParentDirectory(rejectedAbs);
|
|
3747
3895
|
await atomicWriteText(rejectedAbs, merged);
|
|
3748
|
-
await
|
|
3896
|
+
await unlink2(sandboxed.abs);
|
|
3749
3897
|
} else if (merged !== content) {
|
|
3750
3898
|
await atomicWriteText(sandboxed.abs, merged);
|
|
3751
3899
|
}
|
|
@@ -3766,7 +3914,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
|
|
|
3766
3914
|
if (target === null) {
|
|
3767
3915
|
throw new Error(`modify target not found: ${pendingPath}`);
|
|
3768
3916
|
}
|
|
3769
|
-
const content = await
|
|
3917
|
+
const content = await readFile6(target.absPath, "utf8");
|
|
3770
3918
|
const fm = parseFrontmatter(content);
|
|
3771
3919
|
const currentLayer = fm.layer ?? "team";
|
|
3772
3920
|
if (changes.semantic_scope !== void 0 && isPersonalScope2(changes.semantic_scope)) {
|
|
@@ -3811,7 +3959,7 @@ function resolveModifyTarget(projectRoot, pendingPath) {
|
|
|
3811
3959
|
} catch {
|
|
3812
3960
|
return null;
|
|
3813
3961
|
}
|
|
3814
|
-
if (
|
|
3962
|
+
if (existsSync5(sandboxed.abs)) {
|
|
3815
3963
|
return {
|
|
3816
3964
|
absPath: sandboxed.abs,
|
|
3817
3965
|
isInProjectTree: sandboxed.isInProjectTree,
|
|
@@ -3858,7 +4006,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
3858
4006
|
pluralType,
|
|
3859
4007
|
resolveWriteTargetStoreDir(toLayer, projectRoot)
|
|
3860
4008
|
);
|
|
3861
|
-
const toAbs =
|
|
4009
|
+
const toAbs = join10(
|
|
3862
4010
|
resolveStoreCanonicalBase(toLayer, projectRoot),
|
|
3863
4011
|
pluralType,
|
|
3864
4012
|
`${newStableId}--${slug}.md`
|
|
@@ -3886,12 +4034,12 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
3886
4034
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3887
4035
|
});
|
|
3888
4036
|
} catch {
|
|
3889
|
-
if (
|
|
3890
|
-
await
|
|
4037
|
+
if (existsSync5(target.absPath)) {
|
|
4038
|
+
await unlink2(target.absPath);
|
|
3891
4039
|
}
|
|
3892
4040
|
}
|
|
3893
|
-
} else if (
|
|
3894
|
-
await
|
|
4041
|
+
} else if (existsSync5(target.absPath)) {
|
|
4042
|
+
await unlink2(target.absPath);
|
|
3895
4043
|
}
|
|
3896
4044
|
const flipReason = `layer_flip:${priorStableId ?? "<unassigned>"}->${newStableId}`;
|
|
3897
4045
|
const flipTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3954,10 +4102,10 @@ function getSearchDirectoryCache(cacheKey) {
|
|
|
3954
4102
|
return created;
|
|
3955
4103
|
}
|
|
3956
4104
|
async function listIndexedSearchEntries(source, type) {
|
|
3957
|
-
const dir =
|
|
4105
|
+
const dir = join10(source.root, type);
|
|
3958
4106
|
let entries;
|
|
3959
4107
|
try {
|
|
3960
|
-
entries = await
|
|
4108
|
+
entries = await readdir2(dir);
|
|
3961
4109
|
} catch {
|
|
3962
4110
|
return [];
|
|
3963
4111
|
}
|
|
@@ -3967,7 +4115,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
3967
4115
|
const indexed = [];
|
|
3968
4116
|
for (const name of entries) {
|
|
3969
4117
|
if (!name.endsWith(".md")) continue;
|
|
3970
|
-
const absolutePath =
|
|
4118
|
+
const absolutePath = join10(dir, name);
|
|
3971
4119
|
let fingerprint;
|
|
3972
4120
|
try {
|
|
3973
4121
|
const st = await stat2(absolutePath);
|
|
@@ -3984,7 +4132,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
3984
4132
|
}
|
|
3985
4133
|
let content;
|
|
3986
4134
|
try {
|
|
3987
|
-
content = await
|
|
4135
|
+
content = await readFile6(absolutePath, "utf8");
|
|
3988
4136
|
searchEntryIndexContentReads += 1;
|
|
3989
4137
|
} catch {
|
|
3990
4138
|
directoryCache.files.delete(absolutePath);
|
|
@@ -4104,8 +4252,8 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
|
4104
4252
|
let stableId;
|
|
4105
4253
|
try {
|
|
4106
4254
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4107
|
-
if (
|
|
4108
|
-
const content = await
|
|
4255
|
+
if (existsSync5(sandboxed.abs)) {
|
|
4256
|
+
const content = await readFile6(sandboxed.abs, "utf8");
|
|
4109
4257
|
stableId = parseFrontmatter(content).id;
|
|
4110
4258
|
const patch = {
|
|
4111
4259
|
status: "deferred",
|
|
@@ -4377,9 +4525,9 @@ function registerReview(server, tracker) {
|
|
|
4377
4525
|
}
|
|
4378
4526
|
|
|
4379
4527
|
// src/services/doctor.ts
|
|
4380
|
-
import { access as access4, readFile as
|
|
4528
|
+
import { access as access4, readFile as readFile15, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
4381
4529
|
import { constants as constants2 } from "fs";
|
|
4382
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4530
|
+
import { isAbsolute as isAbsolute2, join as join20, posix as posix4, resolve as resolve3 } from "path";
|
|
4383
4531
|
import {
|
|
4384
4532
|
createTranslator,
|
|
4385
4533
|
forensicReportSchema,
|
|
@@ -4593,8 +4741,8 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
|
|
|
4593
4741
|
}
|
|
4594
4742
|
|
|
4595
4743
|
// src/services/doctor-stable-id-collision.ts
|
|
4596
|
-
import { readFile as
|
|
4597
|
-
import { basename as basename2, join as
|
|
4744
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4745
|
+
import { basename as basename2, join as join11 } from "path";
|
|
4598
4746
|
import {
|
|
4599
4747
|
buildStoreResolveInput as buildStoreResolveInput4,
|
|
4600
4748
|
createStoreResolver as createStoreResolver4,
|
|
@@ -4621,7 +4769,7 @@ function resolveIntegrityStores(projectRoot) {
|
|
|
4621
4769
|
return {
|
|
4622
4770
|
store_uuid: entry.store_uuid,
|
|
4623
4771
|
alias: entry.alias,
|
|
4624
|
-
dir:
|
|
4772
|
+
dir: join11(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
|
|
4625
4773
|
};
|
|
4626
4774
|
});
|
|
4627
4775
|
return { dirs, personalUuids };
|
|
@@ -4640,7 +4788,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
|
|
|
4640
4788
|
for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
|
|
4641
4789
|
let source;
|
|
4642
4790
|
try {
|
|
4643
|
-
source = await
|
|
4791
|
+
source = await readFile7(ref.file, "utf8");
|
|
4644
4792
|
} catch {
|
|
4645
4793
|
continue;
|
|
4646
4794
|
}
|
|
@@ -4724,8 +4872,8 @@ function createLayerMismatchCheck(t, inspection) {
|
|
|
4724
4872
|
|
|
4725
4873
|
// src/services/doctor-relevance-paths.ts
|
|
4726
4874
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4727
|
-
import { existsSync as
|
|
4728
|
-
import { join as
|
|
4875
|
+
import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
|
|
4876
|
+
import { join as join12, sep as sep3 } from "path";
|
|
4729
4877
|
import { minimatch } from "minimatch";
|
|
4730
4878
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
4731
4879
|
var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
|
|
@@ -4746,7 +4894,7 @@ function expandGlob(rawGlob) {
|
|
|
4746
4894
|
return rawGlob.endsWith("/") ? `${rawGlob}**` : rawGlob;
|
|
4747
4895
|
}
|
|
4748
4896
|
function collectWorkspacePaths(projectRoot) {
|
|
4749
|
-
if (!
|
|
4897
|
+
if (!existsSync6(projectRoot)) {
|
|
4750
4898
|
return [];
|
|
4751
4899
|
}
|
|
4752
4900
|
try {
|
|
@@ -4768,7 +4916,7 @@ function collectWorkspacePaths(projectRoot) {
|
|
|
4768
4916
|
continue;
|
|
4769
4917
|
}
|
|
4770
4918
|
for (const entry of entries) {
|
|
4771
|
-
const abs =
|
|
4919
|
+
const abs = join12(current, entry.name);
|
|
4772
4920
|
const rel = toPosix(abs.slice(projectRoot.length + 1));
|
|
4773
4921
|
if (rel.length === 0) continue;
|
|
4774
4922
|
if (entry.isDirectory()) {
|
|
@@ -4961,16 +5109,16 @@ function createNarrowNoPathsCheck(t, inspection) {
|
|
|
4961
5109
|
}
|
|
4962
5110
|
|
|
4963
5111
|
// src/services/doctor-broad-index.ts
|
|
4964
|
-
import { readFile as
|
|
4965
|
-
import { join as
|
|
5112
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5113
|
+
import { join as join13 } from "path";
|
|
4966
5114
|
var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
|
|
4967
5115
|
var BROAD_INDEX_BACKSTOP_MIN = 20;
|
|
4968
5116
|
var BROAD_INDEX_BACKSTOP_MAX = 500;
|
|
4969
5117
|
var BROAD_INDEX_DRIFT_RATIO = 0.8;
|
|
4970
5118
|
async function readBroadIndexBackstop(projectRoot) {
|
|
4971
|
-
const configPath =
|
|
5119
|
+
const configPath = join13(projectRoot, ".fabric", "fabric-config.json");
|
|
4972
5120
|
try {
|
|
4973
|
-
const raw = await
|
|
5121
|
+
const raw = await readFile8(configPath, "utf8");
|
|
4974
5122
|
const parsed = JSON.parse(raw);
|
|
4975
5123
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4976
5124
|
const v = parsed.broad_index_backstop;
|
|
@@ -5151,8 +5299,8 @@ function createStaleArchiveCheck(t, inspection) {
|
|
|
5151
5299
|
}
|
|
5152
5300
|
|
|
5153
5301
|
// src/services/doctor-scope-lint.ts
|
|
5154
|
-
import { readFile as
|
|
5155
|
-
import { join as
|
|
5302
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5303
|
+
import { join as join14 } from "path";
|
|
5156
5304
|
import {
|
|
5157
5305
|
buildStoreResolveInput as buildStoreResolveInput5,
|
|
5158
5306
|
createStoreResolver as createStoreResolver5,
|
|
@@ -5187,7 +5335,7 @@ async function resolveLintStores(projectRoot) {
|
|
|
5187
5335
|
const globalRoot = resolveGlobalRoot4();
|
|
5188
5336
|
return Promise.all(readSet.stores.map(async (entry) => {
|
|
5189
5337
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
5190
|
-
const dir =
|
|
5338
|
+
const dir = join14(
|
|
5191
5339
|
globalRoot,
|
|
5192
5340
|
storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
|
|
5193
5341
|
);
|
|
@@ -5219,7 +5367,7 @@ async function lintStoreScopes(projectRoot) {
|
|
|
5219
5367
|
}
|
|
5220
5368
|
let source;
|
|
5221
5369
|
try {
|
|
5222
|
-
source = await
|
|
5370
|
+
source = await readFile9(ref.file, "utf8");
|
|
5223
5371
|
} catch {
|
|
5224
5372
|
continue;
|
|
5225
5373
|
}
|
|
@@ -5341,8 +5489,8 @@ function createUnboundProjectCheck(t, violation) {
|
|
|
5341
5489
|
}
|
|
5342
5490
|
|
|
5343
5491
|
// src/services/doctor-store-counters.ts
|
|
5344
|
-
import { existsSync as
|
|
5345
|
-
import { join as
|
|
5492
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
5493
|
+
import { join as join15 } from "path";
|
|
5346
5494
|
import {
|
|
5347
5495
|
buildStoreResolveInput as buildStoreResolveInput6,
|
|
5348
5496
|
createStoreResolver as createStoreResolver6,
|
|
@@ -5369,7 +5517,7 @@ function resolveCounterStores(projectRoot) {
|
|
|
5369
5517
|
return readSet.stores.map((entry) => ({
|
|
5370
5518
|
uuid: entry.store_uuid,
|
|
5371
5519
|
alias: entry.alias,
|
|
5372
|
-
dir:
|
|
5520
|
+
dir: join15(
|
|
5373
5521
|
globalRoot,
|
|
5374
5522
|
storeRelativePathForMount5(
|
|
5375
5523
|
input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
|
|
@@ -5397,8 +5545,8 @@ function readEntryId(file) {
|
|
|
5397
5545
|
function computeStoreDiskMax(storeDir) {
|
|
5398
5546
|
const max = defaultAgentsMetaCounters();
|
|
5399
5547
|
for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
5400
|
-
const dir =
|
|
5401
|
-
if (!
|
|
5548
|
+
const dir = join15(storeDir, STORE_LAYOUT2.knowledgeDir, type);
|
|
5549
|
+
if (!existsSync7(dir)) {
|
|
5402
5550
|
continue;
|
|
5403
5551
|
}
|
|
5404
5552
|
let names;
|
|
@@ -5411,7 +5559,7 @@ function computeStoreDiskMax(storeDir) {
|
|
|
5411
5559
|
if (!name.endsWith(".md")) {
|
|
5412
5560
|
continue;
|
|
5413
5561
|
}
|
|
5414
|
-
const parsed = parseKnowledgeId2(readEntryId(
|
|
5562
|
+
const parsed = parseKnowledgeId2(readEntryId(join15(dir, name)) ?? "");
|
|
5415
5563
|
if (parsed === null) {
|
|
5416
5564
|
continue;
|
|
5417
5565
|
}
|
|
@@ -5494,7 +5642,7 @@ function createStoreCounterCheck(t, drifts) {
|
|
|
5494
5642
|
|
|
5495
5643
|
// src/services/doctor-store-orphan.ts
|
|
5496
5644
|
import { readdirSync as readdirSync3 } from "fs";
|
|
5497
|
-
import { join as
|
|
5645
|
+
import { join as join16 } from "path";
|
|
5498
5646
|
import {
|
|
5499
5647
|
STORES_ROOT_DIR,
|
|
5500
5648
|
addMountedStore,
|
|
@@ -5518,15 +5666,15 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
|
|
|
5518
5666
|
return [];
|
|
5519
5667
|
}
|
|
5520
5668
|
const registered = new Set(config.stores.map((s) => s.store_uuid));
|
|
5521
|
-
const storesRoot =
|
|
5669
|
+
const storesRoot = join16(globalRoot, STORES_ROOT_DIR);
|
|
5522
5670
|
const orphans = [];
|
|
5523
5671
|
for (const group of listDir(storesRoot)) {
|
|
5524
5672
|
if (group === STORE_BY_ALIAS_DIR) {
|
|
5525
5673
|
continue;
|
|
5526
5674
|
}
|
|
5527
|
-
const groupDir =
|
|
5675
|
+
const groupDir = join16(storesRoot, group);
|
|
5528
5676
|
for (const mount of listDir(groupDir)) {
|
|
5529
|
-
const dir =
|
|
5677
|
+
const dir = join16(groupDir, mount);
|
|
5530
5678
|
const identity = readStoreIdentity(dir);
|
|
5531
5679
|
if (identity === null || registered.has(identity.store_uuid)) {
|
|
5532
5680
|
continue;
|
|
@@ -5599,7 +5747,7 @@ import {
|
|
|
5599
5747
|
|
|
5600
5748
|
// src/services/events-jsonl-gates.ts
|
|
5601
5749
|
import { promises as fs } from "fs";
|
|
5602
|
-
import { existsSync as
|
|
5750
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5603
5751
|
var EVENTS_JSONL_SIZE_WARN_BYTES = 10 * 1024 * 1024;
|
|
5604
5752
|
var METRICS_STALE_WARN_MS = 10 * 60 * 1e3;
|
|
5605
5753
|
var ROTATION_OVERDUE_WARN_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
@@ -5621,7 +5769,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
5621
5769
|
if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
|
|
5622
5770
|
}
|
|
5623
5771
|
let metricsStalenessMs = null;
|
|
5624
|
-
if (
|
|
5772
|
+
if (existsSync8(metricsPath)) {
|
|
5625
5773
|
try {
|
|
5626
5774
|
const stat4 = await fs.stat(metricsPath);
|
|
5627
5775
|
metricsStalenessMs = Math.max(0, now.getTime() - stat4.mtimeMs);
|
|
@@ -5663,8 +5811,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
5663
5811
|
}
|
|
5664
5812
|
|
|
5665
5813
|
// src/services/doctor-skill-lints.ts
|
|
5666
|
-
import { readdir as
|
|
5667
|
-
import { join as
|
|
5814
|
+
import { readdir as readdir3, readFile as readFile10 } from "fs/promises";
|
|
5815
|
+
import { join as join17, posix as posix2 } from "path";
|
|
5668
5816
|
var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review", "fabric-import"];
|
|
5669
5817
|
var ROUTER_VALID_LEAF_SLUGS = /* @__PURE__ */ new Set([
|
|
5670
5818
|
"fabric-archive",
|
|
@@ -5695,7 +5843,7 @@ function issueCheck(name, status, kind, code, message, actionHint, audience) {
|
|
|
5695
5843
|
}
|
|
5696
5844
|
async function listMarkdownFiles(dir) {
|
|
5697
5845
|
try {
|
|
5698
|
-
return (await
|
|
5846
|
+
return (await readdir3(dir)).filter((name) => name.endsWith(".md"));
|
|
5699
5847
|
} catch {
|
|
5700
5848
|
return null;
|
|
5701
5849
|
}
|
|
@@ -5703,8 +5851,8 @@ async function listMarkdownFiles(dir) {
|
|
|
5703
5851
|
async function inspectSkillRefMirror(projectRoot) {
|
|
5704
5852
|
const driftedPaths = [];
|
|
5705
5853
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
5706
|
-
const claudeRef =
|
|
5707
|
-
const codexRef =
|
|
5854
|
+
const claudeRef = join17(projectRoot, ".claude", "skills", slug, "ref");
|
|
5855
|
+
const codexRef = join17(projectRoot, ".codex", "skills", slug, "ref");
|
|
5708
5856
|
const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
|
|
5709
5857
|
if (claudeFiles === null || codexFiles === null) continue;
|
|
5710
5858
|
const claudeSet = new Set(claudeFiles);
|
|
@@ -5721,8 +5869,8 @@ async function inspectSkillRefMirror(projectRoot) {
|
|
|
5721
5869
|
let codexBody;
|
|
5722
5870
|
try {
|
|
5723
5871
|
[claudeBody, codexBody] = await Promise.all([
|
|
5724
|
-
|
|
5725
|
-
|
|
5872
|
+
readFile10(join17(claudeRef, fname), "utf8"),
|
|
5873
|
+
readFile10(join17(codexRef, fname), "utf8")
|
|
5726
5874
|
]);
|
|
5727
5875
|
} catch {
|
|
5728
5876
|
continue;
|
|
@@ -5741,10 +5889,10 @@ async function inspectSkillTokenBudget(projectRoot) {
|
|
|
5741
5889
|
const overSize = [];
|
|
5742
5890
|
let highestSeverity = "ok";
|
|
5743
5891
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
5744
|
-
const skillMdPath =
|
|
5892
|
+
const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
5745
5893
|
let body;
|
|
5746
5894
|
try {
|
|
5747
|
-
body = await
|
|
5895
|
+
body = await readFile10(skillMdPath, "utf8");
|
|
5748
5896
|
} catch {
|
|
5749
5897
|
continue;
|
|
5750
5898
|
}
|
|
@@ -5765,10 +5913,10 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
5765
5913
|
const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
|
|
5766
5914
|
const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
|
|
5767
5915
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
5768
|
-
const skillMdPath =
|
|
5916
|
+
const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
5769
5917
|
let body;
|
|
5770
5918
|
try {
|
|
5771
|
-
body = await
|
|
5919
|
+
body = await readFile10(skillMdPath, "utf8");
|
|
5772
5920
|
} catch {
|
|
5773
5921
|
continue;
|
|
5774
5922
|
}
|
|
@@ -5799,19 +5947,19 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
5799
5947
|
async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
5800
5948
|
const candidates = [];
|
|
5801
5949
|
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
5802
|
-
const rootAbs =
|
|
5950
|
+
const rootAbs = join17(projectRoot, rootRel);
|
|
5803
5951
|
let dirEntries;
|
|
5804
5952
|
try {
|
|
5805
|
-
dirEntries = await
|
|
5953
|
+
dirEntries = await readdir3(rootAbs, { withFileTypes: true });
|
|
5806
5954
|
} catch {
|
|
5807
5955
|
continue;
|
|
5808
5956
|
}
|
|
5809
5957
|
for (const dirEntry of dirEntries) {
|
|
5810
5958
|
if (!dirEntry.isDirectory()) continue;
|
|
5811
|
-
const skillFile =
|
|
5959
|
+
const skillFile = join17(rootAbs, dirEntry.name, "SKILL.md");
|
|
5812
5960
|
let raw;
|
|
5813
5961
|
try {
|
|
5814
|
-
raw = await
|
|
5962
|
+
raw = await readFile10(skillFile, "utf8");
|
|
5815
5963
|
} catch {
|
|
5816
5964
|
continue;
|
|
5817
5965
|
}
|
|
@@ -5880,13 +6028,13 @@ function extractMarkdownSectionBody(markdown, sectionName) {
|
|
|
5880
6028
|
}
|
|
5881
6029
|
async function inspectRouterChainRef(projectRoot) {
|
|
5882
6030
|
const candidatePaths = [
|
|
5883
|
-
|
|
5884
|
-
|
|
6031
|
+
join17(projectRoot, ".claude", "skills", "fabric", "SKILL.md"),
|
|
6032
|
+
join17(projectRoot, ".codex", "skills", "fabric", "SKILL.md")
|
|
5885
6033
|
];
|
|
5886
6034
|
let body = null;
|
|
5887
6035
|
for (const candidate of candidatePaths) {
|
|
5888
6036
|
try {
|
|
5889
|
-
body = await
|
|
6037
|
+
body = await readFile10(candidate, "utf8");
|
|
5890
6038
|
break;
|
|
5891
6039
|
} catch {
|
|
5892
6040
|
}
|
|
@@ -5997,8 +6145,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
|
|
|
5997
6145
|
|
|
5998
6146
|
// src/services/doctor-hooks-lints.ts
|
|
5999
6147
|
import { constants } from "fs";
|
|
6000
|
-
import { access, readdir as
|
|
6001
|
-
import { join as
|
|
6148
|
+
import { access, readdir as readdir4, readFile as readFile11, stat as stat3 } from "fs/promises";
|
|
6149
|
+
import { join as join18, posix as posix3 } from "path";
|
|
6002
6150
|
import { Script } from "vm";
|
|
6003
6151
|
var HOOKS_RUNTIME_CLIENT_DIRS = [
|
|
6004
6152
|
{ client: "claude", dir: ".claude/hooks" },
|
|
@@ -6047,7 +6195,7 @@ function isHookWiredForEvent(hooks, event, hookFile) {
|
|
|
6047
6195
|
}
|
|
6048
6196
|
async function readDirectoryFileNames(dir) {
|
|
6049
6197
|
try {
|
|
6050
|
-
return await
|
|
6198
|
+
return await readdir4(dir);
|
|
6051
6199
|
} catch {
|
|
6052
6200
|
return null;
|
|
6053
6201
|
}
|
|
@@ -6060,14 +6208,14 @@ async function isFile(absPath) {
|
|
|
6060
6208
|
}
|
|
6061
6209
|
}
|
|
6062
6210
|
async function inspectHooksWired(projectRoot) {
|
|
6063
|
-
const claudeEntries = await readDirectoryFileNames(
|
|
6211
|
+
const claudeEntries = await readDirectoryFileNames(join18(projectRoot, ".claude"));
|
|
6064
6212
|
if (claudeEntries === null) {
|
|
6065
6213
|
return { status: "skipped", missingHooks: [] };
|
|
6066
6214
|
}
|
|
6067
|
-
const settingsPath =
|
|
6215
|
+
const settingsPath = join18(projectRoot, ".claude", "settings.json");
|
|
6068
6216
|
let parsed;
|
|
6069
6217
|
try {
|
|
6070
|
-
parsed = JSON.parse(await
|
|
6218
|
+
parsed = JSON.parse(await readFile11(settingsPath, "utf8"));
|
|
6071
6219
|
} catch {
|
|
6072
6220
|
return { status: "missing-settings", missingHooks: [] };
|
|
6073
6221
|
}
|
|
@@ -6090,8 +6238,8 @@ async function inspectHooksWired(projectRoot) {
|
|
|
6090
6238
|
}
|
|
6091
6239
|
async function inspectHookCacheWritability(projectRoot) {
|
|
6092
6240
|
const relPath = posix3.join(".fabric", ".cache");
|
|
6093
|
-
const fabricDir =
|
|
6094
|
-
const cacheDir =
|
|
6241
|
+
const fabricDir = join18(projectRoot, ".fabric");
|
|
6242
|
+
const cacheDir = join18(projectRoot, ".fabric", ".cache");
|
|
6095
6243
|
try {
|
|
6096
6244
|
try {
|
|
6097
6245
|
const cacheStats = await stat3(cacheDir);
|
|
@@ -6140,12 +6288,12 @@ async function inspectHookCacheWritability(projectRoot) {
|
|
|
6140
6288
|
async function inspectHooksContentDrift(projectRoot) {
|
|
6141
6289
|
const hookFilesByBasename = /* @__PURE__ */ new Map();
|
|
6142
6290
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
6143
|
-
const absDir =
|
|
6291
|
+
const absDir = join18(projectRoot, dir);
|
|
6144
6292
|
const entries = await readDirectoryFileNames(absDir);
|
|
6145
6293
|
if (entries === null) continue;
|
|
6146
6294
|
for (const name of entries) {
|
|
6147
6295
|
if (!name.endsWith(".cjs")) continue;
|
|
6148
|
-
const abs =
|
|
6296
|
+
const abs = join18(absDir, name);
|
|
6149
6297
|
if (!await isFile(abs)) continue;
|
|
6150
6298
|
const arr = hookFilesByBasename.get(name) ?? [];
|
|
6151
6299
|
arr.push({ client, abs });
|
|
@@ -6160,7 +6308,7 @@ async function inspectHooksContentDrift(projectRoot) {
|
|
|
6160
6308
|
const hashes = [];
|
|
6161
6309
|
for (const { client, abs } of copies) {
|
|
6162
6310
|
try {
|
|
6163
|
-
const body = await
|
|
6311
|
+
const body = await readFile11(abs, "utf8");
|
|
6164
6312
|
hashes.push({ client, sha: sha256(body) });
|
|
6165
6313
|
} catch {
|
|
6166
6314
|
}
|
|
@@ -6182,18 +6330,18 @@ async function inspectHooksRuntime(projectRoot) {
|
|
|
6182
6330
|
const issues = [];
|
|
6183
6331
|
let scanned = 0;
|
|
6184
6332
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
6185
|
-
const absDir =
|
|
6333
|
+
const absDir = join18(projectRoot, dir);
|
|
6186
6334
|
const entries = await readDirectoryFileNames(absDir);
|
|
6187
6335
|
if (entries === null) continue;
|
|
6188
6336
|
for (const name of entries) {
|
|
6189
6337
|
if (!name.endsWith(".cjs")) continue;
|
|
6190
|
-
const abs =
|
|
6338
|
+
const abs = join18(absDir, name);
|
|
6191
6339
|
const displayPath = `${dir}/${name}`;
|
|
6192
6340
|
if (!await isFile(abs)) continue;
|
|
6193
6341
|
scanned += 1;
|
|
6194
6342
|
let body;
|
|
6195
6343
|
try {
|
|
6196
|
-
body = await
|
|
6344
|
+
body = await readFile11(abs, "utf8");
|
|
6197
6345
|
} catch (err) {
|
|
6198
6346
|
issues.push({
|
|
6199
6347
|
path: displayPath,
|
|
@@ -6330,8 +6478,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
|
|
|
6330
6478
|
}
|
|
6331
6479
|
|
|
6332
6480
|
// src/services/doctor-bootstrap-lints.ts
|
|
6333
|
-
import { access as access2, readFile as
|
|
6334
|
-
import { join as
|
|
6481
|
+
import { access as access2, readFile as readFile12 } from "fs/promises";
|
|
6482
|
+
import { join as join19 } from "path";
|
|
6335
6483
|
import {
|
|
6336
6484
|
BOOTSTRAP_MARKER_BEGIN,
|
|
6337
6485
|
BOOTSTRAP_MARKER_END,
|
|
@@ -6363,17 +6511,17 @@ async function fileExists(path) {
|
|
|
6363
6511
|
}
|
|
6364
6512
|
async function inspectBootstrapAnchor(projectRoot) {
|
|
6365
6513
|
const [hasAgentsMd, hasClaudeMd] = await Promise.all([
|
|
6366
|
-
fileExists(
|
|
6367
|
-
fileExists(
|
|
6514
|
+
fileExists(join19(projectRoot, "AGENTS.md")),
|
|
6515
|
+
fileExists(join19(projectRoot, "CLAUDE.md"))
|
|
6368
6516
|
]);
|
|
6369
6517
|
return { hasAgentsMd, hasClaudeMd };
|
|
6370
6518
|
}
|
|
6371
6519
|
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
6372
|
-
const abs =
|
|
6520
|
+
const abs = join19(target, ".fabric", "AGENTS.md");
|
|
6373
6521
|
const canonical = resolveBootstrapCanonical();
|
|
6374
6522
|
let onDisk;
|
|
6375
6523
|
try {
|
|
6376
|
-
onDisk = await
|
|
6524
|
+
onDisk = await readFile12(abs, "utf8");
|
|
6377
6525
|
} catch {
|
|
6378
6526
|
return { status: "missing", canonical, onDisk: null };
|
|
6379
6527
|
}
|
|
@@ -6399,17 +6547,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
|
|
|
6399
6547
|
);
|
|
6400
6548
|
}
|
|
6401
6549
|
async function inspectL2ManagedBlockDrift(target) {
|
|
6402
|
-
const snapshotPath =
|
|
6550
|
+
const snapshotPath = join19(target, ".fabric", "AGENTS.md");
|
|
6403
6551
|
let snapshot;
|
|
6404
6552
|
try {
|
|
6405
|
-
snapshot = await
|
|
6553
|
+
snapshot = await readFile12(snapshotPath, "utf8");
|
|
6406
6554
|
} catch {
|
|
6407
6555
|
return { status: "ok", drifted: [] };
|
|
6408
6556
|
}
|
|
6409
|
-
const projectRulesPath =
|
|
6557
|
+
const projectRulesPath = join19(target, ".fabric", "project-rules.md");
|
|
6410
6558
|
let expectedBody = snapshot;
|
|
6411
6559
|
try {
|
|
6412
|
-
const projectRules = await
|
|
6560
|
+
const projectRules = await readFile12(projectRulesPath, "utf8");
|
|
6413
6561
|
expectedBody = `${snapshot}
|
|
6414
6562
|
---
|
|
6415
6563
|
${projectRules}`;
|
|
@@ -6418,12 +6566,12 @@ ${projectRules}`;
|
|
|
6418
6566
|
const drifted = [];
|
|
6419
6567
|
let anyManagedBlockFound = false;
|
|
6420
6568
|
const blockTargets = [
|
|
6421
|
-
|
|
6569
|
+
join19(target, "AGENTS.md")
|
|
6422
6570
|
];
|
|
6423
6571
|
for (const abs of blockTargets) {
|
|
6424
6572
|
let content;
|
|
6425
6573
|
try {
|
|
6426
|
-
content = await
|
|
6574
|
+
content = await readFile12(abs, "utf8");
|
|
6427
6575
|
} catch {
|
|
6428
6576
|
continue;
|
|
6429
6577
|
}
|
|
@@ -6446,9 +6594,9 @@ ${projectRules}`;
|
|
|
6446
6594
|
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
6447
6595
|
}
|
|
6448
6596
|
}
|
|
6449
|
-
const claudeMdPath =
|
|
6597
|
+
const claudeMdPath = join19(target, "CLAUDE.md");
|
|
6450
6598
|
try {
|
|
6451
|
-
const claudeContent = await
|
|
6599
|
+
const claudeContent = await readFile12(claudeMdPath, "utf8");
|
|
6452
6600
|
anyManagedBlockFound = true;
|
|
6453
6601
|
const lines = claudeContent.split(/\r?\n/u);
|
|
6454
6602
|
const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
|
|
@@ -6516,7 +6664,7 @@ import { appendFile as appendFile3 } from "fs/promises";
|
|
|
6516
6664
|
import { minimatch as minimatch2 } from "minimatch";
|
|
6517
6665
|
|
|
6518
6666
|
// src/services/cite-rollup.ts
|
|
6519
|
-
import { readFile as
|
|
6667
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
6520
6668
|
import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
6521
6669
|
var citeRollupQueue = createLedgerWriteQueue3();
|
|
6522
6670
|
async function appendCiteRollupRow(projectRoot, row) {
|
|
@@ -6528,7 +6676,7 @@ async function readCiteRollup(projectRoot) {
|
|
|
6528
6676
|
const path = getCiteRollupPath(projectRoot);
|
|
6529
6677
|
let raw;
|
|
6530
6678
|
try {
|
|
6531
|
-
raw = await
|
|
6679
|
+
raw = await readFile13(path, "utf8");
|
|
6532
6680
|
} catch (error) {
|
|
6533
6681
|
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
6534
6682
|
throw error;
|
|
@@ -7653,7 +7801,7 @@ async function runDoctorReport(target) {
|
|
|
7653
7801
|
const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
|
|
7654
7802
|
const targetFiles = Object.fromEntries(
|
|
7655
7803
|
await Promise.all(
|
|
7656
|
-
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(
|
|
7804
|
+
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join20(projectRoot, path))])
|
|
7657
7805
|
)
|
|
7658
7806
|
);
|
|
7659
7807
|
const checks = [
|
|
@@ -7853,7 +8001,7 @@ async function runDoctorFix(target) {
|
|
|
7853
8001
|
const fixed = [];
|
|
7854
8002
|
const ledgerWarnings = [];
|
|
7855
8003
|
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
7856
|
-
const snapshotPath =
|
|
8004
|
+
const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
|
|
7857
8005
|
await ensureParentDirectory(snapshotPath);
|
|
7858
8006
|
await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
|
|
7859
8007
|
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
@@ -7926,9 +8074,9 @@ async function runDoctorFix(target) {
|
|
|
7926
8074
|
if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
|
|
7927
8075
|
const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
|
|
7928
8076
|
if (lockInspection.present && !lockInspection.pidAlive) {
|
|
7929
|
-
const lockFilePath =
|
|
8077
|
+
const lockFilePath = join20(projectRoot, ".fabric", ".serve.lock");
|
|
7930
8078
|
try {
|
|
7931
|
-
await
|
|
8079
|
+
await unlink4(lockFilePath);
|
|
7932
8080
|
} catch (err) {
|
|
7933
8081
|
const errno = err;
|
|
7934
8082
|
if (errno.code !== "ENOENT") throw err;
|
|
@@ -8043,10 +8191,10 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
|
|
|
8043
8191
|
}
|
|
8044
8192
|
async function applySessionHintsStaleCleanup(projectRoot, candidate) {
|
|
8045
8193
|
const detail = `deleted (${candidate.age_days}d old)`;
|
|
8046
|
-
const absPath =
|
|
8194
|
+
const absPath = join20(projectRoot, candidate.path);
|
|
8047
8195
|
try {
|
|
8048
|
-
const { unlink:
|
|
8049
|
-
await
|
|
8196
|
+
const { unlink: unlink5 } = await import("fs/promises");
|
|
8197
|
+
await unlink5(absPath);
|
|
8050
8198
|
return {
|
|
8051
8199
|
kind: "knowledge_session_hints_stale_cleanup",
|
|
8052
8200
|
path: candidate.path,
|
|
@@ -8068,9 +8216,9 @@ function truncateErrorMessage(error) {
|
|
|
8068
8216
|
return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
|
|
8069
8217
|
}
|
|
8070
8218
|
async function inspectForensic(projectRoot) {
|
|
8071
|
-
const path =
|
|
8219
|
+
const path = join20(projectRoot, ".fabric", "forensic.json");
|
|
8072
8220
|
try {
|
|
8073
|
-
const parsed = forensicReportSchema.parse(JSON.parse(await
|
|
8221
|
+
const parsed = forensicReportSchema.parse(JSON.parse(await readFile15(path, "utf8")));
|
|
8074
8222
|
return { present: true, valid: true, report: parsed };
|
|
8075
8223
|
} catch (error) {
|
|
8076
8224
|
if (isMissingFileError(error)) {
|
|
@@ -8100,7 +8248,7 @@ async function inspectEventLedger(projectRoot) {
|
|
|
8100
8248
|
try {
|
|
8101
8249
|
await access4(path, constants2.W_OK);
|
|
8102
8250
|
const { warnings } = await readEventLedger(projectRoot);
|
|
8103
|
-
const raw = await
|
|
8251
|
+
const raw = await readFile15(path, "utf8");
|
|
8104
8252
|
const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
|
|
8105
8253
|
const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
|
|
8106
8254
|
const schemaVersionSamples = [];
|
|
@@ -8645,7 +8793,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
|
|
|
8645
8793
|
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
8646
8794
|
const detected = [];
|
|
8647
8795
|
for (const name of candidates) {
|
|
8648
|
-
if (await pathExists(
|
|
8796
|
+
if (await pathExists(join20(projectRoot, name))) {
|
|
8649
8797
|
detected.push(name);
|
|
8650
8798
|
}
|
|
8651
8799
|
}
|
|
@@ -8730,7 +8878,7 @@ async function buildLastActiveIndex(projectRoot) {
|
|
|
8730
8878
|
return map;
|
|
8731
8879
|
}
|
|
8732
8880
|
async function inspectSessionHintsStale(projectRoot, now) {
|
|
8733
|
-
const cacheDir =
|
|
8881
|
+
const cacheDir = join20(projectRoot, ".fabric", ".cache");
|
|
8734
8882
|
let entries;
|
|
8735
8883
|
try {
|
|
8736
8884
|
entries = await readdirAsync(cacheDir, { withFileTypes: true });
|
|
@@ -8742,7 +8890,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
|
|
|
8742
8890
|
if (!entry.isFile()) continue;
|
|
8743
8891
|
if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
|
|
8744
8892
|
if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
|
|
8745
|
-
const absPath =
|
|
8893
|
+
const absPath = join20(cacheDir, entry.name);
|
|
8746
8894
|
let mtimeMs = 0;
|
|
8747
8895
|
try {
|
|
8748
8896
|
mtimeMs = (await statAsync(absPath)).mtimeMs;
|
|
@@ -8774,9 +8922,9 @@ function inspectStaleServeLock(projectRoot, now) {
|
|
|
8774
8922
|
};
|
|
8775
8923
|
}
|
|
8776
8924
|
async function readUnderseedThresholdFromConfig(projectRoot) {
|
|
8777
|
-
const configPath =
|
|
8925
|
+
const configPath = join20(projectRoot, ".fabric", "fabric-config.json");
|
|
8778
8926
|
try {
|
|
8779
|
-
const raw = await
|
|
8927
|
+
const raw = await readFile15(configPath, "utf8");
|
|
8780
8928
|
const parsed = JSON.parse(raw);
|
|
8781
8929
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
8782
8930
|
const v = parsed.underseed_node_threshold;
|
|
@@ -8892,10 +9040,10 @@ async function inspectOnboardCoverage(projectRoot) {
|
|
|
8892
9040
|
return { filled, missing, opted_out: optedOut };
|
|
8893
9041
|
}
|
|
8894
9042
|
async function readOnboardOptedOut(projectRoot) {
|
|
8895
|
-
const path =
|
|
9043
|
+
const path = join20(projectRoot, ".fabric", "fabric-config.json");
|
|
8896
9044
|
let raw;
|
|
8897
9045
|
try {
|
|
8898
|
-
raw = await
|
|
9046
|
+
raw = await readFile15(path, "utf8");
|
|
8899
9047
|
} catch {
|
|
8900
9048
|
return [];
|
|
8901
9049
|
}
|
|
@@ -8997,22 +9145,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
|
|
|
8997
9145
|
}
|
|
8998
9146
|
}
|
|
8999
9147
|
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
9000
|
-
const snapshotPath =
|
|
9148
|
+
const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
|
|
9001
9149
|
if (!await pathExists(snapshotPath)) {
|
|
9002
9150
|
return;
|
|
9003
9151
|
}
|
|
9004
9152
|
let snapshot;
|
|
9005
9153
|
try {
|
|
9006
|
-
snapshot = await
|
|
9154
|
+
snapshot = await readFile15(snapshotPath, "utf8");
|
|
9007
9155
|
} catch {
|
|
9008
9156
|
return;
|
|
9009
9157
|
}
|
|
9010
|
-
const projectRulesPath =
|
|
9158
|
+
const projectRulesPath = join20(projectRoot, ".fabric", "project-rules.md");
|
|
9011
9159
|
const hasProjectRules = await pathExists(projectRulesPath);
|
|
9012
9160
|
let expectedBody = snapshot;
|
|
9013
9161
|
if (hasProjectRules) {
|
|
9014
9162
|
try {
|
|
9015
|
-
const projectRules = await
|
|
9163
|
+
const projectRules = await readFile15(projectRulesPath, "utf8");
|
|
9016
9164
|
expectedBody = `${snapshot}
|
|
9017
9165
|
---
|
|
9018
9166
|
${projectRules}`;
|
|
@@ -9023,7 +9171,7 @@ ${projectRules}`;
|
|
|
9023
9171
|
${expectedBody}
|
|
9024
9172
|
${BOOTSTRAP_MARKER_END2}`;
|
|
9025
9173
|
const blockTargets = [
|
|
9026
|
-
|
|
9174
|
+
join20(projectRoot, "AGENTS.md")
|
|
9027
9175
|
];
|
|
9028
9176
|
for (const abs of blockTargets) {
|
|
9029
9177
|
if (!await pathExists(abs)) {
|
|
@@ -9031,7 +9179,7 @@ ${BOOTSTRAP_MARKER_END2}`;
|
|
|
9031
9179
|
}
|
|
9032
9180
|
let existing;
|
|
9033
9181
|
try {
|
|
9034
|
-
existing = await
|
|
9182
|
+
existing = await readFile15(abs, "utf8");
|
|
9035
9183
|
} catch {
|
|
9036
9184
|
continue;
|
|
9037
9185
|
}
|
|
@@ -9056,11 +9204,11 @@ ${managedBlock}
|
|
|
9056
9204
|
}
|
|
9057
9205
|
await atomicWriteText4(abs, next);
|
|
9058
9206
|
}
|
|
9059
|
-
const claudeMdPath =
|
|
9207
|
+
const claudeMdPath = join20(projectRoot, "CLAUDE.md");
|
|
9060
9208
|
if (await pathExists(claudeMdPath)) {
|
|
9061
9209
|
let claudeContent;
|
|
9062
9210
|
try {
|
|
9063
|
-
claudeContent = await
|
|
9211
|
+
claudeContent = await readFile15(claudeMdPath, "utf8");
|
|
9064
9212
|
} catch {
|
|
9065
9213
|
return;
|
|
9066
9214
|
}
|
|
@@ -9126,7 +9274,7 @@ async function collectEntryPoints(root) {
|
|
|
9126
9274
|
continue;
|
|
9127
9275
|
}
|
|
9128
9276
|
for (const entry of await readdirAsync(current, { withFileTypes: true })) {
|
|
9129
|
-
const absolutePath =
|
|
9277
|
+
const absolutePath = join20(current, entry.name);
|
|
9130
9278
|
const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
|
|
9131
9279
|
if (relativePath.length === 0) {
|
|
9132
9280
|
continue;
|
|
@@ -9202,7 +9350,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
|
|
|
9202
9350
|
scanned += 1;
|
|
9203
9351
|
let source;
|
|
9204
9352
|
try {
|
|
9205
|
-
source = await
|
|
9353
|
+
source = await readFile15(absPath, "utf8");
|
|
9206
9354
|
} catch {
|
|
9207
9355
|
continue;
|
|
9208
9356
|
}
|
|
@@ -9475,7 +9623,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
|
|
|
9475
9623
|
|
|
9476
9624
|
// src/services/read-ledger.ts
|
|
9477
9625
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
9478
|
-
import { access as access5, copyFile, readFile as
|
|
9626
|
+
import { access as access5, copyFile, readFile as readFile16, rm } from "fs/promises";
|
|
9479
9627
|
import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
9480
9628
|
async function resolveLedgerPaths(projectRoot) {
|
|
9481
9629
|
const primaryPath = getLedgerPath(projectRoot);
|
|
@@ -9503,7 +9651,7 @@ async function readLegacyLedger(projectRoot) {
|
|
|
9503
9651
|
const { readPath } = await resolveLedgerPaths(projectRoot);
|
|
9504
9652
|
let raw;
|
|
9505
9653
|
try {
|
|
9506
|
-
raw = await
|
|
9654
|
+
raw = await readFile16(readPath, "utf8");
|
|
9507
9655
|
} catch (error) {
|
|
9508
9656
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
9509
9657
|
return [];
|
|
@@ -9758,8 +9906,8 @@ function formatError(error) {
|
|
|
9758
9906
|
}
|
|
9759
9907
|
function formatPreexistingRootMessage(projectRoot) {
|
|
9760
9908
|
const preexisting = [];
|
|
9761
|
-
if (
|
|
9762
|
-
if (
|
|
9909
|
+
if (existsSync9(join21(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
|
|
9910
|
+
if (existsSync9(join21(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
|
|
9763
9911
|
if (preexisting.length === 0) return null;
|
|
9764
9912
|
return `[startup] info: detected ${preexisting.join(", ")} at project root. Note: Fabric serves knowledge from mounted stores via MCP \u2014 root markdown files are not auto-loaded into the AI context.`;
|
|
9765
9913
|
}
|
|
@@ -9785,7 +9933,7 @@ function createFabricServer(tracker) {
|
|
|
9785
9933
|
const server = new McpServer(
|
|
9786
9934
|
{
|
|
9787
9935
|
name: "fabric-knowledge-server",
|
|
9788
|
-
version: "2.2.0-rc.
|
|
9936
|
+
version: "2.2.0-rc.11"
|
|
9789
9937
|
},
|
|
9790
9938
|
{
|
|
9791
9939
|
instructions: FABRIC_SERVER_INSTRUCTIONS
|
|
@@ -9804,10 +9952,10 @@ function createFabricServer(tracker) {
|
|
|
9804
9952
|
},
|
|
9805
9953
|
async (_uri) => {
|
|
9806
9954
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
9807
|
-
const path =
|
|
9955
|
+
const path = join21(projectRoot, ".fabric", "bootstrap", "README.md");
|
|
9808
9956
|
let text = "";
|
|
9809
|
-
if (
|
|
9810
|
-
text = await
|
|
9957
|
+
if (existsSync9(path)) {
|
|
9958
|
+
text = await readFile17(path, "utf8");
|
|
9811
9959
|
}
|
|
9812
9960
|
return {
|
|
9813
9961
|
contents: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-server",
|
|
3
|
-
"version": "2.2.0-rc.
|
|
3
|
+
"version": "2.2.0-rc.11",
|
|
4
4
|
"description": "Fabric MCP knowledge server — stdio transport for Claude Code / Codex CLI, manages .fabric/ knowledge base + agents.meta.json + event ledger.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wangzhichao <fenglimg90@gmail.com>",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38
38
|
"minimatch": "^10.0.1",
|
|
39
39
|
"zod": "^3.25.0",
|
|
40
|
-
"@fenglimg/fabric-shared": "2.2.0-rc.
|
|
40
|
+
"@fenglimg/fabric-shared": "2.2.0-rc.11"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.15.0",
|