@fenglimg/fabric-server 2.3.0-rc.2 → 2.3.0-rc.3
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 +26 -1
- package/dist/index.js +116 -100
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -530,6 +530,31 @@ declare function runDoctorConflictLint(projectRoot: string, opts?: {
|
|
|
530
530
|
judge?: ConflictJudge;
|
|
531
531
|
}): Promise<ConflictLintReport>;
|
|
532
532
|
|
|
533
|
+
declare function readEmbedConfig(projectRoot: string): {
|
|
534
|
+
enabled: boolean;
|
|
535
|
+
weight: number;
|
|
536
|
+
model: string;
|
|
537
|
+
};
|
|
538
|
+
declare function readFusion(projectRoot: string): "additive" | "rrf" | "auto";
|
|
539
|
+
|
|
540
|
+
interface Embedder {
|
|
541
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
542
|
+
}
|
|
543
|
+
declare const OPTIONAL_EMBED_PACKAGE = "fastembed";
|
|
544
|
+
declare function defaultEmbedCacheDir(): string;
|
|
545
|
+
/**
|
|
546
|
+
* Lazy-load the optional fastembed embedder, pinned to CPU + cache-only. Returns
|
|
547
|
+
* null (cached) when the package is not installed or initialization throws, so
|
|
548
|
+
* every caller degrades to the text-only path. Never throws.
|
|
549
|
+
*
|
|
550
|
+
* v2.1 ③: `modelName` (a fastembed EmbeddingModel enum value) selects the
|
|
551
|
+
* embedding model — the caller threads `embed_model` config through so the
|
|
552
|
+
* Chinese-heavy KB no longer embeds against fastembed's English default. The
|
|
553
|
+
* load is cached per-process; the FIRST model wins (a config change needs a
|
|
554
|
+
* server restart, already the norm for MCP config changes).
|
|
555
|
+
*/
|
|
556
|
+
declare function loadEmbedder(modelName?: string): Promise<Embedder | null>;
|
|
557
|
+
|
|
533
558
|
interface RetiredToken {
|
|
534
559
|
/** The exact dead token as it appears in agent-facing text. */
|
|
535
560
|
token: string;
|
|
@@ -949,4 +974,4 @@ interface ShutdownHandlerDeps {
|
|
|
949
974
|
*/
|
|
950
975
|
declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
|
|
951
976
|
|
|
952
|
-
export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, type PlanContextInput, type PlanContextResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, bumpCounter, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, detectUnboundProject, drainCounters, enrichDescriptions, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectRetiredReferences, lintConflicts, loadConflictEntries, pairSimilarity, planContext, readEventLedger, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, reviewPending, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
|
|
977
|
+
export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, OPTIONAL_EMBED_PACKAGE, type PlanContextInput, type PlanContextResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, bumpCounter, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, defaultEmbedCacheDir, detectUnboundProject, drainCounters, enrichDescriptions, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectRetiredReferences, lintConflicts, loadConflictEntries, loadEmbedder, pairSimilarity, planContext, readEmbedConfig, readEventLedger, readFusion, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, reviewPending, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { existsSync as existsSync9 } from "fs";
|
|
3
3
|
import { readFile as readFile20 } from "fs/promises";
|
|
4
|
-
import { join as
|
|
4
|
+
import { join as join25, 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";
|
|
@@ -820,9 +820,10 @@ function readOrphanDemoteThresholdDays(projectRoot) {
|
|
|
820
820
|
function readFusion(projectRoot) {
|
|
821
821
|
try {
|
|
822
822
|
const raw = readFabricConfig(projectRoot).fusion;
|
|
823
|
-
|
|
823
|
+
if (raw === "rrf" || raw === "additive") return raw;
|
|
824
|
+
return "auto";
|
|
824
825
|
} catch {
|
|
825
|
-
return "
|
|
826
|
+
return "auto";
|
|
826
827
|
}
|
|
827
828
|
}
|
|
828
829
|
function readConflictLintThreshold(projectRoot) {
|
|
@@ -2752,9 +2753,12 @@ var LEDGER_DUAL_WRITE_METRIC_NAMES = {
|
|
|
2752
2753
|
|
|
2753
2754
|
// src/services/plan-context.ts
|
|
2754
2755
|
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
2755
|
-
import { join as
|
|
2756
|
+
import { join as join10 } from "path";
|
|
2756
2757
|
|
|
2757
2758
|
// src/services/vector-retrieval.ts
|
|
2759
|
+
import { mkdirSync } from "fs";
|
|
2760
|
+
import { join as join9 } from "path";
|
|
2761
|
+
import { resolveGlobalRoot as resolveGlobalRoot3 } from "@fenglimg/fabric-shared";
|
|
2758
2762
|
var embedderLoad;
|
|
2759
2763
|
var OPTIONAL_EMBED_PACKAGE = "fastembed";
|
|
2760
2764
|
var embedderModuleLoader = (name) => import(name);
|
|
@@ -2771,10 +2775,13 @@ function hintMissingEmbedderOnce() {
|
|
|
2771
2775
|
missingEmbedderHinted = true;
|
|
2772
2776
|
emitMissingEmbedderHint();
|
|
2773
2777
|
}
|
|
2778
|
+
function defaultEmbedCacheDir() {
|
|
2779
|
+
return join9(resolveGlobalRoot3(), "cache", "embed");
|
|
2780
|
+
}
|
|
2774
2781
|
function buildEmbedInitOptions(modelName) {
|
|
2775
2782
|
return {
|
|
2776
2783
|
maxLength: 512,
|
|
2777
|
-
cacheDir: process.env.FABRIC_EMBED_CACHE_DIR,
|
|
2784
|
+
cacheDir: process.env.FABRIC_EMBED_CACHE_DIR ?? defaultEmbedCacheDir(),
|
|
2778
2785
|
...typeof modelName === "string" && modelName.length > 0 ? { model: modelName } : {}
|
|
2779
2786
|
};
|
|
2780
2787
|
}
|
|
@@ -2788,7 +2795,9 @@ async function loadEmbedder(modelName) {
|
|
|
2788
2795
|
hintMissingEmbedderOnce();
|
|
2789
2796
|
return null;
|
|
2790
2797
|
}
|
|
2791
|
-
const
|
|
2798
|
+
const initOpts = buildEmbedInitOptions(modelName);
|
|
2799
|
+
mkdirSync(initOpts.cacheDir, { recursive: true });
|
|
2800
|
+
const model = await mod.FlagEmbedding.init(initOpts);
|
|
2792
2801
|
return {
|
|
2793
2802
|
async embed(texts) {
|
|
2794
2803
|
const out = [];
|
|
@@ -3157,7 +3166,7 @@ var bm25BuildCount = 0;
|
|
|
3157
3166
|
var BM25_CACHE_DIR = ".fabric/cache/bm25";
|
|
3158
3167
|
function bm25CachePath(projectRoot, revision) {
|
|
3159
3168
|
const safe = revision.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
3160
|
-
return
|
|
3169
|
+
return join10(projectRoot, BM25_CACHE_DIR, `${safe}.json`);
|
|
3161
3170
|
}
|
|
3162
3171
|
async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
3163
3172
|
try {
|
|
@@ -3172,7 +3181,7 @@ async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
|
3172
3181
|
async function saveBm25ModelToDisk(projectRoot, revision, model) {
|
|
3173
3182
|
try {
|
|
3174
3183
|
const path = bm25CachePath(projectRoot, revision);
|
|
3175
|
-
await mkdir4(
|
|
3184
|
+
await mkdir4(join10(projectRoot, BM25_CACHE_DIR), { recursive: true });
|
|
3176
3185
|
await writeFile2(path, JSON.stringify(serializeBm25Model(model)), "utf8");
|
|
3177
3186
|
} catch {
|
|
3178
3187
|
}
|
|
@@ -3271,7 +3280,9 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
|
|
|
3271
3280
|
scoringContext.vectorWeight = embedConfig.weight;
|
|
3272
3281
|
}
|
|
3273
3282
|
}
|
|
3274
|
-
|
|
3283
|
+
const configuredFusion = readFusion(projectRoot);
|
|
3284
|
+
const vectorActive = scoringContext.vectorScores !== void 0 && scoringContext.vectorScores.size > 0;
|
|
3285
|
+
scoringContext.fusion = configuredFusion === "auto" ? vectorActive ? "rrf" : "additive" : configuredFusion;
|
|
3275
3286
|
if (scoringContext.fusion === "rrf" && scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
|
|
3276
3287
|
const rankIds = rawItems.map((item) => item.stable_id).sort((a, b) => compareStableIds(a, b));
|
|
3277
3288
|
if (scoringContext.bm25 !== void 0) {
|
|
@@ -3909,7 +3920,7 @@ import { execFileSync } from "child_process";
|
|
|
3909
3920
|
import { existsSync as existsSync5 } from "fs";
|
|
3910
3921
|
import { readFile as readFile7, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
3911
3922
|
import { homedir } from "os";
|
|
3912
|
-
import { basename, isAbsolute, join as
|
|
3923
|
+
import { basename, isAbsolute, join as join12, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
3913
3924
|
|
|
3914
3925
|
// src/services/promotion-gate.ts
|
|
3915
3926
|
function toLocalId(id) {
|
|
@@ -3950,7 +3961,7 @@ import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@
|
|
|
3950
3961
|
// src/services/pending-dedupe.ts
|
|
3951
3962
|
import { existsSync as existsSync4 } from "fs";
|
|
3952
3963
|
import { readdir, readFile as readFile6, unlink } from "fs/promises";
|
|
3953
|
-
import { join as
|
|
3964
|
+
import { join as join11 } from "path";
|
|
3954
3965
|
var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
|
|
3955
3966
|
var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
|
|
3956
3967
|
var SOURCE_SESSIONS_LINE = /^source_sessions:\s*\[(.*)\]\s*$/mu;
|
|
@@ -4023,7 +4034,7 @@ async function mergePendingTwins(projectRoot) {
|
|
|
4023
4034
|
continue;
|
|
4024
4035
|
}
|
|
4025
4036
|
for (const type of PENDING_TYPES) {
|
|
4026
|
-
const dir =
|
|
4037
|
+
const dir = join11(pendingBase2, type);
|
|
4027
4038
|
if (!existsSync4(dir)) continue;
|
|
4028
4039
|
let names;
|
|
4029
4040
|
try {
|
|
@@ -4044,7 +4055,7 @@ async function mergePendingTwins(projectRoot) {
|
|
|
4044
4055
|
if (groupNames.length < 2) continue;
|
|
4045
4056
|
const parsed = [];
|
|
4046
4057
|
for (const name of groupNames) {
|
|
4047
|
-
const abs =
|
|
4058
|
+
const abs = join11(dir, name);
|
|
4048
4059
|
let content;
|
|
4049
4060
|
try {
|
|
4050
4061
|
content = await readFile6(abs, "utf8");
|
|
@@ -4227,7 +4238,7 @@ async function listPending(projectRoot, filters) {
|
|
|
4227
4238
|
}
|
|
4228
4239
|
for (const source of sources) {
|
|
4229
4240
|
for (const type of typesToScan) {
|
|
4230
|
-
const dir =
|
|
4241
|
+
const dir = join12(source.root, type);
|
|
4231
4242
|
if (!existsSync5(dir)) {
|
|
4232
4243
|
continue;
|
|
4233
4244
|
}
|
|
@@ -4239,7 +4250,7 @@ async function listPending(projectRoot, filters) {
|
|
|
4239
4250
|
}
|
|
4240
4251
|
for (const name of entries) {
|
|
4241
4252
|
if (!name.endsWith(".md")) continue;
|
|
4242
|
-
const absolutePath =
|
|
4253
|
+
const absolutePath = join12(dir, name);
|
|
4243
4254
|
let content;
|
|
4244
4255
|
try {
|
|
4245
4256
|
content = await readFile7(absolutePath, "utf8");
|
|
@@ -4366,7 +4377,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4366
4377
|
);
|
|
4367
4378
|
allocatedId = stableId;
|
|
4368
4379
|
const newFilename = `${stableId}--${slug}.md`;
|
|
4369
|
-
targetAbs =
|
|
4380
|
+
targetAbs = join12(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
|
|
4370
4381
|
await ensureParentDirectory(targetAbs);
|
|
4371
4382
|
const rewritten = rewriteFrontmatterMerge(
|
|
4372
4383
|
rewriteFrontmatterForPromote(content, stableId),
|
|
@@ -4553,7 +4564,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
4553
4564
|
pluralType,
|
|
4554
4565
|
resolveWriteTargetStoreDir(toLayer, projectRoot)
|
|
4555
4566
|
);
|
|
4556
|
-
const toAbs =
|
|
4567
|
+
const toAbs = join12(
|
|
4557
4568
|
resolveStoreCanonicalBase(toLayer, projectRoot),
|
|
4558
4569
|
pluralType,
|
|
4559
4570
|
`${newStableId}--${slug}.md`
|
|
@@ -4653,7 +4664,7 @@ function getSearchDirectoryCache(cacheKey) {
|
|
|
4653
4664
|
return created;
|
|
4654
4665
|
}
|
|
4655
4666
|
async function listIndexedSearchEntries(source, type) {
|
|
4656
|
-
const dir =
|
|
4667
|
+
const dir = join12(source.root, type);
|
|
4657
4668
|
let entries;
|
|
4658
4669
|
try {
|
|
4659
4670
|
entries = await readdir2(dir);
|
|
@@ -4666,7 +4677,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4666
4677
|
const indexed = [];
|
|
4667
4678
|
for (const name of entries) {
|
|
4668
4679
|
if (!name.endsWith(".md")) continue;
|
|
4669
|
-
const absolutePath =
|
|
4680
|
+
const absolutePath = join12(dir, name);
|
|
4670
4681
|
let fingerprint;
|
|
4671
4682
|
try {
|
|
4672
4683
|
const st = await stat2(absolutePath);
|
|
@@ -5195,7 +5206,7 @@ function registerPending(server, tracker) {
|
|
|
5195
5206
|
// src/services/doctor.ts
|
|
5196
5207
|
import { access as access4, readFile as readFile17, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
5197
5208
|
import { constants as constants2 } from "fs";
|
|
5198
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5209
|
+
import { isAbsolute as isAbsolute2, join as join23, posix as posix5, resolve as resolve3 } from "path";
|
|
5199
5210
|
import {
|
|
5200
5211
|
createTranslator,
|
|
5201
5212
|
forensicReportSchema,
|
|
@@ -5410,12 +5421,12 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
|
|
|
5410
5421
|
|
|
5411
5422
|
// src/services/doctor-stable-id-collision.ts
|
|
5412
5423
|
import { readFile as readFile8 } from "fs/promises";
|
|
5413
|
-
import { basename as basename2, join as
|
|
5424
|
+
import { basename as basename2, join as join13 } from "path";
|
|
5414
5425
|
import {
|
|
5415
5426
|
buildStoreResolveInput as buildStoreResolveInput4,
|
|
5416
5427
|
createStoreResolver as createStoreResolver4,
|
|
5417
5428
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores2,
|
|
5418
|
-
resolveGlobalRoot as
|
|
5429
|
+
resolveGlobalRoot as resolveGlobalRoot4,
|
|
5419
5430
|
storeRelativePathForMount as storeRelativePathForMount3
|
|
5420
5431
|
} from "@fenglimg/fabric-shared";
|
|
5421
5432
|
var ID_LINE = /^id:\s*"?([^"\n]+?)"?\s*$/mu;
|
|
@@ -5431,13 +5442,13 @@ function resolveIntegrityStores(projectRoot) {
|
|
|
5431
5442
|
const personalUuids = new Set(
|
|
5432
5443
|
input.mountedStores.filter((s) => s.personal).map((s) => s.store_uuid)
|
|
5433
5444
|
);
|
|
5434
|
-
const globalRoot =
|
|
5445
|
+
const globalRoot = resolveGlobalRoot4();
|
|
5435
5446
|
const dirs = readSet.stores.map((entry) => {
|
|
5436
5447
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
5437
5448
|
return {
|
|
5438
5449
|
store_uuid: entry.store_uuid,
|
|
5439
5450
|
alias: entry.alias,
|
|
5440
|
-
dir:
|
|
5451
|
+
dir: join13(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
|
|
5441
5452
|
};
|
|
5442
5453
|
});
|
|
5443
5454
|
return { dirs, personalUuids };
|
|
@@ -5541,7 +5552,7 @@ function createLayerMismatchCheck(t, inspection) {
|
|
|
5541
5552
|
// src/services/doctor-relevance-paths.ts
|
|
5542
5553
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5543
5554
|
import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
|
|
5544
|
-
import { join as
|
|
5555
|
+
import { join as join14, sep as sep3 } from "path";
|
|
5545
5556
|
import { minimatch } from "minimatch";
|
|
5546
5557
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
5547
5558
|
var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
|
|
@@ -5584,7 +5595,7 @@ function collectWorkspacePaths(projectRoot) {
|
|
|
5584
5595
|
continue;
|
|
5585
5596
|
}
|
|
5586
5597
|
for (const entry of entries) {
|
|
5587
|
-
const abs =
|
|
5598
|
+
const abs = join14(current, entry.name);
|
|
5588
5599
|
const rel = toPosix(abs.slice(projectRoot.length + 1));
|
|
5589
5600
|
if (rel.length === 0) continue;
|
|
5590
5601
|
if (entry.isDirectory()) {
|
|
@@ -5778,13 +5789,13 @@ function createNarrowNoPathsCheck(t, inspection) {
|
|
|
5778
5789
|
|
|
5779
5790
|
// src/services/doctor-broad-index.ts
|
|
5780
5791
|
import { readFile as readFile9 } from "fs/promises";
|
|
5781
|
-
import { join as
|
|
5792
|
+
import { join as join15 } from "path";
|
|
5782
5793
|
var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
|
|
5783
5794
|
var BROAD_INDEX_BACKSTOP_MIN = 20;
|
|
5784
5795
|
var BROAD_INDEX_BACKSTOP_MAX = 500;
|
|
5785
5796
|
var BROAD_INDEX_DRIFT_RATIO = 0.8;
|
|
5786
5797
|
async function readBroadIndexBackstop(projectRoot) {
|
|
5787
|
-
const configPath =
|
|
5798
|
+
const configPath = join15(projectRoot, ".fabric", "fabric-config.json");
|
|
5788
5799
|
try {
|
|
5789
5800
|
const raw = await readFile9(configPath, "utf8");
|
|
5790
5801
|
const parsed = JSON.parse(raw);
|
|
@@ -6104,14 +6115,14 @@ function createBroadReviewRecheckCheck(t, inspection) {
|
|
|
6104
6115
|
|
|
6105
6116
|
// src/services/doctor-scope-lint.ts
|
|
6106
6117
|
import { readFile as readFile10 } from "fs/promises";
|
|
6107
|
-
import { join as
|
|
6118
|
+
import { join as join16 } from "path";
|
|
6108
6119
|
import {
|
|
6109
6120
|
buildStoreResolveInput as buildStoreResolveInput5,
|
|
6110
6121
|
createStoreResolver as createStoreResolver5,
|
|
6111
6122
|
isPersonalScope as isPersonalScope3,
|
|
6112
6123
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores3,
|
|
6113
6124
|
readStoreProjects,
|
|
6114
|
-
resolveGlobalRoot as
|
|
6125
|
+
resolveGlobalRoot as resolveGlobalRoot5,
|
|
6115
6126
|
scopeRoot as scopeRoot2,
|
|
6116
6127
|
storeRelativePathForMount as storeRelativePathForMount4
|
|
6117
6128
|
} from "@fenglimg/fabric-shared";
|
|
@@ -6136,10 +6147,10 @@ async function resolveLintStores(projectRoot) {
|
|
|
6136
6147
|
const personalUuids = new Set(
|
|
6137
6148
|
input.mountedStores.filter((s) => s.personal).map((s) => s.store_uuid)
|
|
6138
6149
|
);
|
|
6139
|
-
const globalRoot =
|
|
6150
|
+
const globalRoot = resolveGlobalRoot5();
|
|
6140
6151
|
return Promise.all(readSet.stores.map(async (entry) => {
|
|
6141
6152
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
6142
|
-
const dir =
|
|
6153
|
+
const dir = join16(
|
|
6143
6154
|
globalRoot,
|
|
6144
6155
|
storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
|
|
6145
6156
|
);
|
|
@@ -6294,7 +6305,7 @@ function createUnboundProjectCheck(t, violation) {
|
|
|
6294
6305
|
|
|
6295
6306
|
// src/services/doctor-store-counters.ts
|
|
6296
6307
|
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
6297
|
-
import { join as
|
|
6308
|
+
import { join as join17 } from "path";
|
|
6298
6309
|
import {
|
|
6299
6310
|
buildStoreResolveInput as buildStoreResolveInput6,
|
|
6300
6311
|
createStoreResolver as createStoreResolver6,
|
|
@@ -6303,7 +6314,7 @@ import {
|
|
|
6303
6314
|
parseKnowledgeId as parseKnowledgeId2,
|
|
6304
6315
|
readStoreCounters,
|
|
6305
6316
|
reconcileStoreCounters,
|
|
6306
|
-
resolveGlobalRoot as
|
|
6317
|
+
resolveGlobalRoot as resolveGlobalRoot6,
|
|
6307
6318
|
STORE_KNOWLEDGE_TYPE_DIRS,
|
|
6308
6319
|
STORE_LAYOUT as STORE_LAYOUT2,
|
|
6309
6320
|
storeRelativePathForMount as storeRelativePathForMount5
|
|
@@ -6317,11 +6328,11 @@ function resolveCounterStores(projectRoot) {
|
|
|
6317
6328
|
if (readSet.stores.length === 0) {
|
|
6318
6329
|
return [];
|
|
6319
6330
|
}
|
|
6320
|
-
const globalRoot =
|
|
6331
|
+
const globalRoot = resolveGlobalRoot6();
|
|
6321
6332
|
return readSet.stores.map((entry) => ({
|
|
6322
6333
|
uuid: entry.store_uuid,
|
|
6323
6334
|
alias: entry.alias,
|
|
6324
|
-
dir:
|
|
6335
|
+
dir: join17(
|
|
6325
6336
|
globalRoot,
|
|
6326
6337
|
storeRelativePathForMount5(
|
|
6327
6338
|
input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
|
|
@@ -6349,7 +6360,7 @@ function readEntryId(file) {
|
|
|
6349
6360
|
function computeStoreDiskMax(storeDir) {
|
|
6350
6361
|
const max = defaultAgentsMetaCounters();
|
|
6351
6362
|
for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
6352
|
-
const dir =
|
|
6363
|
+
const dir = join17(storeDir, STORE_LAYOUT2.knowledgeDir, type);
|
|
6353
6364
|
if (!existsSync7(dir)) {
|
|
6354
6365
|
continue;
|
|
6355
6366
|
}
|
|
@@ -6363,7 +6374,7 @@ function computeStoreDiskMax(storeDir) {
|
|
|
6363
6374
|
if (!name.endsWith(".md")) {
|
|
6364
6375
|
continue;
|
|
6365
6376
|
}
|
|
6366
|
-
const parsed = parseKnowledgeId2(readEntryId(
|
|
6377
|
+
const parsed = parseKnowledgeId2(readEntryId(join17(dir, name)) ?? "");
|
|
6367
6378
|
if (parsed === null) {
|
|
6368
6379
|
continue;
|
|
6369
6380
|
}
|
|
@@ -6446,14 +6457,14 @@ function createStoreCounterCheck(t, drifts) {
|
|
|
6446
6457
|
|
|
6447
6458
|
// src/services/doctor-store-orphan.ts
|
|
6448
6459
|
import { readdirSync as readdirSync3 } from "fs";
|
|
6449
|
-
import { join as
|
|
6460
|
+
import { join as join18 } from "path";
|
|
6450
6461
|
import {
|
|
6451
6462
|
STORES_ROOT_DIR,
|
|
6452
6463
|
addMountedStore,
|
|
6453
6464
|
disambiguateAlias,
|
|
6454
6465
|
loadGlobalConfig,
|
|
6455
6466
|
readStoreIdentity,
|
|
6456
|
-
resolveGlobalRoot as
|
|
6467
|
+
resolveGlobalRoot as resolveGlobalRoot7,
|
|
6457
6468
|
saveGlobalConfig
|
|
6458
6469
|
} from "@fenglimg/fabric-shared";
|
|
6459
6470
|
var STORE_BY_ALIAS_DIR = "by-alias";
|
|
@@ -6464,21 +6475,21 @@ function listDir(dir) {
|
|
|
6464
6475
|
return [];
|
|
6465
6476
|
}
|
|
6466
6477
|
}
|
|
6467
|
-
function inspectStoreOrphans(globalRoot =
|
|
6478
|
+
function inspectStoreOrphans(globalRoot = resolveGlobalRoot7()) {
|
|
6468
6479
|
const config = loadGlobalConfig(globalRoot);
|
|
6469
6480
|
if (config === null) {
|
|
6470
6481
|
return [];
|
|
6471
6482
|
}
|
|
6472
6483
|
const registered = new Set(config.stores.map((s) => s.store_uuid));
|
|
6473
|
-
const storesRoot =
|
|
6484
|
+
const storesRoot = join18(globalRoot, STORES_ROOT_DIR);
|
|
6474
6485
|
const orphans = [];
|
|
6475
6486
|
for (const group of listDir(storesRoot)) {
|
|
6476
6487
|
if (group === STORE_BY_ALIAS_DIR) {
|
|
6477
6488
|
continue;
|
|
6478
6489
|
}
|
|
6479
|
-
const groupDir =
|
|
6490
|
+
const groupDir = join18(storesRoot, group);
|
|
6480
6491
|
for (const mount of listDir(groupDir)) {
|
|
6481
|
-
const dir =
|
|
6492
|
+
const dir = join18(groupDir, mount);
|
|
6482
6493
|
const identity = readStoreIdentity(dir);
|
|
6483
6494
|
if (identity === null || registered.has(identity.store_uuid)) {
|
|
6484
6495
|
continue;
|
|
@@ -6488,7 +6499,7 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
|
|
|
6488
6499
|
}
|
|
6489
6500
|
return orphans;
|
|
6490
6501
|
}
|
|
6491
|
-
function fixStoreOrphans(globalRoot =
|
|
6502
|
+
function fixStoreOrphans(globalRoot = resolveGlobalRoot7()) {
|
|
6492
6503
|
let config = loadGlobalConfig(globalRoot);
|
|
6493
6504
|
if (config === null) {
|
|
6494
6505
|
return [];
|
|
@@ -6616,7 +6627,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
6616
6627
|
|
|
6617
6628
|
// src/services/doctor-skill-lints.ts
|
|
6618
6629
|
import { readdir as readdir3, readFile as readFile11 } from "fs/promises";
|
|
6619
|
-
import { join as
|
|
6630
|
+
import { join as join19, posix as posix2 } from "path";
|
|
6620
6631
|
var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review"];
|
|
6621
6632
|
var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
|
|
6622
6633
|
var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
|
|
@@ -6646,8 +6657,8 @@ async function listMarkdownFiles(dir) {
|
|
|
6646
6657
|
async function inspectSkillRefMirror(projectRoot) {
|
|
6647
6658
|
const driftedPaths = [];
|
|
6648
6659
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6649
|
-
const claudeRef =
|
|
6650
|
-
const codexRef =
|
|
6660
|
+
const claudeRef = join19(projectRoot, ".claude", "skills", slug, "ref");
|
|
6661
|
+
const codexRef = join19(projectRoot, ".codex", "skills", slug, "ref");
|
|
6651
6662
|
const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
|
|
6652
6663
|
if (claudeFiles === null || codexFiles === null) continue;
|
|
6653
6664
|
const claudeSet = new Set(claudeFiles);
|
|
@@ -6664,8 +6675,8 @@ async function inspectSkillRefMirror(projectRoot) {
|
|
|
6664
6675
|
let codexBody;
|
|
6665
6676
|
try {
|
|
6666
6677
|
[claudeBody, codexBody] = await Promise.all([
|
|
6667
|
-
readFile11(
|
|
6668
|
-
readFile11(
|
|
6678
|
+
readFile11(join19(claudeRef, fname), "utf8"),
|
|
6679
|
+
readFile11(join19(codexRef, fname), "utf8")
|
|
6669
6680
|
]);
|
|
6670
6681
|
} catch {
|
|
6671
6682
|
continue;
|
|
@@ -6684,7 +6695,7 @@ async function inspectSkillTokenBudget(projectRoot) {
|
|
|
6684
6695
|
const overSize = [];
|
|
6685
6696
|
let highestSeverity = "ok";
|
|
6686
6697
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6687
|
-
const skillMdPath =
|
|
6698
|
+
const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6688
6699
|
let body;
|
|
6689
6700
|
try {
|
|
6690
6701
|
body = await readFile11(skillMdPath, "utf8");
|
|
@@ -6708,7 +6719,7 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6708
6719
|
const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
|
|
6709
6720
|
const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
|
|
6710
6721
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6711
|
-
const skillMdPath =
|
|
6722
|
+
const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6712
6723
|
let body;
|
|
6713
6724
|
try {
|
|
6714
6725
|
body = await readFile11(skillMdPath, "utf8");
|
|
@@ -6742,7 +6753,7 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6742
6753
|
async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
6743
6754
|
const candidates = [];
|
|
6744
6755
|
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
6745
|
-
const rootAbs =
|
|
6756
|
+
const rootAbs = join19(projectRoot, rootRel);
|
|
6746
6757
|
let dirEntries;
|
|
6747
6758
|
try {
|
|
6748
6759
|
dirEntries = await readdir3(rootAbs, { withFileTypes: true });
|
|
@@ -6751,7 +6762,7 @@ async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
|
6751
6762
|
}
|
|
6752
6763
|
for (const dirEntry of dirEntries) {
|
|
6753
6764
|
if (!dirEntry.isDirectory()) continue;
|
|
6754
|
-
const skillFile =
|
|
6765
|
+
const skillFile = join19(rootAbs, dirEntry.name, "SKILL.md");
|
|
6755
6766
|
let raw;
|
|
6756
6767
|
try {
|
|
6757
6768
|
raw = await readFile11(skillFile, "utf8");
|
|
@@ -6878,7 +6889,7 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
|
|
|
6878
6889
|
|
|
6879
6890
|
// src/services/doctor-retired-references-lint.ts
|
|
6880
6891
|
import { readdir as readdir4, readFile as readFile12, stat as stat3 } from "fs/promises";
|
|
6881
|
-
import { join as
|
|
6892
|
+
import { join as join20, posix as posix3, relative as relative2 } from "path";
|
|
6882
6893
|
var RETIRED_TOKENS = [
|
|
6883
6894
|
{ token: "fab_plan_context", replacement: "fab_recall", reason: "retrieval collapsed to one lean fab_recall (KT-DEC-0026)" },
|
|
6884
6895
|
{ token: "fab_get_knowledge_sections", replacement: "fab_recall", reason: "two-step fetch retired (KT-DEC-0026)" },
|
|
@@ -6901,7 +6912,7 @@ var RETIRED_TOKENS = [
|
|
|
6901
6912
|
];
|
|
6902
6913
|
var HOOK_DIRS = [".claude/hooks", ".codex/hooks"];
|
|
6903
6914
|
var SKILL_DIRS = [".claude/skills", ".codex/skills"];
|
|
6904
|
-
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md",
|
|
6915
|
+
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md", join20(".fabric", "AGENTS.md")];
|
|
6905
6916
|
function isCommentLine(line) {
|
|
6906
6917
|
const t = line.trim();
|
|
6907
6918
|
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
@@ -6915,7 +6926,7 @@ async function walkFiles(dir, exts) {
|
|
|
6915
6926
|
}
|
|
6916
6927
|
const out = [];
|
|
6917
6928
|
for (const e of entries) {
|
|
6918
|
-
const full =
|
|
6929
|
+
const full = join20(dir, e.name);
|
|
6919
6930
|
if (e.isDirectory()) {
|
|
6920
6931
|
out.push(...await walkFiles(full, exts));
|
|
6921
6932
|
} else if (exts.some((ext) => e.name.endsWith(ext))) {
|
|
@@ -6941,7 +6952,7 @@ async function inspectRetiredReferences(projectRoot) {
|
|
|
6941
6952
|
let scannedFiles = 0;
|
|
6942
6953
|
const toRel = (p) => posix3.normalize(relative2(projectRoot, p).split("\\").join("/"));
|
|
6943
6954
|
for (const rel of BOOTSTRAP_FILES) {
|
|
6944
|
-
const abs =
|
|
6955
|
+
const abs = join20(projectRoot, rel);
|
|
6945
6956
|
try {
|
|
6946
6957
|
if ((await stat3(abs)).isFile()) {
|
|
6947
6958
|
scannedFiles += 1;
|
|
@@ -6951,7 +6962,7 @@ async function inspectRetiredReferences(projectRoot) {
|
|
|
6951
6962
|
}
|
|
6952
6963
|
}
|
|
6953
6964
|
for (const dir of SKILL_DIRS) {
|
|
6954
|
-
for (const file of await walkFiles(
|
|
6965
|
+
for (const file of await walkFiles(join20(projectRoot, dir), [".md"])) {
|
|
6955
6966
|
scannedFiles += 1;
|
|
6956
6967
|
try {
|
|
6957
6968
|
scanText(toRel(file), await readFile12(file, "utf8"), false, hits);
|
|
@@ -6960,7 +6971,7 @@ async function inspectRetiredReferences(projectRoot) {
|
|
|
6960
6971
|
}
|
|
6961
6972
|
}
|
|
6962
6973
|
for (const dir of HOOK_DIRS) {
|
|
6963
|
-
for (const file of await walkFiles(
|
|
6974
|
+
for (const file of await walkFiles(join20(projectRoot, dir), [".cjs"])) {
|
|
6964
6975
|
scannedFiles += 1;
|
|
6965
6976
|
try {
|
|
6966
6977
|
scanText(toRel(file), await readFile12(file, "utf8"), true, hits);
|
|
@@ -6999,7 +7010,7 @@ function createRetiredReferenceCheck(t, inspection) {
|
|
|
6999
7010
|
// src/services/doctor-hooks-lints.ts
|
|
7000
7011
|
import { constants } from "fs";
|
|
7001
7012
|
import { access, readdir as readdir5, readFile as readFile13, stat as stat4 } from "fs/promises";
|
|
7002
|
-
import { join as
|
|
7013
|
+
import { join as join21, posix as posix4 } from "path";
|
|
7003
7014
|
import { Script } from "vm";
|
|
7004
7015
|
var HOOKS_RUNTIME_CLIENT_DIRS = [
|
|
7005
7016
|
{ client: "claude", dir: ".claude/hooks" },
|
|
@@ -7061,11 +7072,11 @@ async function isFile(absPath) {
|
|
|
7061
7072
|
}
|
|
7062
7073
|
}
|
|
7063
7074
|
async function inspectHooksWired(projectRoot) {
|
|
7064
|
-
const claudeEntries = await readDirectoryFileNames(
|
|
7075
|
+
const claudeEntries = await readDirectoryFileNames(join21(projectRoot, ".claude"));
|
|
7065
7076
|
if (claudeEntries === null) {
|
|
7066
7077
|
return { status: "skipped", missingHooks: [] };
|
|
7067
7078
|
}
|
|
7068
|
-
const settingsPath =
|
|
7079
|
+
const settingsPath = join21(projectRoot, ".claude", "settings.json");
|
|
7069
7080
|
let parsed;
|
|
7070
7081
|
try {
|
|
7071
7082
|
parsed = JSON.parse(await readFile13(settingsPath, "utf8"));
|
|
@@ -7091,8 +7102,8 @@ async function inspectHooksWired(projectRoot) {
|
|
|
7091
7102
|
}
|
|
7092
7103
|
async function inspectHookCacheWritability(projectRoot) {
|
|
7093
7104
|
const relPath = posix4.join(".fabric", ".cache");
|
|
7094
|
-
const fabricDir =
|
|
7095
|
-
const cacheDir =
|
|
7105
|
+
const fabricDir = join21(projectRoot, ".fabric");
|
|
7106
|
+
const cacheDir = join21(projectRoot, ".fabric", ".cache");
|
|
7096
7107
|
try {
|
|
7097
7108
|
try {
|
|
7098
7109
|
const cacheStats = await stat4(cacheDir);
|
|
@@ -7141,12 +7152,12 @@ async function inspectHookCacheWritability(projectRoot) {
|
|
|
7141
7152
|
async function inspectHooksContentDrift(projectRoot) {
|
|
7142
7153
|
const hookFilesByBasename = /* @__PURE__ */ new Map();
|
|
7143
7154
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
7144
|
-
const absDir =
|
|
7155
|
+
const absDir = join21(projectRoot, dir);
|
|
7145
7156
|
const entries = await readDirectoryFileNames(absDir);
|
|
7146
7157
|
if (entries === null) continue;
|
|
7147
7158
|
for (const name of entries) {
|
|
7148
7159
|
if (!name.endsWith(".cjs")) continue;
|
|
7149
|
-
const abs =
|
|
7160
|
+
const abs = join21(absDir, name);
|
|
7150
7161
|
if (!await isFile(abs)) continue;
|
|
7151
7162
|
const arr = hookFilesByBasename.get(name) ?? [];
|
|
7152
7163
|
arr.push({ client, abs });
|
|
@@ -7183,12 +7194,12 @@ async function inspectHooksRuntime(projectRoot) {
|
|
|
7183
7194
|
const issues = [];
|
|
7184
7195
|
let scanned = 0;
|
|
7185
7196
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
7186
|
-
const absDir =
|
|
7197
|
+
const absDir = join21(projectRoot, dir);
|
|
7187
7198
|
const entries = await readDirectoryFileNames(absDir);
|
|
7188
7199
|
if (entries === null) continue;
|
|
7189
7200
|
for (const name of entries) {
|
|
7190
7201
|
if (!name.endsWith(".cjs")) continue;
|
|
7191
|
-
const abs =
|
|
7202
|
+
const abs = join21(absDir, name);
|
|
7192
7203
|
const displayPath = `${dir}/${name}`;
|
|
7193
7204
|
if (!await isFile(abs)) continue;
|
|
7194
7205
|
scanned += 1;
|
|
@@ -7332,7 +7343,7 @@ function createHookCacheWritabilityCheck(t, inspection) {
|
|
|
7332
7343
|
|
|
7333
7344
|
// src/services/doctor-bootstrap-lints.ts
|
|
7334
7345
|
import { access as access2, readFile as readFile14 } from "fs/promises";
|
|
7335
|
-
import { join as
|
|
7346
|
+
import { join as join22 } from "path";
|
|
7336
7347
|
import {
|
|
7337
7348
|
BOOTSTRAP_MARKER_BEGIN,
|
|
7338
7349
|
BOOTSTRAP_MARKER_END,
|
|
@@ -7364,13 +7375,13 @@ async function fileExists(path) {
|
|
|
7364
7375
|
}
|
|
7365
7376
|
async function inspectBootstrapAnchor(projectRoot) {
|
|
7366
7377
|
const [hasAgentsMd, hasClaudeMd] = await Promise.all([
|
|
7367
|
-
fileExists(
|
|
7368
|
-
fileExists(
|
|
7378
|
+
fileExists(join22(projectRoot, "AGENTS.md")),
|
|
7379
|
+
fileExists(join22(projectRoot, "CLAUDE.md"))
|
|
7369
7380
|
]);
|
|
7370
7381
|
return { hasAgentsMd, hasClaudeMd };
|
|
7371
7382
|
}
|
|
7372
7383
|
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
7373
|
-
const abs =
|
|
7384
|
+
const abs = join22(target, ".fabric", "AGENTS.md");
|
|
7374
7385
|
const canonical = resolveBootstrapCanonical();
|
|
7375
7386
|
let onDisk;
|
|
7376
7387
|
try {
|
|
@@ -7400,14 +7411,14 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
|
|
|
7400
7411
|
);
|
|
7401
7412
|
}
|
|
7402
7413
|
async function inspectL2ManagedBlockDrift(target) {
|
|
7403
|
-
const snapshotPath =
|
|
7414
|
+
const snapshotPath = join22(target, ".fabric", "AGENTS.md");
|
|
7404
7415
|
let snapshot;
|
|
7405
7416
|
try {
|
|
7406
7417
|
snapshot = await readFile14(snapshotPath, "utf8");
|
|
7407
7418
|
} catch {
|
|
7408
7419
|
return { status: "ok", drifted: [] };
|
|
7409
7420
|
}
|
|
7410
|
-
const projectRulesPath =
|
|
7421
|
+
const projectRulesPath = join22(target, ".fabric", "project-rules.md");
|
|
7411
7422
|
let expectedBody = snapshot;
|
|
7412
7423
|
try {
|
|
7413
7424
|
const projectRules = await readFile14(projectRulesPath, "utf8");
|
|
@@ -7419,7 +7430,7 @@ ${projectRules}`;
|
|
|
7419
7430
|
const drifted = [];
|
|
7420
7431
|
let anyManagedBlockFound = false;
|
|
7421
7432
|
const blockTargets = [
|
|
7422
|
-
|
|
7433
|
+
join22(target, "AGENTS.md")
|
|
7423
7434
|
];
|
|
7424
7435
|
for (const abs of blockTargets) {
|
|
7425
7436
|
let content;
|
|
@@ -7447,7 +7458,7 @@ ${projectRules}`;
|
|
|
7447
7458
|
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
7448
7459
|
}
|
|
7449
7460
|
}
|
|
7450
|
-
const claudeMdPath =
|
|
7461
|
+
const claudeMdPath = join22(target, "CLAUDE.md");
|
|
7451
7462
|
try {
|
|
7452
7463
|
const claudeContent = await readFile14(claudeMdPath, "utf8");
|
|
7453
7464
|
anyManagedBlockFound = true;
|
|
@@ -8659,7 +8670,7 @@ async function runDoctorReport(target) {
|
|
|
8659
8670
|
const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
|
|
8660
8671
|
const targetFiles = Object.fromEntries(
|
|
8661
8672
|
await Promise.all(
|
|
8662
|
-
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(
|
|
8673
|
+
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join23(projectRoot, path))])
|
|
8663
8674
|
)
|
|
8664
8675
|
);
|
|
8665
8676
|
const checks = [
|
|
@@ -8863,7 +8874,7 @@ async function runDoctorFix(target) {
|
|
|
8863
8874
|
const fixed = [];
|
|
8864
8875
|
const ledgerWarnings = [];
|
|
8865
8876
|
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
8866
|
-
const snapshotPath =
|
|
8877
|
+
const snapshotPath = join23(projectRoot, ".fabric", "AGENTS.md");
|
|
8867
8878
|
await ensureParentDirectory(snapshotPath);
|
|
8868
8879
|
await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
|
|
8869
8880
|
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
@@ -8936,7 +8947,7 @@ async function runDoctorFix(target) {
|
|
|
8936
8947
|
if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
|
|
8937
8948
|
const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
|
|
8938
8949
|
if (lockInspection.present && !lockInspection.pidAlive) {
|
|
8939
|
-
const lockFilePath =
|
|
8950
|
+
const lockFilePath = join23(projectRoot, ".fabric", ".serve.lock");
|
|
8940
8951
|
try {
|
|
8941
8952
|
await unlink4(lockFilePath);
|
|
8942
8953
|
} catch (err) {
|
|
@@ -9053,7 +9064,7 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
|
|
|
9053
9064
|
}
|
|
9054
9065
|
async function applySessionHintsStaleCleanup(projectRoot, candidate) {
|
|
9055
9066
|
const detail = `deleted (${candidate.age_days}d old)`;
|
|
9056
|
-
const absPath =
|
|
9067
|
+
const absPath = join23(projectRoot, candidate.path);
|
|
9057
9068
|
try {
|
|
9058
9069
|
const { unlink: unlink5 } = await import("fs/promises");
|
|
9059
9070
|
await unlink5(absPath);
|
|
@@ -9078,7 +9089,7 @@ function truncateErrorMessage(error) {
|
|
|
9078
9089
|
return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
|
|
9079
9090
|
}
|
|
9080
9091
|
async function inspectForensic(projectRoot) {
|
|
9081
|
-
const path =
|
|
9092
|
+
const path = join23(projectRoot, ".fabric", "forensic.json");
|
|
9082
9093
|
try {
|
|
9083
9094
|
const parsed = forensicReportSchema.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
9084
9095
|
return { present: true, valid: true, report: parsed };
|
|
@@ -9655,7 +9666,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
|
|
|
9655
9666
|
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
9656
9667
|
const detected = [];
|
|
9657
9668
|
for (const name of candidates) {
|
|
9658
|
-
if (await pathExists(
|
|
9669
|
+
if (await pathExists(join23(projectRoot, name))) {
|
|
9659
9670
|
detected.push(name);
|
|
9660
9671
|
}
|
|
9661
9672
|
}
|
|
@@ -9740,7 +9751,7 @@ async function buildLastActiveIndex(projectRoot) {
|
|
|
9740
9751
|
return map;
|
|
9741
9752
|
}
|
|
9742
9753
|
async function inspectSessionHintsStale(projectRoot, now) {
|
|
9743
|
-
const cacheDir =
|
|
9754
|
+
const cacheDir = join23(projectRoot, ".fabric", ".cache");
|
|
9744
9755
|
let entries;
|
|
9745
9756
|
try {
|
|
9746
9757
|
entries = await readdirAsync(cacheDir, { withFileTypes: true });
|
|
@@ -9752,7 +9763,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
|
|
|
9752
9763
|
if (!entry.isFile()) continue;
|
|
9753
9764
|
if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
|
|
9754
9765
|
if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
|
|
9755
|
-
const absPath =
|
|
9766
|
+
const absPath = join23(cacheDir, entry.name);
|
|
9756
9767
|
let mtimeMs = 0;
|
|
9757
9768
|
try {
|
|
9758
9769
|
mtimeMs = (await statAsync(absPath)).mtimeMs;
|
|
@@ -9784,7 +9795,7 @@ function inspectStaleServeLock(projectRoot, now) {
|
|
|
9784
9795
|
};
|
|
9785
9796
|
}
|
|
9786
9797
|
async function readUnderseedThresholdFromConfig(projectRoot) {
|
|
9787
|
-
const configPath =
|
|
9798
|
+
const configPath = join23(projectRoot, ".fabric", "fabric-config.json");
|
|
9788
9799
|
try {
|
|
9789
9800
|
const raw = await readFile17(configPath, "utf8");
|
|
9790
9801
|
const parsed = JSON.parse(raw);
|
|
@@ -9902,7 +9913,7 @@ async function inspectOnboardCoverage(projectRoot) {
|
|
|
9902
9913
|
return { filled, missing, opted_out: optedOut };
|
|
9903
9914
|
}
|
|
9904
9915
|
async function readOnboardOptedOut(projectRoot) {
|
|
9905
|
-
const path =
|
|
9916
|
+
const path = join23(projectRoot, ".fabric", "fabric-config.json");
|
|
9906
9917
|
let raw;
|
|
9907
9918
|
try {
|
|
9908
9919
|
raw = await readFile17(path, "utf8");
|
|
@@ -10007,7 +10018,7 @@ async function* iterateCanonicalFilenames(projectRoot) {
|
|
|
10007
10018
|
}
|
|
10008
10019
|
}
|
|
10009
10020
|
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
10010
|
-
const snapshotPath =
|
|
10021
|
+
const snapshotPath = join23(projectRoot, ".fabric", "AGENTS.md");
|
|
10011
10022
|
if (!await pathExists(snapshotPath)) {
|
|
10012
10023
|
return;
|
|
10013
10024
|
}
|
|
@@ -10017,7 +10028,7 @@ async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
|
10017
10028
|
} catch {
|
|
10018
10029
|
return;
|
|
10019
10030
|
}
|
|
10020
|
-
const projectRulesPath =
|
|
10031
|
+
const projectRulesPath = join23(projectRoot, ".fabric", "project-rules.md");
|
|
10021
10032
|
const hasProjectRules = await pathExists(projectRulesPath);
|
|
10022
10033
|
let expectedBody = snapshot;
|
|
10023
10034
|
if (hasProjectRules) {
|
|
@@ -10033,7 +10044,7 @@ ${projectRules}`;
|
|
|
10033
10044
|
${expectedBody}
|
|
10034
10045
|
${BOOTSTRAP_MARKER_END2}`;
|
|
10035
10046
|
const blockTargets = [
|
|
10036
|
-
|
|
10047
|
+
join23(projectRoot, "AGENTS.md")
|
|
10037
10048
|
];
|
|
10038
10049
|
for (const abs of blockTargets) {
|
|
10039
10050
|
if (!await pathExists(abs)) {
|
|
@@ -10066,7 +10077,7 @@ ${managedBlock}
|
|
|
10066
10077
|
}
|
|
10067
10078
|
await atomicWriteText4(abs, next);
|
|
10068
10079
|
}
|
|
10069
|
-
const claudeMdPath =
|
|
10080
|
+
const claudeMdPath = join23(projectRoot, "CLAUDE.md");
|
|
10070
10081
|
if (await pathExists(claudeMdPath)) {
|
|
10071
10082
|
let claudeContent;
|
|
10072
10083
|
try {
|
|
@@ -10136,7 +10147,7 @@ async function collectEntryPoints(root) {
|
|
|
10136
10147
|
continue;
|
|
10137
10148
|
}
|
|
10138
10149
|
for (const entry of await readdirAsync(current, { withFileTypes: true })) {
|
|
10139
|
-
const absolutePath =
|
|
10150
|
+
const absolutePath = join23(current, entry.name);
|
|
10140
10151
|
const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
|
|
10141
10152
|
if (relativePath.length === 0) {
|
|
10142
10153
|
continue;
|
|
@@ -10351,13 +10362,13 @@ async function runDoctorConflictLint(projectRoot, opts = {}) {
|
|
|
10351
10362
|
|
|
10352
10363
|
// src/services/why-not-surfaced.ts
|
|
10353
10364
|
import { readFile as readFile18 } from "fs/promises";
|
|
10354
|
-
import { basename as basename3, join as
|
|
10365
|
+
import { basename as basename3, join as join24 } from "path";
|
|
10355
10366
|
import {
|
|
10356
10367
|
buildStoreResolveInput as buildStoreResolveInput7,
|
|
10357
10368
|
createStoreResolver as createStoreResolver7,
|
|
10358
10369
|
loadProjectConfig as loadProjectConfig4,
|
|
10359
10370
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores4,
|
|
10360
|
-
resolveGlobalRoot as
|
|
10371
|
+
resolveGlobalRoot as resolveGlobalRoot8,
|
|
10361
10372
|
scopeRoot as scopeRoot3,
|
|
10362
10373
|
storeRelativePathForMount as storeRelativePathForMount6
|
|
10363
10374
|
} from "@fenglimg/fabric-shared";
|
|
@@ -10387,11 +10398,11 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10387
10398
|
if (input === null) {
|
|
10388
10399
|
return base;
|
|
10389
10400
|
}
|
|
10390
|
-
const globalRoot =
|
|
10401
|
+
const globalRoot = resolveGlobalRoot8();
|
|
10391
10402
|
const allStores = input.mountedStores.map((s) => ({
|
|
10392
10403
|
store_uuid: s.store_uuid,
|
|
10393
10404
|
alias: s.alias,
|
|
10394
|
-
dir:
|
|
10405
|
+
dir: join24(globalRoot, storeRelativePathForMount6(s))
|
|
10395
10406
|
}));
|
|
10396
10407
|
const refs = await readKnowledgeAcrossStores4(allStores);
|
|
10397
10408
|
const candidate = refs.find((ref) => fileMatchesId(ref.file, localId));
|
|
@@ -10771,8 +10782,8 @@ function formatError(error) {
|
|
|
10771
10782
|
}
|
|
10772
10783
|
function formatPreexistingRootMessage(projectRoot) {
|
|
10773
10784
|
const preexisting = [];
|
|
10774
|
-
if (existsSync9(
|
|
10775
|
-
if (existsSync9(
|
|
10785
|
+
if (existsSync9(join25(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
|
|
10786
|
+
if (existsSync9(join25(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
|
|
10776
10787
|
if (preexisting.length === 0) return null;
|
|
10777
10788
|
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.`;
|
|
10778
10789
|
}
|
|
@@ -10799,7 +10810,7 @@ function createFabricServer(tracker) {
|
|
|
10799
10810
|
const server = new McpServer(
|
|
10800
10811
|
{
|
|
10801
10812
|
name: "fabric-knowledge-server",
|
|
10802
|
-
version: "2.3.0-rc.
|
|
10813
|
+
version: "2.3.0-rc.3"
|
|
10803
10814
|
},
|
|
10804
10815
|
{
|
|
10805
10816
|
instructions: FABRIC_SERVER_INSTRUCTIONS
|
|
@@ -10819,7 +10830,7 @@ function createFabricServer(tracker) {
|
|
|
10819
10830
|
},
|
|
10820
10831
|
async (_uri) => {
|
|
10821
10832
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
10822
|
-
const path =
|
|
10833
|
+
const path = join25(projectRoot, ".fabric", "bootstrap", "README.md");
|
|
10823
10834
|
let text = "";
|
|
10824
10835
|
if (existsSync9(path)) {
|
|
10825
10836
|
text = await readFile20(path, "utf8");
|
|
@@ -10920,6 +10931,7 @@ export {
|
|
|
10920
10931
|
LEGACY_LEDGER_PATH,
|
|
10921
10932
|
METRICS_LEDGER_PATH,
|
|
10922
10933
|
METRIC_COUNTER_NAMES,
|
|
10934
|
+
OPTIONAL_EMBED_PACKAGE,
|
|
10923
10935
|
RETIRED_TOKENS,
|
|
10924
10936
|
appendEventLedgerEvent,
|
|
10925
10937
|
buildAlwaysActiveBodies,
|
|
@@ -10930,6 +10942,7 @@ export {
|
|
|
10930
10942
|
createFabricServer,
|
|
10931
10943
|
createInFlightTracker,
|
|
10932
10944
|
createShutdownHandler,
|
|
10945
|
+
defaultEmbedCacheDir,
|
|
10933
10946
|
detectUnboundProject,
|
|
10934
10947
|
drainCounters,
|
|
10935
10948
|
enrichDescriptions,
|
|
@@ -10946,9 +10959,12 @@ export {
|
|
|
10946
10959
|
inspectRetiredReferences,
|
|
10947
10960
|
lintConflicts,
|
|
10948
10961
|
loadConflictEntries,
|
|
10962
|
+
loadEmbedder,
|
|
10949
10963
|
pairSimilarity,
|
|
10950
10964
|
planContext,
|
|
10965
|
+
readEmbedConfig,
|
|
10951
10966
|
readEventLedger,
|
|
10967
|
+
readFusion,
|
|
10952
10968
|
readLedger,
|
|
10953
10969
|
readMetrics,
|
|
10954
10970
|
readSelectionToken,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-server",
|
|
3
|
-
"version": "2.3.0-rc.
|
|
3
|
+
"version": "2.3.0-rc.3",
|
|
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.3.0-rc.
|
|
40
|
+
"@fenglimg/fabric-shared": "2.3.0-rc.3"
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
43
|
"fastembed": "^2.0.0"
|