@fenglimg/fabric-server 2.2.0-rc.9 → 2.2.0
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.d.ts +3 -0
- package/dist/index.js +308 -151
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,9 @@ interface KnowledgeCensus {
|
|
|
62
62
|
personal: number;
|
|
63
63
|
project: number;
|
|
64
64
|
};
|
|
65
|
+
broad_by_type: Record<string, number>;
|
|
66
|
+
/** count of narrow-scope kept entries (file-specific; only合计, not per-type). */
|
|
67
|
+
narrow_total: number;
|
|
65
68
|
/** entries专属 to OTHER projects that filterByActiveProject removed. */
|
|
66
69
|
dropped_other_project: number;
|
|
67
70
|
/** kept (post-filter) total. */
|
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";
|
|
@@ -2028,6 +2028,8 @@ async function buildKnowledgeCensus(projectRoot) {
|
|
|
2028
2028
|
const census = {
|
|
2029
2029
|
by_type: {},
|
|
2030
2030
|
by_layer: { team: 0, personal: 0, project: 0 },
|
|
2031
|
+
broad_by_type: {},
|
|
2032
|
+
narrow_total: 0,
|
|
2031
2033
|
dropped_other_project: 0,
|
|
2032
2034
|
total: 0
|
|
2033
2035
|
};
|
|
@@ -2037,10 +2039,16 @@ async function buildKnowledgeCensus(projectRoot) {
|
|
|
2037
2039
|
const kept = filterByActiveProject(all, activeProject);
|
|
2038
2040
|
census.dropped_other_project = all.length - kept.length;
|
|
2039
2041
|
for (const entry of kept) {
|
|
2040
|
-
const
|
|
2042
|
+
const desc = extractRuleDescription(entry.source);
|
|
2043
|
+
const type = desc?.knowledge_type;
|
|
2044
|
+
const isNarrow = desc?.relevance_scope === "narrow";
|
|
2041
2045
|
if (typeof type === "string") {
|
|
2042
2046
|
census.by_type[type] = (census.by_type[type] ?? 0) + 1;
|
|
2047
|
+
if (!isNarrow) {
|
|
2048
|
+
census.broad_by_type[type] = (census.broad_by_type[type] ?? 0) + 1;
|
|
2049
|
+
}
|
|
2043
2050
|
}
|
|
2051
|
+
if (isNarrow) census.narrow_total += 1;
|
|
2044
2052
|
if (scopeRoot(entry.semanticScope) === "project") {
|
|
2045
2053
|
census.by_layer.project += 1;
|
|
2046
2054
|
} else {
|
|
@@ -2064,6 +2072,7 @@ async function buildAlwaysActiveBodies(projectRoot) {
|
|
|
2064
2072
|
if (desc === void 0) continue;
|
|
2065
2073
|
const type = desc.knowledge_type;
|
|
2066
2074
|
if (typeof type !== "string" || !ALWAYS_ACTIVE_TYPES.has(type)) continue;
|
|
2075
|
+
if (desc.relevance_scope === "narrow") continue;
|
|
2067
2076
|
out.push({
|
|
2068
2077
|
stable_id: entry.qualifiedId,
|
|
2069
2078
|
type,
|
|
@@ -3406,11 +3415,155 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
|
|
|
3406
3415
|
|
|
3407
3416
|
// src/services/review.ts
|
|
3408
3417
|
import { execFileSync } from "child_process";
|
|
3409
|
-
import { existsSync as
|
|
3410
|
-
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";
|
|
3411
3420
|
import { homedir } from "os";
|
|
3412
|
-
import { basename, isAbsolute, join as
|
|
3421
|
+
import { basename, isAbsolute, join as join10, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
3413
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
|
|
3414
3567
|
var PLURAL_TYPES = [
|
|
3415
3568
|
"decisions",
|
|
3416
3569
|
"pitfalls",
|
|
@@ -3517,6 +3670,10 @@ function isVisibleByLifecycle(fm, filters) {
|
|
|
3517
3670
|
return true;
|
|
3518
3671
|
}
|
|
3519
3672
|
async function listPending(projectRoot, filters) {
|
|
3673
|
+
try {
|
|
3674
|
+
await mergePendingTwins(projectRoot);
|
|
3675
|
+
} catch {
|
|
3676
|
+
}
|
|
3520
3677
|
const items = [];
|
|
3521
3678
|
const typesToScan = filters?.type !== void 0 ? [filters.type] : PLURAL_TYPES;
|
|
3522
3679
|
const sources = [];
|
|
@@ -3536,22 +3693,22 @@ async function listPending(projectRoot, filters) {
|
|
|
3536
3693
|
}
|
|
3537
3694
|
for (const source of sources) {
|
|
3538
3695
|
for (const type of typesToScan) {
|
|
3539
|
-
const dir =
|
|
3540
|
-
if (!
|
|
3696
|
+
const dir = join10(source.root, type);
|
|
3697
|
+
if (!existsSync5(dir)) {
|
|
3541
3698
|
continue;
|
|
3542
3699
|
}
|
|
3543
3700
|
let entries;
|
|
3544
3701
|
try {
|
|
3545
|
-
entries = await
|
|
3702
|
+
entries = await readdir2(dir);
|
|
3546
3703
|
} catch {
|
|
3547
3704
|
continue;
|
|
3548
3705
|
}
|
|
3549
3706
|
for (const name of entries) {
|
|
3550
3707
|
if (!name.endsWith(".md")) continue;
|
|
3551
|
-
const absolutePath =
|
|
3708
|
+
const absolutePath = join10(dir, name);
|
|
3552
3709
|
let content;
|
|
3553
3710
|
try {
|
|
3554
|
-
content = await
|
|
3711
|
+
content = await readFile6(absolutePath, "utf8");
|
|
3555
3712
|
} catch {
|
|
3556
3713
|
continue;
|
|
3557
3714
|
}
|
|
@@ -3661,7 +3818,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3661
3818
|
let targetAbs;
|
|
3662
3819
|
let writtenTarget = false;
|
|
3663
3820
|
try {
|
|
3664
|
-
const content = await
|
|
3821
|
+
const content = await readFile6(sourceAbs, "utf8");
|
|
3665
3822
|
const fm = parseFrontmatter(content);
|
|
3666
3823
|
const pluralType = fm.type;
|
|
3667
3824
|
if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
|
|
@@ -3675,14 +3832,14 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3675
3832
|
);
|
|
3676
3833
|
allocatedId = stableId;
|
|
3677
3834
|
const newFilename = `${stableId}--${slug}.md`;
|
|
3678
|
-
targetAbs =
|
|
3835
|
+
targetAbs = join10(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
|
|
3679
3836
|
await ensureParentDirectory(targetAbs);
|
|
3680
3837
|
const rewritten = rewriteFrontmatterForPromote(content, stableId);
|
|
3681
3838
|
await atomicWriteText(targetAbs, rewritten);
|
|
3682
3839
|
writtenTarget = true;
|
|
3683
3840
|
if (sourceIsStore) {
|
|
3684
|
-
if (
|
|
3685
|
-
await
|
|
3841
|
+
if (existsSync5(sourceAbs)) {
|
|
3842
|
+
await unlink2(sourceAbs);
|
|
3686
3843
|
}
|
|
3687
3844
|
} else if (sourceOrigin === "team") {
|
|
3688
3845
|
try {
|
|
@@ -3691,13 +3848,13 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3691
3848
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3692
3849
|
});
|
|
3693
3850
|
} catch {
|
|
3694
|
-
if (
|
|
3695
|
-
await
|
|
3851
|
+
if (existsSync5(sourceAbs)) {
|
|
3852
|
+
await unlink2(sourceAbs);
|
|
3696
3853
|
}
|
|
3697
3854
|
}
|
|
3698
3855
|
} else {
|
|
3699
|
-
if (
|
|
3700
|
-
await
|
|
3856
|
+
if (existsSync5(sourceAbs)) {
|
|
3857
|
+
await unlink2(sourceAbs);
|
|
3701
3858
|
}
|
|
3702
3859
|
}
|
|
3703
3860
|
await emitEventBestEffort2(projectRoot, {
|
|
@@ -3708,9 +3865,9 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
3708
3865
|
});
|
|
3709
3866
|
return { pending_path: pendingPath, stable_id: stableId };
|
|
3710
3867
|
} catch (err) {
|
|
3711
|
-
if (writtenTarget && targetAbs !== void 0 &&
|
|
3868
|
+
if (writtenTarget && targetAbs !== void 0 && existsSync5(targetAbs)) {
|
|
3712
3869
|
try {
|
|
3713
|
-
await
|
|
3870
|
+
await unlink2(targetAbs);
|
|
3714
3871
|
} catch {
|
|
3715
3872
|
}
|
|
3716
3873
|
}
|
|
@@ -3729,14 +3886,14 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
|
|
|
3729
3886
|
for (const pendingPath of pendingPaths) {
|
|
3730
3887
|
try {
|
|
3731
3888
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
3732
|
-
if (
|
|
3733
|
-
const content = await
|
|
3889
|
+
if (existsSync5(sandboxed.abs)) {
|
|
3890
|
+
const content = await readFile6(sandboxed.abs, "utf8");
|
|
3734
3891
|
const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
|
|
3735
3892
|
const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
|
|
3736
3893
|
if (rejectedAbs !== null) {
|
|
3737
3894
|
await ensureParentDirectory(rejectedAbs);
|
|
3738
3895
|
await atomicWriteText(rejectedAbs, merged);
|
|
3739
|
-
await
|
|
3896
|
+
await unlink2(sandboxed.abs);
|
|
3740
3897
|
} else if (merged !== content) {
|
|
3741
3898
|
await atomicWriteText(sandboxed.abs, merged);
|
|
3742
3899
|
}
|
|
@@ -3757,7 +3914,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
|
|
|
3757
3914
|
if (target === null) {
|
|
3758
3915
|
throw new Error(`modify target not found: ${pendingPath}`);
|
|
3759
3916
|
}
|
|
3760
|
-
const content = await
|
|
3917
|
+
const content = await readFile6(target.absPath, "utf8");
|
|
3761
3918
|
const fm = parseFrontmatter(content);
|
|
3762
3919
|
const currentLayer = fm.layer ?? "team";
|
|
3763
3920
|
if (changes.semantic_scope !== void 0 && isPersonalScope2(changes.semantic_scope)) {
|
|
@@ -3802,7 +3959,7 @@ function resolveModifyTarget(projectRoot, pendingPath) {
|
|
|
3802
3959
|
} catch {
|
|
3803
3960
|
return null;
|
|
3804
3961
|
}
|
|
3805
|
-
if (
|
|
3962
|
+
if (existsSync5(sandboxed.abs)) {
|
|
3806
3963
|
return {
|
|
3807
3964
|
absPath: sandboxed.abs,
|
|
3808
3965
|
isInProjectTree: sandboxed.isInProjectTree,
|
|
@@ -3849,7 +4006,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
3849
4006
|
pluralType,
|
|
3850
4007
|
resolveWriteTargetStoreDir(toLayer, projectRoot)
|
|
3851
4008
|
);
|
|
3852
|
-
const toAbs =
|
|
4009
|
+
const toAbs = join10(
|
|
3853
4010
|
resolveStoreCanonicalBase(toLayer, projectRoot),
|
|
3854
4011
|
pluralType,
|
|
3855
4012
|
`${newStableId}--${slug}.md`
|
|
@@ -3877,12 +4034,12 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
3877
4034
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3878
4035
|
});
|
|
3879
4036
|
} catch {
|
|
3880
|
-
if (
|
|
3881
|
-
await
|
|
4037
|
+
if (existsSync5(target.absPath)) {
|
|
4038
|
+
await unlink2(target.absPath);
|
|
3882
4039
|
}
|
|
3883
4040
|
}
|
|
3884
|
-
} else if (
|
|
3885
|
-
await
|
|
4041
|
+
} else if (existsSync5(target.absPath)) {
|
|
4042
|
+
await unlink2(target.absPath);
|
|
3886
4043
|
}
|
|
3887
4044
|
const flipReason = `layer_flip:${priorStableId ?? "<unassigned>"}->${newStableId}`;
|
|
3888
4045
|
const flipTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3945,10 +4102,10 @@ function getSearchDirectoryCache(cacheKey) {
|
|
|
3945
4102
|
return created;
|
|
3946
4103
|
}
|
|
3947
4104
|
async function listIndexedSearchEntries(source, type) {
|
|
3948
|
-
const dir =
|
|
4105
|
+
const dir = join10(source.root, type);
|
|
3949
4106
|
let entries;
|
|
3950
4107
|
try {
|
|
3951
|
-
entries = await
|
|
4108
|
+
entries = await readdir2(dir);
|
|
3952
4109
|
} catch {
|
|
3953
4110
|
return [];
|
|
3954
4111
|
}
|
|
@@ -3958,7 +4115,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
3958
4115
|
const indexed = [];
|
|
3959
4116
|
for (const name of entries) {
|
|
3960
4117
|
if (!name.endsWith(".md")) continue;
|
|
3961
|
-
const absolutePath =
|
|
4118
|
+
const absolutePath = join10(dir, name);
|
|
3962
4119
|
let fingerprint;
|
|
3963
4120
|
try {
|
|
3964
4121
|
const st = await stat2(absolutePath);
|
|
@@ -3975,7 +4132,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
3975
4132
|
}
|
|
3976
4133
|
let content;
|
|
3977
4134
|
try {
|
|
3978
|
-
content = await
|
|
4135
|
+
content = await readFile6(absolutePath, "utf8");
|
|
3979
4136
|
searchEntryIndexContentReads += 1;
|
|
3980
4137
|
} catch {
|
|
3981
4138
|
directoryCache.files.delete(absolutePath);
|
|
@@ -4095,8 +4252,8 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
|
4095
4252
|
let stableId;
|
|
4096
4253
|
try {
|
|
4097
4254
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4098
|
-
if (
|
|
4099
|
-
const content = await
|
|
4255
|
+
if (existsSync5(sandboxed.abs)) {
|
|
4256
|
+
const content = await readFile6(sandboxed.abs, "utf8");
|
|
4100
4257
|
stableId = parseFrontmatter(content).id;
|
|
4101
4258
|
const patch = {
|
|
4102
4259
|
status: "deferred",
|
|
@@ -4368,9 +4525,9 @@ function registerReview(server, tracker) {
|
|
|
4368
4525
|
}
|
|
4369
4526
|
|
|
4370
4527
|
// src/services/doctor.ts
|
|
4371
|
-
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";
|
|
4372
4529
|
import { constants as constants2 } from "fs";
|
|
4373
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4530
|
+
import { isAbsolute as isAbsolute2, join as join20, posix as posix4, resolve as resolve3 } from "path";
|
|
4374
4531
|
import {
|
|
4375
4532
|
createTranslator,
|
|
4376
4533
|
forensicReportSchema,
|
|
@@ -4584,8 +4741,8 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
|
|
|
4584
4741
|
}
|
|
4585
4742
|
|
|
4586
4743
|
// src/services/doctor-stable-id-collision.ts
|
|
4587
|
-
import { readFile as
|
|
4588
|
-
import { basename as basename2, join as
|
|
4744
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4745
|
+
import { basename as basename2, join as join11 } from "path";
|
|
4589
4746
|
import {
|
|
4590
4747
|
buildStoreResolveInput as buildStoreResolveInput4,
|
|
4591
4748
|
createStoreResolver as createStoreResolver4,
|
|
@@ -4612,7 +4769,7 @@ function resolveIntegrityStores(projectRoot) {
|
|
|
4612
4769
|
return {
|
|
4613
4770
|
store_uuid: entry.store_uuid,
|
|
4614
4771
|
alias: entry.alias,
|
|
4615
|
-
dir:
|
|
4772
|
+
dir: join11(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
|
|
4616
4773
|
};
|
|
4617
4774
|
});
|
|
4618
4775
|
return { dirs, personalUuids };
|
|
@@ -4631,7 +4788,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
|
|
|
4631
4788
|
for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
|
|
4632
4789
|
let source;
|
|
4633
4790
|
try {
|
|
4634
|
-
source = await
|
|
4791
|
+
source = await readFile7(ref.file, "utf8");
|
|
4635
4792
|
} catch {
|
|
4636
4793
|
continue;
|
|
4637
4794
|
}
|
|
@@ -4715,8 +4872,8 @@ function createLayerMismatchCheck(t, inspection) {
|
|
|
4715
4872
|
|
|
4716
4873
|
// src/services/doctor-relevance-paths.ts
|
|
4717
4874
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4718
|
-
import { existsSync as
|
|
4719
|
-
import { join as
|
|
4875
|
+
import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
|
|
4876
|
+
import { join as join12, sep as sep3 } from "path";
|
|
4720
4877
|
import { minimatch } from "minimatch";
|
|
4721
4878
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
4722
4879
|
var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
|
|
@@ -4737,7 +4894,7 @@ function expandGlob(rawGlob) {
|
|
|
4737
4894
|
return rawGlob.endsWith("/") ? `${rawGlob}**` : rawGlob;
|
|
4738
4895
|
}
|
|
4739
4896
|
function collectWorkspacePaths(projectRoot) {
|
|
4740
|
-
if (!
|
|
4897
|
+
if (!existsSync6(projectRoot)) {
|
|
4741
4898
|
return [];
|
|
4742
4899
|
}
|
|
4743
4900
|
try {
|
|
@@ -4759,7 +4916,7 @@ function collectWorkspacePaths(projectRoot) {
|
|
|
4759
4916
|
continue;
|
|
4760
4917
|
}
|
|
4761
4918
|
for (const entry of entries) {
|
|
4762
|
-
const abs =
|
|
4919
|
+
const abs = join12(current, entry.name);
|
|
4763
4920
|
const rel = toPosix(abs.slice(projectRoot.length + 1));
|
|
4764
4921
|
if (rel.length === 0) continue;
|
|
4765
4922
|
if (entry.isDirectory()) {
|
|
@@ -4952,16 +5109,16 @@ function createNarrowNoPathsCheck(t, inspection) {
|
|
|
4952
5109
|
}
|
|
4953
5110
|
|
|
4954
5111
|
// src/services/doctor-broad-index.ts
|
|
4955
|
-
import { readFile as
|
|
4956
|
-
import { join as
|
|
5112
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5113
|
+
import { join as join13 } from "path";
|
|
4957
5114
|
var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
|
|
4958
5115
|
var BROAD_INDEX_BACKSTOP_MIN = 20;
|
|
4959
5116
|
var BROAD_INDEX_BACKSTOP_MAX = 500;
|
|
4960
5117
|
var BROAD_INDEX_DRIFT_RATIO = 0.8;
|
|
4961
5118
|
async function readBroadIndexBackstop(projectRoot) {
|
|
4962
|
-
const configPath =
|
|
5119
|
+
const configPath = join13(projectRoot, ".fabric", "fabric-config.json");
|
|
4963
5120
|
try {
|
|
4964
|
-
const raw = await
|
|
5121
|
+
const raw = await readFile8(configPath, "utf8");
|
|
4965
5122
|
const parsed = JSON.parse(raw);
|
|
4966
5123
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4967
5124
|
const v = parsed.broad_index_backstop;
|
|
@@ -5142,8 +5299,8 @@ function createStaleArchiveCheck(t, inspection) {
|
|
|
5142
5299
|
}
|
|
5143
5300
|
|
|
5144
5301
|
// src/services/doctor-scope-lint.ts
|
|
5145
|
-
import { readFile as
|
|
5146
|
-
import { join as
|
|
5302
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5303
|
+
import { join as join14 } from "path";
|
|
5147
5304
|
import {
|
|
5148
5305
|
buildStoreResolveInput as buildStoreResolveInput5,
|
|
5149
5306
|
createStoreResolver as createStoreResolver5,
|
|
@@ -5178,7 +5335,7 @@ async function resolveLintStores(projectRoot) {
|
|
|
5178
5335
|
const globalRoot = resolveGlobalRoot4();
|
|
5179
5336
|
return Promise.all(readSet.stores.map(async (entry) => {
|
|
5180
5337
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
5181
|
-
const dir =
|
|
5338
|
+
const dir = join14(
|
|
5182
5339
|
globalRoot,
|
|
5183
5340
|
storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
|
|
5184
5341
|
);
|
|
@@ -5210,7 +5367,7 @@ async function lintStoreScopes(projectRoot) {
|
|
|
5210
5367
|
}
|
|
5211
5368
|
let source;
|
|
5212
5369
|
try {
|
|
5213
|
-
source = await
|
|
5370
|
+
source = await readFile9(ref.file, "utf8");
|
|
5214
5371
|
} catch {
|
|
5215
5372
|
continue;
|
|
5216
5373
|
}
|
|
@@ -5332,8 +5489,8 @@ function createUnboundProjectCheck(t, violation) {
|
|
|
5332
5489
|
}
|
|
5333
5490
|
|
|
5334
5491
|
// src/services/doctor-store-counters.ts
|
|
5335
|
-
import { existsSync as
|
|
5336
|
-
import { join as
|
|
5492
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
5493
|
+
import { join as join15 } from "path";
|
|
5337
5494
|
import {
|
|
5338
5495
|
buildStoreResolveInput as buildStoreResolveInput6,
|
|
5339
5496
|
createStoreResolver as createStoreResolver6,
|
|
@@ -5360,7 +5517,7 @@ function resolveCounterStores(projectRoot) {
|
|
|
5360
5517
|
return readSet.stores.map((entry) => ({
|
|
5361
5518
|
uuid: entry.store_uuid,
|
|
5362
5519
|
alias: entry.alias,
|
|
5363
|
-
dir:
|
|
5520
|
+
dir: join15(
|
|
5364
5521
|
globalRoot,
|
|
5365
5522
|
storeRelativePathForMount5(
|
|
5366
5523
|
input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
|
|
@@ -5388,8 +5545,8 @@ function readEntryId(file) {
|
|
|
5388
5545
|
function computeStoreDiskMax(storeDir) {
|
|
5389
5546
|
const max = defaultAgentsMetaCounters();
|
|
5390
5547
|
for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
5391
|
-
const dir =
|
|
5392
|
-
if (!
|
|
5548
|
+
const dir = join15(storeDir, STORE_LAYOUT2.knowledgeDir, type);
|
|
5549
|
+
if (!existsSync7(dir)) {
|
|
5393
5550
|
continue;
|
|
5394
5551
|
}
|
|
5395
5552
|
let names;
|
|
@@ -5402,7 +5559,7 @@ function computeStoreDiskMax(storeDir) {
|
|
|
5402
5559
|
if (!name.endsWith(".md")) {
|
|
5403
5560
|
continue;
|
|
5404
5561
|
}
|
|
5405
|
-
const parsed = parseKnowledgeId2(readEntryId(
|
|
5562
|
+
const parsed = parseKnowledgeId2(readEntryId(join15(dir, name)) ?? "");
|
|
5406
5563
|
if (parsed === null) {
|
|
5407
5564
|
continue;
|
|
5408
5565
|
}
|
|
@@ -5485,7 +5642,7 @@ function createStoreCounterCheck(t, drifts) {
|
|
|
5485
5642
|
|
|
5486
5643
|
// src/services/doctor-store-orphan.ts
|
|
5487
5644
|
import { readdirSync as readdirSync3 } from "fs";
|
|
5488
|
-
import { join as
|
|
5645
|
+
import { join as join16 } from "path";
|
|
5489
5646
|
import {
|
|
5490
5647
|
STORES_ROOT_DIR,
|
|
5491
5648
|
addMountedStore,
|
|
@@ -5509,15 +5666,15 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
|
|
|
5509
5666
|
return [];
|
|
5510
5667
|
}
|
|
5511
5668
|
const registered = new Set(config.stores.map((s) => s.store_uuid));
|
|
5512
|
-
const storesRoot =
|
|
5669
|
+
const storesRoot = join16(globalRoot, STORES_ROOT_DIR);
|
|
5513
5670
|
const orphans = [];
|
|
5514
5671
|
for (const group of listDir(storesRoot)) {
|
|
5515
5672
|
if (group === STORE_BY_ALIAS_DIR) {
|
|
5516
5673
|
continue;
|
|
5517
5674
|
}
|
|
5518
|
-
const groupDir =
|
|
5675
|
+
const groupDir = join16(storesRoot, group);
|
|
5519
5676
|
for (const mount of listDir(groupDir)) {
|
|
5520
|
-
const dir =
|
|
5677
|
+
const dir = join16(groupDir, mount);
|
|
5521
5678
|
const identity = readStoreIdentity(dir);
|
|
5522
5679
|
if (identity === null || registered.has(identity.store_uuid)) {
|
|
5523
5680
|
continue;
|
|
@@ -5590,7 +5747,7 @@ import {
|
|
|
5590
5747
|
|
|
5591
5748
|
// src/services/events-jsonl-gates.ts
|
|
5592
5749
|
import { promises as fs } from "fs";
|
|
5593
|
-
import { existsSync as
|
|
5750
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5594
5751
|
var EVENTS_JSONL_SIZE_WARN_BYTES = 10 * 1024 * 1024;
|
|
5595
5752
|
var METRICS_STALE_WARN_MS = 10 * 60 * 1e3;
|
|
5596
5753
|
var ROTATION_OVERDUE_WARN_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
@@ -5612,7 +5769,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
5612
5769
|
if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
|
|
5613
5770
|
}
|
|
5614
5771
|
let metricsStalenessMs = null;
|
|
5615
|
-
if (
|
|
5772
|
+
if (existsSync8(metricsPath)) {
|
|
5616
5773
|
try {
|
|
5617
5774
|
const stat4 = await fs.stat(metricsPath);
|
|
5618
5775
|
metricsStalenessMs = Math.max(0, now.getTime() - stat4.mtimeMs);
|
|
@@ -5654,8 +5811,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
5654
5811
|
}
|
|
5655
5812
|
|
|
5656
5813
|
// src/services/doctor-skill-lints.ts
|
|
5657
|
-
import { readdir as
|
|
5658
|
-
import { join as
|
|
5814
|
+
import { readdir as readdir3, readFile as readFile10 } from "fs/promises";
|
|
5815
|
+
import { join as join17, posix as posix2 } from "path";
|
|
5659
5816
|
var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review", "fabric-import"];
|
|
5660
5817
|
var ROUTER_VALID_LEAF_SLUGS = /* @__PURE__ */ new Set([
|
|
5661
5818
|
"fabric-archive",
|
|
@@ -5686,7 +5843,7 @@ function issueCheck(name, status, kind, code, message, actionHint, audience) {
|
|
|
5686
5843
|
}
|
|
5687
5844
|
async function listMarkdownFiles(dir) {
|
|
5688
5845
|
try {
|
|
5689
|
-
return (await
|
|
5846
|
+
return (await readdir3(dir)).filter((name) => name.endsWith(".md"));
|
|
5690
5847
|
} catch {
|
|
5691
5848
|
return null;
|
|
5692
5849
|
}
|
|
@@ -5694,8 +5851,8 @@ async function listMarkdownFiles(dir) {
|
|
|
5694
5851
|
async function inspectSkillRefMirror(projectRoot) {
|
|
5695
5852
|
const driftedPaths = [];
|
|
5696
5853
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
5697
|
-
const claudeRef =
|
|
5698
|
-
const codexRef =
|
|
5854
|
+
const claudeRef = join17(projectRoot, ".claude", "skills", slug, "ref");
|
|
5855
|
+
const codexRef = join17(projectRoot, ".codex", "skills", slug, "ref");
|
|
5699
5856
|
const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
|
|
5700
5857
|
if (claudeFiles === null || codexFiles === null) continue;
|
|
5701
5858
|
const claudeSet = new Set(claudeFiles);
|
|
@@ -5712,8 +5869,8 @@ async function inspectSkillRefMirror(projectRoot) {
|
|
|
5712
5869
|
let codexBody;
|
|
5713
5870
|
try {
|
|
5714
5871
|
[claudeBody, codexBody] = await Promise.all([
|
|
5715
|
-
|
|
5716
|
-
|
|
5872
|
+
readFile10(join17(claudeRef, fname), "utf8"),
|
|
5873
|
+
readFile10(join17(codexRef, fname), "utf8")
|
|
5717
5874
|
]);
|
|
5718
5875
|
} catch {
|
|
5719
5876
|
continue;
|
|
@@ -5732,10 +5889,10 @@ async function inspectSkillTokenBudget(projectRoot) {
|
|
|
5732
5889
|
const overSize = [];
|
|
5733
5890
|
let highestSeverity = "ok";
|
|
5734
5891
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
5735
|
-
const skillMdPath =
|
|
5892
|
+
const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
5736
5893
|
let body;
|
|
5737
5894
|
try {
|
|
5738
|
-
body = await
|
|
5895
|
+
body = await readFile10(skillMdPath, "utf8");
|
|
5739
5896
|
} catch {
|
|
5740
5897
|
continue;
|
|
5741
5898
|
}
|
|
@@ -5756,10 +5913,10 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
5756
5913
|
const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
|
|
5757
5914
|
const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
|
|
5758
5915
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
5759
|
-
const skillMdPath =
|
|
5916
|
+
const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
5760
5917
|
let body;
|
|
5761
5918
|
try {
|
|
5762
|
-
body = await
|
|
5919
|
+
body = await readFile10(skillMdPath, "utf8");
|
|
5763
5920
|
} catch {
|
|
5764
5921
|
continue;
|
|
5765
5922
|
}
|
|
@@ -5790,19 +5947,19 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
5790
5947
|
async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
5791
5948
|
const candidates = [];
|
|
5792
5949
|
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
5793
|
-
const rootAbs =
|
|
5950
|
+
const rootAbs = join17(projectRoot, rootRel);
|
|
5794
5951
|
let dirEntries;
|
|
5795
5952
|
try {
|
|
5796
|
-
dirEntries = await
|
|
5953
|
+
dirEntries = await readdir3(rootAbs, { withFileTypes: true });
|
|
5797
5954
|
} catch {
|
|
5798
5955
|
continue;
|
|
5799
5956
|
}
|
|
5800
5957
|
for (const dirEntry of dirEntries) {
|
|
5801
5958
|
if (!dirEntry.isDirectory()) continue;
|
|
5802
|
-
const skillFile =
|
|
5959
|
+
const skillFile = join17(rootAbs, dirEntry.name, "SKILL.md");
|
|
5803
5960
|
let raw;
|
|
5804
5961
|
try {
|
|
5805
|
-
raw = await
|
|
5962
|
+
raw = await readFile10(skillFile, "utf8");
|
|
5806
5963
|
} catch {
|
|
5807
5964
|
continue;
|
|
5808
5965
|
}
|
|
@@ -5871,13 +6028,13 @@ function extractMarkdownSectionBody(markdown, sectionName) {
|
|
|
5871
6028
|
}
|
|
5872
6029
|
async function inspectRouterChainRef(projectRoot) {
|
|
5873
6030
|
const candidatePaths = [
|
|
5874
|
-
|
|
5875
|
-
|
|
6031
|
+
join17(projectRoot, ".claude", "skills", "fabric", "SKILL.md"),
|
|
6032
|
+
join17(projectRoot, ".codex", "skills", "fabric", "SKILL.md")
|
|
5876
6033
|
];
|
|
5877
6034
|
let body = null;
|
|
5878
6035
|
for (const candidate of candidatePaths) {
|
|
5879
6036
|
try {
|
|
5880
|
-
body = await
|
|
6037
|
+
body = await readFile10(candidate, "utf8");
|
|
5881
6038
|
break;
|
|
5882
6039
|
} catch {
|
|
5883
6040
|
}
|
|
@@ -5988,8 +6145,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
|
|
|
5988
6145
|
|
|
5989
6146
|
// src/services/doctor-hooks-lints.ts
|
|
5990
6147
|
import { constants } from "fs";
|
|
5991
|
-
import { access, readdir as
|
|
5992
|
-
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";
|
|
5993
6150
|
import { Script } from "vm";
|
|
5994
6151
|
var HOOKS_RUNTIME_CLIENT_DIRS = [
|
|
5995
6152
|
{ client: "claude", dir: ".claude/hooks" },
|
|
@@ -6038,7 +6195,7 @@ function isHookWiredForEvent(hooks, event, hookFile) {
|
|
|
6038
6195
|
}
|
|
6039
6196
|
async function readDirectoryFileNames(dir) {
|
|
6040
6197
|
try {
|
|
6041
|
-
return await
|
|
6198
|
+
return await readdir4(dir);
|
|
6042
6199
|
} catch {
|
|
6043
6200
|
return null;
|
|
6044
6201
|
}
|
|
@@ -6051,14 +6208,14 @@ async function isFile(absPath) {
|
|
|
6051
6208
|
}
|
|
6052
6209
|
}
|
|
6053
6210
|
async function inspectHooksWired(projectRoot) {
|
|
6054
|
-
const claudeEntries = await readDirectoryFileNames(
|
|
6211
|
+
const claudeEntries = await readDirectoryFileNames(join18(projectRoot, ".claude"));
|
|
6055
6212
|
if (claudeEntries === null) {
|
|
6056
6213
|
return { status: "skipped", missingHooks: [] };
|
|
6057
6214
|
}
|
|
6058
|
-
const settingsPath =
|
|
6215
|
+
const settingsPath = join18(projectRoot, ".claude", "settings.json");
|
|
6059
6216
|
let parsed;
|
|
6060
6217
|
try {
|
|
6061
|
-
parsed = JSON.parse(await
|
|
6218
|
+
parsed = JSON.parse(await readFile11(settingsPath, "utf8"));
|
|
6062
6219
|
} catch {
|
|
6063
6220
|
return { status: "missing-settings", missingHooks: [] };
|
|
6064
6221
|
}
|
|
@@ -6081,8 +6238,8 @@ async function inspectHooksWired(projectRoot) {
|
|
|
6081
6238
|
}
|
|
6082
6239
|
async function inspectHookCacheWritability(projectRoot) {
|
|
6083
6240
|
const relPath = posix3.join(".fabric", ".cache");
|
|
6084
|
-
const fabricDir =
|
|
6085
|
-
const cacheDir =
|
|
6241
|
+
const fabricDir = join18(projectRoot, ".fabric");
|
|
6242
|
+
const cacheDir = join18(projectRoot, ".fabric", ".cache");
|
|
6086
6243
|
try {
|
|
6087
6244
|
try {
|
|
6088
6245
|
const cacheStats = await stat3(cacheDir);
|
|
@@ -6131,12 +6288,12 @@ async function inspectHookCacheWritability(projectRoot) {
|
|
|
6131
6288
|
async function inspectHooksContentDrift(projectRoot) {
|
|
6132
6289
|
const hookFilesByBasename = /* @__PURE__ */ new Map();
|
|
6133
6290
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
6134
|
-
const absDir =
|
|
6291
|
+
const absDir = join18(projectRoot, dir);
|
|
6135
6292
|
const entries = await readDirectoryFileNames(absDir);
|
|
6136
6293
|
if (entries === null) continue;
|
|
6137
6294
|
for (const name of entries) {
|
|
6138
6295
|
if (!name.endsWith(".cjs")) continue;
|
|
6139
|
-
const abs =
|
|
6296
|
+
const abs = join18(absDir, name);
|
|
6140
6297
|
if (!await isFile(abs)) continue;
|
|
6141
6298
|
const arr = hookFilesByBasename.get(name) ?? [];
|
|
6142
6299
|
arr.push({ client, abs });
|
|
@@ -6151,7 +6308,7 @@ async function inspectHooksContentDrift(projectRoot) {
|
|
|
6151
6308
|
const hashes = [];
|
|
6152
6309
|
for (const { client, abs } of copies) {
|
|
6153
6310
|
try {
|
|
6154
|
-
const body = await
|
|
6311
|
+
const body = await readFile11(abs, "utf8");
|
|
6155
6312
|
hashes.push({ client, sha: sha256(body) });
|
|
6156
6313
|
} catch {
|
|
6157
6314
|
}
|
|
@@ -6173,18 +6330,18 @@ async function inspectHooksRuntime(projectRoot) {
|
|
|
6173
6330
|
const issues = [];
|
|
6174
6331
|
let scanned = 0;
|
|
6175
6332
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
6176
|
-
const absDir =
|
|
6333
|
+
const absDir = join18(projectRoot, dir);
|
|
6177
6334
|
const entries = await readDirectoryFileNames(absDir);
|
|
6178
6335
|
if (entries === null) continue;
|
|
6179
6336
|
for (const name of entries) {
|
|
6180
6337
|
if (!name.endsWith(".cjs")) continue;
|
|
6181
|
-
const abs =
|
|
6338
|
+
const abs = join18(absDir, name);
|
|
6182
6339
|
const displayPath = `${dir}/${name}`;
|
|
6183
6340
|
if (!await isFile(abs)) continue;
|
|
6184
6341
|
scanned += 1;
|
|
6185
6342
|
let body;
|
|
6186
6343
|
try {
|
|
6187
|
-
body = await
|
|
6344
|
+
body = await readFile11(abs, "utf8");
|
|
6188
6345
|
} catch (err) {
|
|
6189
6346
|
issues.push({
|
|
6190
6347
|
path: displayPath,
|
|
@@ -6321,8 +6478,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
|
|
|
6321
6478
|
}
|
|
6322
6479
|
|
|
6323
6480
|
// src/services/doctor-bootstrap-lints.ts
|
|
6324
|
-
import { access as access2, readFile as
|
|
6325
|
-
import { join as
|
|
6481
|
+
import { access as access2, readFile as readFile12 } from "fs/promises";
|
|
6482
|
+
import { join as join19 } from "path";
|
|
6326
6483
|
import {
|
|
6327
6484
|
BOOTSTRAP_MARKER_BEGIN,
|
|
6328
6485
|
BOOTSTRAP_MARKER_END,
|
|
@@ -6354,17 +6511,17 @@ async function fileExists(path) {
|
|
|
6354
6511
|
}
|
|
6355
6512
|
async function inspectBootstrapAnchor(projectRoot) {
|
|
6356
6513
|
const [hasAgentsMd, hasClaudeMd] = await Promise.all([
|
|
6357
|
-
fileExists(
|
|
6358
|
-
fileExists(
|
|
6514
|
+
fileExists(join19(projectRoot, "AGENTS.md")),
|
|
6515
|
+
fileExists(join19(projectRoot, "CLAUDE.md"))
|
|
6359
6516
|
]);
|
|
6360
6517
|
return { hasAgentsMd, hasClaudeMd };
|
|
6361
6518
|
}
|
|
6362
6519
|
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
6363
|
-
const abs =
|
|
6520
|
+
const abs = join19(target, ".fabric", "AGENTS.md");
|
|
6364
6521
|
const canonical = resolveBootstrapCanonical();
|
|
6365
6522
|
let onDisk;
|
|
6366
6523
|
try {
|
|
6367
|
-
onDisk = await
|
|
6524
|
+
onDisk = await readFile12(abs, "utf8");
|
|
6368
6525
|
} catch {
|
|
6369
6526
|
return { status: "missing", canonical, onDisk: null };
|
|
6370
6527
|
}
|
|
@@ -6390,17 +6547,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
|
|
|
6390
6547
|
);
|
|
6391
6548
|
}
|
|
6392
6549
|
async function inspectL2ManagedBlockDrift(target) {
|
|
6393
|
-
const snapshotPath =
|
|
6550
|
+
const snapshotPath = join19(target, ".fabric", "AGENTS.md");
|
|
6394
6551
|
let snapshot;
|
|
6395
6552
|
try {
|
|
6396
|
-
snapshot = await
|
|
6553
|
+
snapshot = await readFile12(snapshotPath, "utf8");
|
|
6397
6554
|
} catch {
|
|
6398
6555
|
return { status: "ok", drifted: [] };
|
|
6399
6556
|
}
|
|
6400
|
-
const projectRulesPath =
|
|
6557
|
+
const projectRulesPath = join19(target, ".fabric", "project-rules.md");
|
|
6401
6558
|
let expectedBody = snapshot;
|
|
6402
6559
|
try {
|
|
6403
|
-
const projectRules = await
|
|
6560
|
+
const projectRules = await readFile12(projectRulesPath, "utf8");
|
|
6404
6561
|
expectedBody = `${snapshot}
|
|
6405
6562
|
---
|
|
6406
6563
|
${projectRules}`;
|
|
@@ -6409,12 +6566,12 @@ ${projectRules}`;
|
|
|
6409
6566
|
const drifted = [];
|
|
6410
6567
|
let anyManagedBlockFound = false;
|
|
6411
6568
|
const blockTargets = [
|
|
6412
|
-
|
|
6569
|
+
join19(target, "AGENTS.md")
|
|
6413
6570
|
];
|
|
6414
6571
|
for (const abs of blockTargets) {
|
|
6415
6572
|
let content;
|
|
6416
6573
|
try {
|
|
6417
|
-
content = await
|
|
6574
|
+
content = await readFile12(abs, "utf8");
|
|
6418
6575
|
} catch {
|
|
6419
6576
|
continue;
|
|
6420
6577
|
}
|
|
@@ -6437,9 +6594,9 @@ ${projectRules}`;
|
|
|
6437
6594
|
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
6438
6595
|
}
|
|
6439
6596
|
}
|
|
6440
|
-
const claudeMdPath =
|
|
6597
|
+
const claudeMdPath = join19(target, "CLAUDE.md");
|
|
6441
6598
|
try {
|
|
6442
|
-
const claudeContent = await
|
|
6599
|
+
const claudeContent = await readFile12(claudeMdPath, "utf8");
|
|
6443
6600
|
anyManagedBlockFound = true;
|
|
6444
6601
|
const lines = claudeContent.split(/\r?\n/u);
|
|
6445
6602
|
const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
|
|
@@ -6507,7 +6664,7 @@ import { appendFile as appendFile3 } from "fs/promises";
|
|
|
6507
6664
|
import { minimatch as minimatch2 } from "minimatch";
|
|
6508
6665
|
|
|
6509
6666
|
// src/services/cite-rollup.ts
|
|
6510
|
-
import { readFile as
|
|
6667
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
6511
6668
|
import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
6512
6669
|
var citeRollupQueue = createLedgerWriteQueue3();
|
|
6513
6670
|
async function appendCiteRollupRow(projectRoot, row) {
|
|
@@ -6519,7 +6676,7 @@ async function readCiteRollup(projectRoot) {
|
|
|
6519
6676
|
const path = getCiteRollupPath(projectRoot);
|
|
6520
6677
|
let raw;
|
|
6521
6678
|
try {
|
|
6522
|
-
raw = await
|
|
6679
|
+
raw = await readFile13(path, "utf8");
|
|
6523
6680
|
} catch (error) {
|
|
6524
6681
|
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
6525
6682
|
throw error;
|
|
@@ -7644,7 +7801,7 @@ async function runDoctorReport(target) {
|
|
|
7644
7801
|
const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
|
|
7645
7802
|
const targetFiles = Object.fromEntries(
|
|
7646
7803
|
await Promise.all(
|
|
7647
|
-
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(
|
|
7804
|
+
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join20(projectRoot, path))])
|
|
7648
7805
|
)
|
|
7649
7806
|
);
|
|
7650
7807
|
const checks = [
|
|
@@ -7844,7 +8001,7 @@ async function runDoctorFix(target) {
|
|
|
7844
8001
|
const fixed = [];
|
|
7845
8002
|
const ledgerWarnings = [];
|
|
7846
8003
|
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
7847
|
-
const snapshotPath =
|
|
8004
|
+
const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
|
|
7848
8005
|
await ensureParentDirectory(snapshotPath);
|
|
7849
8006
|
await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
|
|
7850
8007
|
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
@@ -7917,9 +8074,9 @@ async function runDoctorFix(target) {
|
|
|
7917
8074
|
if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
|
|
7918
8075
|
const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
|
|
7919
8076
|
if (lockInspection.present && !lockInspection.pidAlive) {
|
|
7920
|
-
const lockFilePath =
|
|
8077
|
+
const lockFilePath = join20(projectRoot, ".fabric", ".serve.lock");
|
|
7921
8078
|
try {
|
|
7922
|
-
await
|
|
8079
|
+
await unlink4(lockFilePath);
|
|
7923
8080
|
} catch (err) {
|
|
7924
8081
|
const errno = err;
|
|
7925
8082
|
if (errno.code !== "ENOENT") throw err;
|
|
@@ -8034,10 +8191,10 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
|
|
|
8034
8191
|
}
|
|
8035
8192
|
async function applySessionHintsStaleCleanup(projectRoot, candidate) {
|
|
8036
8193
|
const detail = `deleted (${candidate.age_days}d old)`;
|
|
8037
|
-
const absPath =
|
|
8194
|
+
const absPath = join20(projectRoot, candidate.path);
|
|
8038
8195
|
try {
|
|
8039
|
-
const { unlink:
|
|
8040
|
-
await
|
|
8196
|
+
const { unlink: unlink5 } = await import("fs/promises");
|
|
8197
|
+
await unlink5(absPath);
|
|
8041
8198
|
return {
|
|
8042
8199
|
kind: "knowledge_session_hints_stale_cleanup",
|
|
8043
8200
|
path: candidate.path,
|
|
@@ -8059,9 +8216,9 @@ function truncateErrorMessage(error) {
|
|
|
8059
8216
|
return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
|
|
8060
8217
|
}
|
|
8061
8218
|
async function inspectForensic(projectRoot) {
|
|
8062
|
-
const path =
|
|
8219
|
+
const path = join20(projectRoot, ".fabric", "forensic.json");
|
|
8063
8220
|
try {
|
|
8064
|
-
const parsed = forensicReportSchema.parse(JSON.parse(await
|
|
8221
|
+
const parsed = forensicReportSchema.parse(JSON.parse(await readFile15(path, "utf8")));
|
|
8065
8222
|
return { present: true, valid: true, report: parsed };
|
|
8066
8223
|
} catch (error) {
|
|
8067
8224
|
if (isMissingFileError(error)) {
|
|
@@ -8091,7 +8248,7 @@ async function inspectEventLedger(projectRoot) {
|
|
|
8091
8248
|
try {
|
|
8092
8249
|
await access4(path, constants2.W_OK);
|
|
8093
8250
|
const { warnings } = await readEventLedger(projectRoot);
|
|
8094
|
-
const raw = await
|
|
8251
|
+
const raw = await readFile15(path, "utf8");
|
|
8095
8252
|
const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
|
|
8096
8253
|
const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
|
|
8097
8254
|
const schemaVersionSamples = [];
|
|
@@ -8636,7 +8793,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
|
|
|
8636
8793
|
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
8637
8794
|
const detected = [];
|
|
8638
8795
|
for (const name of candidates) {
|
|
8639
|
-
if (await pathExists(
|
|
8796
|
+
if (await pathExists(join20(projectRoot, name))) {
|
|
8640
8797
|
detected.push(name);
|
|
8641
8798
|
}
|
|
8642
8799
|
}
|
|
@@ -8721,7 +8878,7 @@ async function buildLastActiveIndex(projectRoot) {
|
|
|
8721
8878
|
return map;
|
|
8722
8879
|
}
|
|
8723
8880
|
async function inspectSessionHintsStale(projectRoot, now) {
|
|
8724
|
-
const cacheDir =
|
|
8881
|
+
const cacheDir = join20(projectRoot, ".fabric", ".cache");
|
|
8725
8882
|
let entries;
|
|
8726
8883
|
try {
|
|
8727
8884
|
entries = await readdirAsync(cacheDir, { withFileTypes: true });
|
|
@@ -8733,7 +8890,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
|
|
|
8733
8890
|
if (!entry.isFile()) continue;
|
|
8734
8891
|
if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
|
|
8735
8892
|
if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
|
|
8736
|
-
const absPath =
|
|
8893
|
+
const absPath = join20(cacheDir, entry.name);
|
|
8737
8894
|
let mtimeMs = 0;
|
|
8738
8895
|
try {
|
|
8739
8896
|
mtimeMs = (await statAsync(absPath)).mtimeMs;
|
|
@@ -8765,9 +8922,9 @@ function inspectStaleServeLock(projectRoot, now) {
|
|
|
8765
8922
|
};
|
|
8766
8923
|
}
|
|
8767
8924
|
async function readUnderseedThresholdFromConfig(projectRoot) {
|
|
8768
|
-
const configPath =
|
|
8925
|
+
const configPath = join20(projectRoot, ".fabric", "fabric-config.json");
|
|
8769
8926
|
try {
|
|
8770
|
-
const raw = await
|
|
8927
|
+
const raw = await readFile15(configPath, "utf8");
|
|
8771
8928
|
const parsed = JSON.parse(raw);
|
|
8772
8929
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
8773
8930
|
const v = parsed.underseed_node_threshold;
|
|
@@ -8883,10 +9040,10 @@ async function inspectOnboardCoverage(projectRoot) {
|
|
|
8883
9040
|
return { filled, missing, opted_out: optedOut };
|
|
8884
9041
|
}
|
|
8885
9042
|
async function readOnboardOptedOut(projectRoot) {
|
|
8886
|
-
const path =
|
|
9043
|
+
const path = join20(projectRoot, ".fabric", "fabric-config.json");
|
|
8887
9044
|
let raw;
|
|
8888
9045
|
try {
|
|
8889
|
-
raw = await
|
|
9046
|
+
raw = await readFile15(path, "utf8");
|
|
8890
9047
|
} catch {
|
|
8891
9048
|
return [];
|
|
8892
9049
|
}
|
|
@@ -8988,22 +9145,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
|
|
|
8988
9145
|
}
|
|
8989
9146
|
}
|
|
8990
9147
|
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
8991
|
-
const snapshotPath =
|
|
9148
|
+
const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
|
|
8992
9149
|
if (!await pathExists(snapshotPath)) {
|
|
8993
9150
|
return;
|
|
8994
9151
|
}
|
|
8995
9152
|
let snapshot;
|
|
8996
9153
|
try {
|
|
8997
|
-
snapshot = await
|
|
9154
|
+
snapshot = await readFile15(snapshotPath, "utf8");
|
|
8998
9155
|
} catch {
|
|
8999
9156
|
return;
|
|
9000
9157
|
}
|
|
9001
|
-
const projectRulesPath =
|
|
9158
|
+
const projectRulesPath = join20(projectRoot, ".fabric", "project-rules.md");
|
|
9002
9159
|
const hasProjectRules = await pathExists(projectRulesPath);
|
|
9003
9160
|
let expectedBody = snapshot;
|
|
9004
9161
|
if (hasProjectRules) {
|
|
9005
9162
|
try {
|
|
9006
|
-
const projectRules = await
|
|
9163
|
+
const projectRules = await readFile15(projectRulesPath, "utf8");
|
|
9007
9164
|
expectedBody = `${snapshot}
|
|
9008
9165
|
---
|
|
9009
9166
|
${projectRules}`;
|
|
@@ -9014,7 +9171,7 @@ ${projectRules}`;
|
|
|
9014
9171
|
${expectedBody}
|
|
9015
9172
|
${BOOTSTRAP_MARKER_END2}`;
|
|
9016
9173
|
const blockTargets = [
|
|
9017
|
-
|
|
9174
|
+
join20(projectRoot, "AGENTS.md")
|
|
9018
9175
|
];
|
|
9019
9176
|
for (const abs of blockTargets) {
|
|
9020
9177
|
if (!await pathExists(abs)) {
|
|
@@ -9022,7 +9179,7 @@ ${BOOTSTRAP_MARKER_END2}`;
|
|
|
9022
9179
|
}
|
|
9023
9180
|
let existing;
|
|
9024
9181
|
try {
|
|
9025
|
-
existing = await
|
|
9182
|
+
existing = await readFile15(abs, "utf8");
|
|
9026
9183
|
} catch {
|
|
9027
9184
|
continue;
|
|
9028
9185
|
}
|
|
@@ -9047,11 +9204,11 @@ ${managedBlock}
|
|
|
9047
9204
|
}
|
|
9048
9205
|
await atomicWriteText4(abs, next);
|
|
9049
9206
|
}
|
|
9050
|
-
const claudeMdPath =
|
|
9207
|
+
const claudeMdPath = join20(projectRoot, "CLAUDE.md");
|
|
9051
9208
|
if (await pathExists(claudeMdPath)) {
|
|
9052
9209
|
let claudeContent;
|
|
9053
9210
|
try {
|
|
9054
|
-
claudeContent = await
|
|
9211
|
+
claudeContent = await readFile15(claudeMdPath, "utf8");
|
|
9055
9212
|
} catch {
|
|
9056
9213
|
return;
|
|
9057
9214
|
}
|
|
@@ -9117,7 +9274,7 @@ async function collectEntryPoints(root) {
|
|
|
9117
9274
|
continue;
|
|
9118
9275
|
}
|
|
9119
9276
|
for (const entry of await readdirAsync(current, { withFileTypes: true })) {
|
|
9120
|
-
const absolutePath =
|
|
9277
|
+
const absolutePath = join20(current, entry.name);
|
|
9121
9278
|
const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
|
|
9122
9279
|
if (relativePath.length === 0) {
|
|
9123
9280
|
continue;
|
|
@@ -9193,7 +9350,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
|
|
|
9193
9350
|
scanned += 1;
|
|
9194
9351
|
let source;
|
|
9195
9352
|
try {
|
|
9196
|
-
source = await
|
|
9353
|
+
source = await readFile15(absPath, "utf8");
|
|
9197
9354
|
} catch {
|
|
9198
9355
|
continue;
|
|
9199
9356
|
}
|
|
@@ -9466,7 +9623,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
|
|
|
9466
9623
|
|
|
9467
9624
|
// src/services/read-ledger.ts
|
|
9468
9625
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
9469
|
-
import { access as access5, copyFile, readFile as
|
|
9626
|
+
import { access as access5, copyFile, readFile as readFile16, rm } from "fs/promises";
|
|
9470
9627
|
import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
9471
9628
|
async function resolveLedgerPaths(projectRoot) {
|
|
9472
9629
|
const primaryPath = getLedgerPath(projectRoot);
|
|
@@ -9494,7 +9651,7 @@ async function readLegacyLedger(projectRoot) {
|
|
|
9494
9651
|
const { readPath } = await resolveLedgerPaths(projectRoot);
|
|
9495
9652
|
let raw;
|
|
9496
9653
|
try {
|
|
9497
|
-
raw = await
|
|
9654
|
+
raw = await readFile16(readPath, "utf8");
|
|
9498
9655
|
} catch (error) {
|
|
9499
9656
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
9500
9657
|
return [];
|
|
@@ -9749,8 +9906,8 @@ function formatError(error) {
|
|
|
9749
9906
|
}
|
|
9750
9907
|
function formatPreexistingRootMessage(projectRoot) {
|
|
9751
9908
|
const preexisting = [];
|
|
9752
|
-
if (
|
|
9753
|
-
if (
|
|
9909
|
+
if (existsSync9(join21(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
|
|
9910
|
+
if (existsSync9(join21(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
|
|
9754
9911
|
if (preexisting.length === 0) return null;
|
|
9755
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.`;
|
|
9756
9913
|
}
|
|
@@ -9776,7 +9933,7 @@ function createFabricServer(tracker) {
|
|
|
9776
9933
|
const server = new McpServer(
|
|
9777
9934
|
{
|
|
9778
9935
|
name: "fabric-knowledge-server",
|
|
9779
|
-
version: "2.2.0
|
|
9936
|
+
version: "2.2.0"
|
|
9780
9937
|
},
|
|
9781
9938
|
{
|
|
9782
9939
|
instructions: FABRIC_SERVER_INSTRUCTIONS
|
|
@@ -9795,10 +9952,10 @@ function createFabricServer(tracker) {
|
|
|
9795
9952
|
},
|
|
9796
9953
|
async (_uri) => {
|
|
9797
9954
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
9798
|
-
const path =
|
|
9955
|
+
const path = join21(projectRoot, ".fabric", "bootstrap", "README.md");
|
|
9799
9956
|
let text = "";
|
|
9800
|
-
if (
|
|
9801
|
-
text = await
|
|
9957
|
+
if (existsSync9(path)) {
|
|
9958
|
+
text = await readFile17(path, "utf8");
|
|
9802
9959
|
}
|
|
9803
9960
|
return {
|
|
9804
9961
|
contents: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-server",
|
|
3
|
-
"version": "2.2.0
|
|
3
|
+
"version": "2.2.0",
|
|
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
|
|
40
|
+
"@fenglimg/fabric-shared": "2.2.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.15.0",
|