@fenglimg/fabric-server 2.3.0-rc.1 → 2.3.0-rc.2
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 +32 -4
- package/dist/index.js +513 -237
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { existsSync as existsSync9 } from "fs";
|
|
3
|
-
import { readFile as
|
|
4
|
-
import { join as
|
|
3
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
4
|
+
import { join as join24, 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";
|
|
@@ -742,7 +742,7 @@ var SUPPORTED_EMBED_MODELS = /* @__PURE__ */ new Set([
|
|
|
742
742
|
function readEmbedConfig(projectRoot) {
|
|
743
743
|
try {
|
|
744
744
|
const config = readFabricConfig(projectRoot);
|
|
745
|
-
const enabled = config.embed_enabled
|
|
745
|
+
const enabled = config.embed_enabled !== false;
|
|
746
746
|
const rawWeight = config.embed_weight;
|
|
747
747
|
const weight = typeof rawWeight === "number" && Number.isInteger(rawWeight) && rawWeight >= 0 && rawWeight <= 49 ? rawWeight : 30;
|
|
748
748
|
const rawModel = config.embed_model;
|
|
@@ -817,6 +817,14 @@ function readOrphanDemoteThresholdDays(projectRoot) {
|
|
|
817
817
|
return {};
|
|
818
818
|
}
|
|
819
819
|
}
|
|
820
|
+
function readFusion(projectRoot) {
|
|
821
|
+
try {
|
|
822
|
+
const raw = readFabricConfig(projectRoot).fusion;
|
|
823
|
+
return raw === "rrf" ? "rrf" : "additive";
|
|
824
|
+
} catch {
|
|
825
|
+
return "additive";
|
|
826
|
+
}
|
|
827
|
+
}
|
|
820
828
|
function readConflictLintThreshold(projectRoot) {
|
|
821
829
|
try {
|
|
822
830
|
const cfgPath = join4(projectRoot, ".fabric", "fabric-config.json");
|
|
@@ -1534,11 +1542,44 @@ function buildBm25Model(docs) {
|
|
|
1534
1542
|
for (const field of BM25_FIELDS) {
|
|
1535
1543
|
avgFieldLength[field] = totalDocs > 0 ? totalFieldLength[field] / totalDocs : 0;
|
|
1536
1544
|
}
|
|
1545
|
+
const serialized = {
|
|
1546
|
+
version: 1,
|
|
1547
|
+
totalDocs,
|
|
1548
|
+
documentFrequency: [...documentFrequency],
|
|
1549
|
+
avgFieldLength,
|
|
1550
|
+
perDoc: [...perDoc].map(([id, stats]) => ({
|
|
1551
|
+
id,
|
|
1552
|
+
fieldTermFreq: emptyFieldRecord(() => []),
|
|
1553
|
+
fieldLength: { ...stats.fieldLength }
|
|
1554
|
+
}))
|
|
1555
|
+
};
|
|
1556
|
+
for (const entry of serialized.perDoc) {
|
|
1557
|
+
const stats = perDoc.get(entry.id);
|
|
1558
|
+
if (stats === void 0) continue;
|
|
1559
|
+
for (const field of BM25_FIELDS) {
|
|
1560
|
+
entry.fieldTermFreq[field] = [...stats.fieldTermFreq[field]];
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return modelFromStats(serialized);
|
|
1564
|
+
}
|
|
1565
|
+
function modelFromStats(serialized) {
|
|
1566
|
+
const totalDocs = serialized.totalDocs;
|
|
1567
|
+
const documentFrequency = new Map(serialized.documentFrequency);
|
|
1568
|
+
const avgFieldLength = serialized.avgFieldLength;
|
|
1569
|
+
const perDoc = /* @__PURE__ */ new Map();
|
|
1570
|
+
for (const entry of serialized.perDoc) {
|
|
1571
|
+
const fieldTermFreq = emptyFieldRecord(() => /* @__PURE__ */ new Map());
|
|
1572
|
+
for (const field of BM25_FIELDS) {
|
|
1573
|
+
fieldTermFreq[field] = new Map(entry.fieldTermFreq[field]);
|
|
1574
|
+
}
|
|
1575
|
+
perDoc.set(entry.id, { fieldTermFreq, fieldLength: entry.fieldLength });
|
|
1576
|
+
}
|
|
1537
1577
|
const idf = (term) => {
|
|
1538
1578
|
const n = documentFrequency.get(term) ?? 0;
|
|
1539
1579
|
return Math.log(1 + (totalDocs - n + 0.5) / (n + 0.5));
|
|
1540
1580
|
};
|
|
1541
1581
|
return {
|
|
1582
|
+
__serialized: serialized,
|
|
1542
1583
|
scoreDoc(id, queryTerms) {
|
|
1543
1584
|
const data = perDoc.get(id);
|
|
1544
1585
|
if (data === void 0 || queryTerms.length === 0) {
|
|
@@ -1574,9 +1615,48 @@ function buildBm25Model(docs) {
|
|
|
1574
1615
|
}
|
|
1575
1616
|
};
|
|
1576
1617
|
}
|
|
1618
|
+
function serializeBm25Model(model) {
|
|
1619
|
+
return model.__serialized;
|
|
1620
|
+
}
|
|
1621
|
+
function rehydrateBm25Model(serialized) {
|
|
1622
|
+
return modelFromStats(serialized);
|
|
1623
|
+
}
|
|
1577
1624
|
function buildQueryTerms(text) {
|
|
1578
1625
|
return tokenize(text);
|
|
1579
1626
|
}
|
|
1627
|
+
function rankDocuments(model, ids, queryTerms) {
|
|
1628
|
+
if (queryTerms.length === 0) {
|
|
1629
|
+
return /* @__PURE__ */ new Map();
|
|
1630
|
+
}
|
|
1631
|
+
const scored = [];
|
|
1632
|
+
ids.forEach((id, order) => {
|
|
1633
|
+
const score = model.scoreDoc(id, queryTerms);
|
|
1634
|
+
if (score > 0) {
|
|
1635
|
+
scored.push({ id, score, order });
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
scored.sort((a, b) => a.score !== b.score ? b.score - a.score : a.order - b.order);
|
|
1639
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
1640
|
+
scored.forEach((entry, index) => {
|
|
1641
|
+
ranks.set(entry.id, index + 1);
|
|
1642
|
+
});
|
|
1643
|
+
return ranks;
|
|
1644
|
+
}
|
|
1645
|
+
function rankByScore(ids, scores) {
|
|
1646
|
+
const scored = [];
|
|
1647
|
+
ids.forEach((id, order) => {
|
|
1648
|
+
const score = scores.get(id) ?? 0;
|
|
1649
|
+
if (score > 0) {
|
|
1650
|
+
scored.push({ id, score, order });
|
|
1651
|
+
}
|
|
1652
|
+
});
|
|
1653
|
+
scored.sort((a, b) => a.score !== b.score ? b.score - a.score : a.order - b.order);
|
|
1654
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
1655
|
+
scored.forEach((entry, index) => {
|
|
1656
|
+
ranks.set(entry.id, index + 1);
|
|
1657
|
+
});
|
|
1658
|
+
return ranks;
|
|
1659
|
+
}
|
|
1580
1660
|
|
|
1581
1661
|
// src/services/conflict-lint.ts
|
|
1582
1662
|
function buildSimilarityModel(docs) {
|
|
@@ -2670,9 +2750,27 @@ var LEDGER_DUAL_WRITE_METRIC_NAMES = {
|
|
|
2670
2750
|
edit_intent_checked: METRIC_COUNTER_NAMES.edit_intent_checked
|
|
2671
2751
|
};
|
|
2672
2752
|
|
|
2753
|
+
// src/services/plan-context.ts
|
|
2754
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
2755
|
+
import { join as join9 } from "path";
|
|
2756
|
+
|
|
2673
2757
|
// src/services/vector-retrieval.ts
|
|
2674
2758
|
var embedderLoad;
|
|
2675
2759
|
var OPTIONAL_EMBED_PACKAGE = "fastembed";
|
|
2760
|
+
var embedderModuleLoader = (name) => import(name);
|
|
2761
|
+
var MISSING_EMBEDDER_HINT = "[fabric] vector semantic recall is enabled but the optional 'fastembed' package is unavailable \u2014 falling back to text-only ranking. Install it where the server resolves modules (e.g. `npm i -g fastembed`) to enable embeddings, or set embed_enabled:false to silence this.\n";
|
|
2762
|
+
var defaultMissingEmbedderHint = () => {
|
|
2763
|
+
process.stderr.write(MISSING_EMBEDDER_HINT);
|
|
2764
|
+
};
|
|
2765
|
+
var missingEmbedderHinted = false;
|
|
2766
|
+
var emitMissingEmbedderHint = defaultMissingEmbedderHint;
|
|
2767
|
+
function hintMissingEmbedderOnce() {
|
|
2768
|
+
if (missingEmbedderHinted) {
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
missingEmbedderHinted = true;
|
|
2772
|
+
emitMissingEmbedderHint();
|
|
2773
|
+
}
|
|
2676
2774
|
function buildEmbedInitOptions(modelName) {
|
|
2677
2775
|
return {
|
|
2678
2776
|
maxLength: 512,
|
|
@@ -2685,8 +2783,9 @@ async function loadEmbedder(modelName) {
|
|
|
2685
2783
|
embedderLoad = (async () => {
|
|
2686
2784
|
try {
|
|
2687
2785
|
const moduleName = OPTIONAL_EMBED_PACKAGE;
|
|
2688
|
-
const mod = await
|
|
2786
|
+
const mod = await embedderModuleLoader(moduleName);
|
|
2689
2787
|
if (mod?.FlagEmbedding?.init === void 0) {
|
|
2788
|
+
hintMissingEmbedderOnce();
|
|
2690
2789
|
return null;
|
|
2691
2790
|
}
|
|
2692
2791
|
const model = await mod.FlagEmbedding.init(buildEmbedInitOptions(modelName));
|
|
@@ -2702,6 +2801,7 @@ async function loadEmbedder(modelName) {
|
|
|
2702
2801
|
}
|
|
2703
2802
|
};
|
|
2704
2803
|
} catch {
|
|
2804
|
+
hintMissingEmbedderOnce();
|
|
2705
2805
|
return null;
|
|
2706
2806
|
}
|
|
2707
2807
|
})();
|
|
@@ -2732,7 +2832,7 @@ function cosineSimilarity(a, b) {
|
|
|
2732
2832
|
if (!Number.isFinite(sim)) {
|
|
2733
2833
|
return 0;
|
|
2734
2834
|
}
|
|
2735
|
-
return Math.max(
|
|
2835
|
+
return Math.max(0, Math.min(1, sim));
|
|
2736
2836
|
}
|
|
2737
2837
|
var docVectorCache = /* @__PURE__ */ new Map();
|
|
2738
2838
|
var DOC_VECTOR_CACHE_MAX = 1e4;
|
|
@@ -2837,56 +2937,28 @@ async function planContext(projectRoot, input) {
|
|
|
2837
2937
|
...input.known_tech ?? [],
|
|
2838
2938
|
...Object.values(input.detected_entities ?? {}).flat()
|
|
2839
2939
|
].join(" ");
|
|
2840
|
-
const scoringContext = {
|
|
2841
|
-
nowMs: Date.now(),
|
|
2842
|
-
targetPaths: input.target_paths ?? dedupePaths(input.paths),
|
|
2843
|
-
queryTerms: buildQueryTerms(queryText)
|
|
2844
|
-
};
|
|
2845
2940
|
const storeRawItems = await buildCrossStoreRawItems(projectRoot).catch(() => []);
|
|
2846
2941
|
const { rawItems: allRawItems, suppressedStableIds } = partitionEmptyShells(storeRawItems);
|
|
2847
2942
|
const effectiveLayerFilter = input.layer_filter ?? readDefaultLayerFilter(projectRoot);
|
|
2848
2943
|
const rawItems = effectiveLayerFilter === "both" ? allRawItems : allRawItems.filter((item) => item.description.knowledge_layer === effectiveLayerFilter);
|
|
2849
|
-
const
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
}
|
|
2853
|
-
scoringContext.docTexts = docTexts;
|
|
2854
|
-
if (scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
|
|
2855
|
-
scoringContext.bm25 = getOrBuildBm25Model(revision, rawItems, docTexts);
|
|
2856
|
-
}
|
|
2857
|
-
scoringContext.scopeRank = buildScopeRankMap(rawItems, projectRoot);
|
|
2858
|
-
const embedConfig = readEmbedConfig(projectRoot);
|
|
2859
|
-
if (embedConfig.enabled && queryText.trim().length > 0 && rawItems.length > 0) {
|
|
2860
|
-
const embedder = await loadEmbedder(embedConfig.model);
|
|
2861
|
-
const vectorScores = await buildVectorScores(
|
|
2862
|
-
embedder,
|
|
2863
|
-
queryText,
|
|
2864
|
-
rawItems.map((item) => ({
|
|
2865
|
-
stable_id: item.stable_id,
|
|
2866
|
-
text: docTexts.get(item.stable_id) ?? documentTextForItem(item.description)
|
|
2867
|
-
}))
|
|
2868
|
-
);
|
|
2869
|
-
if (vectorScores !== null) {
|
|
2870
|
-
scoringContext.vectorScores = vectorScores;
|
|
2871
|
-
scoringContext.vectorWeight = embedConfig.weight;
|
|
2872
|
-
}
|
|
2873
|
-
}
|
|
2874
|
-
const scoredSorted = sortDescriptionItems(rawItems, scoringContext);
|
|
2875
|
-
const seenStableIds = /* @__PURE__ */ new Set();
|
|
2876
|
-
const rankedScored = scoredSorted.filter(({ item }) => {
|
|
2877
|
-
if (seenStableIds.has(item.stable_id)) return false;
|
|
2878
|
-
seenStableIds.add(item.stable_id);
|
|
2879
|
-
return true;
|
|
2944
|
+
const scoringContext = await buildScoringContext(projectRoot, revision, rawItems, {
|
|
2945
|
+
queryText,
|
|
2946
|
+
targetPaths: input.target_paths ?? dedupePaths(input.paths)
|
|
2880
2947
|
});
|
|
2948
|
+
const rankedScored = rankDescriptionItems(rawItems, scoringContext, "triage");
|
|
2881
2949
|
const rankedCandidates = rankedScored.map((entry) => entry.item);
|
|
2882
|
-
const
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
const maxScore = rankedScored.length > 0 ? rankedScored[0].score : 0;
|
|
2887
|
-
const relevanceFloor = maxScore * relevanceRatio;
|
|
2888
|
-
const survivingScored = hasQuery && maxScore > 0 && relevanceRatio > 0 ? cappedScored.filter((entry) => entry.score >= relevanceFloor) : cappedScored;
|
|
2950
|
+
const survivingScored = rankDescriptionItems(rawItems, scoringContext, "recall", {
|
|
2951
|
+
topK: readPlanContextTopK(projectRoot),
|
|
2952
|
+
relevanceRatio: readRecallRelevanceRatio(projectRoot)
|
|
2953
|
+
});
|
|
2889
2954
|
const topKCandidates = survivingScored.map((entry) => entry.item);
|
|
2955
|
+
const candidateScores = /* @__PURE__ */ new Map();
|
|
2956
|
+
for (const entry of survivingScored) {
|
|
2957
|
+
candidateScores.set(entry.item.stable_id, {
|
|
2958
|
+
score: entry.score,
|
|
2959
|
+
score_breakdown: scoreBreakdownForItem(entry.item, scoringContext)
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2890
2962
|
const topKIds = new Set(topKCandidates.map((item) => item.stable_id));
|
|
2891
2963
|
const retrievalDropped = rankedCandidates.filter((item) => !topKIds.has(item.stable_id)).map((item) => ({ id: item.stable_id, reason: "retrieval_budget" }));
|
|
2892
2964
|
const omittedCandidateCount = retrievalDropped.length;
|
|
@@ -2992,6 +3064,8 @@ async function planContext(projectRoot, input) {
|
|
|
2992
3064
|
// ONLY when at least one neighbour was actually appended. Empty (graph-empty
|
|
2993
3065
|
// no-op) → field omitted, steady-state wire shape unchanged.
|
|
2994
3066
|
...Object.keys(relatedAppended).length > 0 ? { related_appended: relatedAppended } : {},
|
|
3067
|
+
// P1 recall-observability: runtime-only score channel (Map → {} on the wire).
|
|
3068
|
+
...candidateScores.size > 0 ? { candidate_scores: candidateScores } : {},
|
|
2995
3069
|
...payloadTrimDropped > 0 ? { payload_trimmed: true } : {},
|
|
2996
3070
|
...payloadOverBudget ? { payload_over_budget: true } : {}
|
|
2997
3071
|
};
|
|
@@ -3080,10 +3154,38 @@ function partitionEmptyShells(items) {
|
|
|
3080
3154
|
}
|
|
3081
3155
|
var bm25ModelCache = null;
|
|
3082
3156
|
var bm25BuildCount = 0;
|
|
3083
|
-
|
|
3157
|
+
var BM25_CACHE_DIR = ".fabric/cache/bm25";
|
|
3158
|
+
function bm25CachePath(projectRoot, revision) {
|
|
3159
|
+
const safe = revision.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
3160
|
+
return join9(projectRoot, BM25_CACHE_DIR, `${safe}.json`);
|
|
3161
|
+
}
|
|
3162
|
+
async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
3163
|
+
try {
|
|
3164
|
+
const raw = await readFile5(bm25CachePath(projectRoot, revision), "utf8");
|
|
3165
|
+
const parsed = JSON.parse(raw);
|
|
3166
|
+
if (parsed.version !== 1) return null;
|
|
3167
|
+
return rehydrateBm25Model(parsed);
|
|
3168
|
+
} catch {
|
|
3169
|
+
return null;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
async function saveBm25ModelToDisk(projectRoot, revision, model) {
|
|
3173
|
+
try {
|
|
3174
|
+
const path = bm25CachePath(projectRoot, revision);
|
|
3175
|
+
await mkdir4(join9(projectRoot, BM25_CACHE_DIR), { recursive: true });
|
|
3176
|
+
await writeFile2(path, JSON.stringify(serializeBm25Model(model)), "utf8");
|
|
3177
|
+
} catch {
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
async function getOrBuildBm25Model(projectRoot, revision, rawItems, docTexts) {
|
|
3084
3181
|
if (bm25ModelCache !== null && bm25ModelCache.revision === revision) {
|
|
3085
3182
|
return bm25ModelCache.model;
|
|
3086
3183
|
}
|
|
3184
|
+
const fromDisk = await loadBm25ModelFromDisk(projectRoot, revision);
|
|
3185
|
+
if (fromDisk !== null) {
|
|
3186
|
+
bm25ModelCache = { revision, model: fromDisk };
|
|
3187
|
+
return fromDisk;
|
|
3188
|
+
}
|
|
3087
3189
|
bm25BuildCount += 1;
|
|
3088
3190
|
const model = buildBm25Model(
|
|
3089
3191
|
rawItems.map((item) => ({
|
|
@@ -3092,6 +3194,7 @@ function getOrBuildBm25Model(revision, rawItems, docTexts) {
|
|
|
3092
3194
|
}))
|
|
3093
3195
|
);
|
|
3094
3196
|
bm25ModelCache = { revision, model };
|
|
3197
|
+
await saveBm25ModelToDisk(projectRoot, revision, model);
|
|
3095
3198
|
return model;
|
|
3096
3199
|
}
|
|
3097
3200
|
function compareStableIds(a, b) {
|
|
@@ -3137,6 +3240,49 @@ function compareScopeThenId(left, right, scopeRank) {
|
|
|
3137
3240
|
}
|
|
3138
3241
|
return compareStableIds(left.stable_id, right.stable_id);
|
|
3139
3242
|
}
|
|
3243
|
+
async function buildScoringContext(projectRoot, revision, rawItems, opts) {
|
|
3244
|
+
const scoringContext = {
|
|
3245
|
+
nowMs: Date.now(),
|
|
3246
|
+
targetPaths: opts.targetPaths,
|
|
3247
|
+
queryTerms: buildQueryTerms(opts.queryText)
|
|
3248
|
+
};
|
|
3249
|
+
const docTexts = /* @__PURE__ */ new Map();
|
|
3250
|
+
for (const item of rawItems) {
|
|
3251
|
+
docTexts.set(item.stable_id, documentTextForItem(item.description));
|
|
3252
|
+
}
|
|
3253
|
+
scoringContext.docTexts = docTexts;
|
|
3254
|
+
if (scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
|
|
3255
|
+
scoringContext.bm25 = await getOrBuildBm25Model(projectRoot, revision, rawItems, docTexts);
|
|
3256
|
+
}
|
|
3257
|
+
scoringContext.scopeRank = buildScopeRankMap(rawItems, projectRoot);
|
|
3258
|
+
const embedConfig = readEmbedConfig(projectRoot);
|
|
3259
|
+
if (embedConfig.enabled && opts.queryText.trim().length > 0 && rawItems.length > 0) {
|
|
3260
|
+
const embedder = await loadEmbedder(embedConfig.model);
|
|
3261
|
+
const vectorScores = await buildVectorScores(
|
|
3262
|
+
embedder,
|
|
3263
|
+
opts.queryText,
|
|
3264
|
+
rawItems.map((item) => ({
|
|
3265
|
+
stable_id: item.stable_id,
|
|
3266
|
+
text: docTexts.get(item.stable_id) ?? documentTextForItem(item.description)
|
|
3267
|
+
}))
|
|
3268
|
+
);
|
|
3269
|
+
if (vectorScores !== null) {
|
|
3270
|
+
scoringContext.vectorScores = vectorScores;
|
|
3271
|
+
scoringContext.vectorWeight = embedConfig.weight;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
scoringContext.fusion = readFusion(projectRoot);
|
|
3275
|
+
if (scoringContext.fusion === "rrf" && scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
|
|
3276
|
+
const rankIds = rawItems.map((item) => item.stable_id).sort((a, b) => compareStableIds(a, b));
|
|
3277
|
+
if (scoringContext.bm25 !== void 0) {
|
|
3278
|
+
scoringContext.bm25Ranks = rankDocuments(scoringContext.bm25, rankIds, scoringContext.queryTerms);
|
|
3279
|
+
}
|
|
3280
|
+
if (scoringContext.vectorScores !== void 0) {
|
|
3281
|
+
scoringContext.vectorRanks = rankByScore(rankIds, scoringContext.vectorScores);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
return scoringContext;
|
|
3285
|
+
}
|
|
3140
3286
|
function sortDescriptionItems(rawItems, scoringContext) {
|
|
3141
3287
|
if (scoringContext === void 0) {
|
|
3142
3288
|
return [...rawItems].sort((left, right) => compareStableIds(left.stable_id, right.stable_id)).map((item) => ({ item, score: 0 }));
|
|
@@ -3151,6 +3297,25 @@ function sortDescriptionItems(rawItems, scoringContext) {
|
|
|
3151
3297
|
);
|
|
3152
3298
|
return scored;
|
|
3153
3299
|
}
|
|
3300
|
+
function rankDescriptionItems(items, scoringContext, mode, options = {}) {
|
|
3301
|
+
const sorted = sortDescriptionItems(items, scoringContext);
|
|
3302
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3303
|
+
const rankedScored = sorted.filter(({ item }) => {
|
|
3304
|
+
if (seen.has(item.stable_id)) return false;
|
|
3305
|
+
seen.add(item.stable_id);
|
|
3306
|
+
return true;
|
|
3307
|
+
});
|
|
3308
|
+
if (mode === "triage") {
|
|
3309
|
+
return rankedScored;
|
|
3310
|
+
}
|
|
3311
|
+
const topK = options.topK ?? rankedScored.length;
|
|
3312
|
+
const cappedScored = rankedScored.slice(0, topK);
|
|
3313
|
+
const relevanceRatio = options.relevanceRatio ?? 0;
|
|
3314
|
+
const hasQuery = scoringContext.queryTerms.length > 0;
|
|
3315
|
+
const maxScore = rankedScored.length > 0 ? rankedScored[0].score : 0;
|
|
3316
|
+
const relevanceFloor = maxScore * relevanceRatio;
|
|
3317
|
+
return hasQuery && maxScore > 0 && relevanceRatio > 0 ? cappedScored.filter((entry) => entry.score >= relevanceFloor) : cappedScored;
|
|
3318
|
+
}
|
|
3154
3319
|
function documentTextForItem(description) {
|
|
3155
3320
|
return [
|
|
3156
3321
|
description.summary,
|
|
@@ -3199,6 +3364,9 @@ var BM25_WEIGHT = 50;
|
|
|
3199
3364
|
var SALIENCE_PROVEN = 15;
|
|
3200
3365
|
var SALIENCE_VERIFIED = 8;
|
|
3201
3366
|
var SALIENCE_DRAFT = 0;
|
|
3367
|
+
var RRF_K = 10;
|
|
3368
|
+
var RRF_NORMALIZATION = 2e3;
|
|
3369
|
+
var RRF_STRUCTURAL_SCALE = 0.2;
|
|
3202
3370
|
function salienceScore(item) {
|
|
3203
3371
|
switch (item.description?.maturity) {
|
|
3204
3372
|
case "proven":
|
|
@@ -3209,35 +3377,87 @@ function salienceScore(item) {
|
|
|
3209
3377
|
return SALIENCE_DRAFT;
|
|
3210
3378
|
}
|
|
3211
3379
|
}
|
|
3212
|
-
function
|
|
3213
|
-
|
|
3214
|
-
if (context.
|
|
3215
|
-
|
|
3380
|
+
function contentScore(item, context) {
|
|
3381
|
+
const hasQuery = context.queryTerms.length > 0;
|
|
3382
|
+
if (context.fusion === "rrf" && hasQuery) {
|
|
3383
|
+
let rrf = 0;
|
|
3384
|
+
const bm25Rank = context.bm25Ranks?.get(item.stable_id);
|
|
3385
|
+
if (bm25Rank !== void 0) rrf += 1 / (RRF_K + bm25Rank);
|
|
3386
|
+
const vectorRank = context.vectorRanks?.get(item.stable_id);
|
|
3387
|
+
if (vectorRank !== void 0) rrf += 1 / (RRF_K + vectorRank);
|
|
3388
|
+
return RRF_NORMALIZATION * rrf;
|
|
3389
|
+
}
|
|
3390
|
+
let content = 0;
|
|
3391
|
+
if (context.bm25 !== void 0 && hasQuery) {
|
|
3392
|
+
content += BM25_WEIGHT * context.bm25.scoreDoc(item.stable_id, context.queryTerms);
|
|
3216
3393
|
}
|
|
3217
3394
|
if (context.vectorScores !== void 0) {
|
|
3218
|
-
|
|
3395
|
+
content += (context.vectorWeight ?? 0) * (context.vectorScores.get(item.stable_id) ?? 0);
|
|
3219
3396
|
}
|
|
3220
|
-
|
|
3397
|
+
return content;
|
|
3398
|
+
}
|
|
3399
|
+
function recencyBoost(item, context) {
|
|
3221
3400
|
const createdAtRaw = item.description?.created_at;
|
|
3222
3401
|
if (typeof createdAtRaw === "string" && createdAtRaw.length > 0) {
|
|
3223
3402
|
const createdMs = Date.parse(createdAtRaw);
|
|
3224
3403
|
if (Number.isFinite(createdMs) && context.nowMs - createdMs < RECENCY_WINDOW_MS) {
|
|
3225
|
-
|
|
3404
|
+
return RECENCY_BOOST;
|
|
3226
3405
|
}
|
|
3227
3406
|
}
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3407
|
+
return 0;
|
|
3408
|
+
}
|
|
3409
|
+
function localityBoost(item, context) {
|
|
3410
|
+
if (context.targetPaths.length === 0) return 0;
|
|
3411
|
+
const relevancePaths = item.description?.relevance_paths ?? [];
|
|
3412
|
+
let best = 0;
|
|
3413
|
+
outer: for (const rp of relevancePaths) {
|
|
3414
|
+
for (const tp of context.targetPaths) {
|
|
3415
|
+
const tier = localityTier(rp, tp);
|
|
3416
|
+
if (tier > best) best = tier;
|
|
3417
|
+
if (best === LOCALITY_SAME_FILE) break outer;
|
|
3237
3418
|
}
|
|
3238
|
-
score += best;
|
|
3239
3419
|
}
|
|
3240
|
-
return
|
|
3420
|
+
return best;
|
|
3421
|
+
}
|
|
3422
|
+
function structuralScaleFor(context) {
|
|
3423
|
+
return context.fusion === "rrf" && context.queryTerms.length > 0 ? RRF_STRUCTURAL_SCALE : 1;
|
|
3424
|
+
}
|
|
3425
|
+
function scoreDescriptionItem(item, context) {
|
|
3426
|
+
const content = contentScore(item, context);
|
|
3427
|
+
const structural = salienceScore(item) + recencyBoost(item, context) + localityBoost(item, context);
|
|
3428
|
+
return content + structuralScaleFor(context) * structural;
|
|
3429
|
+
}
|
|
3430
|
+
function scoreBreakdownForItem(item, context) {
|
|
3431
|
+
const hasQuery = context.queryTerms.length > 0;
|
|
3432
|
+
const rrfMode = context.fusion === "rrf" && hasQuery;
|
|
3433
|
+
let bm25 = 0;
|
|
3434
|
+
let vector = 0;
|
|
3435
|
+
let bm25Rank;
|
|
3436
|
+
let vectorRank;
|
|
3437
|
+
if (rrfMode) {
|
|
3438
|
+
bm25Rank = context.bm25Ranks?.get(item.stable_id);
|
|
3439
|
+
if (bm25Rank !== void 0) bm25 = RRF_NORMALIZATION * (1 / (RRF_K + bm25Rank));
|
|
3440
|
+
vectorRank = context.vectorRanks?.get(item.stable_id);
|
|
3441
|
+
if (vectorRank !== void 0) vector = RRF_NORMALIZATION * (1 / (RRF_K + vectorRank));
|
|
3442
|
+
} else {
|
|
3443
|
+
bm25 = context.bm25 !== void 0 && hasQuery ? BM25_WEIGHT * context.bm25.scoreDoc(item.stable_id, context.queryTerms) : 0;
|
|
3444
|
+
vector = context.vectorScores !== void 0 ? (context.vectorWeight ?? 0) * (context.vectorScores.get(item.stable_id) ?? 0) : 0;
|
|
3445
|
+
}
|
|
3446
|
+
const scale = structuralScaleFor(context);
|
|
3447
|
+
const salience = salienceScore(item) * scale;
|
|
3448
|
+
const recency = recencyBoost(item, context) * scale;
|
|
3449
|
+
const locality = localityBoost(item, context) * scale;
|
|
3450
|
+
const final = bm25 + vector + salience + recency + locality;
|
|
3451
|
+
return {
|
|
3452
|
+
final,
|
|
3453
|
+
...bm25 !== 0 ? { bm25 } : {},
|
|
3454
|
+
...bm25Rank !== void 0 ? { bm25_rank: bm25Rank } : {},
|
|
3455
|
+
...vector !== 0 ? { vector } : {},
|
|
3456
|
+
...vectorRank !== void 0 ? { vector_rank: vectorRank } : {},
|
|
3457
|
+
salience,
|
|
3458
|
+
recency,
|
|
3459
|
+
locality
|
|
3460
|
+
};
|
|
3241
3461
|
}
|
|
3242
3462
|
function localityTier(relevancePath, targetPath) {
|
|
3243
3463
|
if (relevancePath === targetPath) return LOCALITY_SAME_FILE;
|
|
@@ -3274,7 +3494,13 @@ function relatedLookupKeys(stableId) {
|
|
|
3274
3494
|
var RECALL_DIRECTIVE = "These entries are auto-accounted as citations for edits whose paths overlap this recall \u2014 no first-line cite needed. Speak up only to dismiss one you judge inapplicable: `dismissed: <id> (<reason>)`.";
|
|
3275
3495
|
async function recall(projectRoot, input) {
|
|
3276
3496
|
const planResult = await planContext(projectRoot, input);
|
|
3277
|
-
const {
|
|
3497
|
+
const {
|
|
3498
|
+
selection_token: _token,
|
|
3499
|
+
payload_trimmed: _pt,
|
|
3500
|
+
payload_over_budget: _pob,
|
|
3501
|
+
candidate_scores: candidateScores,
|
|
3502
|
+
...planRest
|
|
3503
|
+
} = planResult;
|
|
3278
3504
|
let bodyIndex;
|
|
3279
3505
|
try {
|
|
3280
3506
|
bodyIndex = await buildCrossStoreBodyIndex(projectRoot);
|
|
@@ -3323,13 +3549,15 @@ async function recall(projectRoot, input) {
|
|
|
3323
3549
|
const pathByStableId = new Map(paths.map((p) => [p.stable_id, p]));
|
|
3324
3550
|
const entries = planRest.candidates.map((c, index) => {
|
|
3325
3551
|
const readPath = pathByStableId.get(c.stable_id);
|
|
3552
|
+
const scored = candidateScores?.get(c.stable_id);
|
|
3326
3553
|
return {
|
|
3327
3554
|
stable_id: c.stable_id,
|
|
3328
3555
|
rank: index + 1,
|
|
3329
3556
|
description: c.description,
|
|
3330
3557
|
...readPath ? { read_path: readPath.path } : {},
|
|
3331
3558
|
...readPath?.store ? { store: readPath.store } : {},
|
|
3332
|
-
...isAlwaysActive(c) ? { body_in_context: true } : {}
|
|
3559
|
+
...isAlwaysActive(c) ? { body_in_context: true } : {},
|
|
3560
|
+
...scored ? { score: scored.score, score_breakdown: scored.score_breakdown } : {}
|
|
3333
3561
|
};
|
|
3334
3562
|
});
|
|
3335
3563
|
const { entries: _reqProfiles, candidates: _candidates, ...planRestNoLists } = planRest;
|
|
@@ -3679,9 +3907,9 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
|
|
|
3679
3907
|
// src/services/review.ts
|
|
3680
3908
|
import { execFileSync } from "child_process";
|
|
3681
3909
|
import { existsSync as existsSync5 } from "fs";
|
|
3682
|
-
import { readFile as
|
|
3910
|
+
import { readFile as readFile7, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
3683
3911
|
import { homedir } from "os";
|
|
3684
|
-
import { basename, isAbsolute, join as
|
|
3912
|
+
import { basename, isAbsolute, join as join11, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
3685
3913
|
|
|
3686
3914
|
// src/services/promotion-gate.ts
|
|
3687
3915
|
function toLocalId(id) {
|
|
@@ -3721,8 +3949,8 @@ import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@
|
|
|
3721
3949
|
|
|
3722
3950
|
// src/services/pending-dedupe.ts
|
|
3723
3951
|
import { existsSync as existsSync4 } from "fs";
|
|
3724
|
-
import { readdir, readFile as
|
|
3725
|
-
import { join as
|
|
3952
|
+
import { readdir, readFile as readFile6, unlink } from "fs/promises";
|
|
3953
|
+
import { join as join10 } from "path";
|
|
3726
3954
|
var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
|
|
3727
3955
|
var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
|
|
3728
3956
|
var SOURCE_SESSIONS_LINE = /^source_sessions:\s*\[(.*)\]\s*$/mu;
|
|
@@ -3795,7 +4023,7 @@ async function mergePendingTwins(projectRoot) {
|
|
|
3795
4023
|
continue;
|
|
3796
4024
|
}
|
|
3797
4025
|
for (const type of PENDING_TYPES) {
|
|
3798
|
-
const dir =
|
|
4026
|
+
const dir = join10(pendingBase2, type);
|
|
3799
4027
|
if (!existsSync4(dir)) continue;
|
|
3800
4028
|
let names;
|
|
3801
4029
|
try {
|
|
@@ -3816,10 +4044,10 @@ async function mergePendingTwins(projectRoot) {
|
|
|
3816
4044
|
if (groupNames.length < 2) continue;
|
|
3817
4045
|
const parsed = [];
|
|
3818
4046
|
for (const name of groupNames) {
|
|
3819
|
-
const abs =
|
|
4047
|
+
const abs = join10(dir, name);
|
|
3820
4048
|
let content;
|
|
3821
4049
|
try {
|
|
3822
|
-
content = await
|
|
4050
|
+
content = await readFile6(abs, "utf8");
|
|
3823
4051
|
} catch {
|
|
3824
4052
|
continue;
|
|
3825
4053
|
}
|
|
@@ -3919,7 +4147,7 @@ async function reviewPending(projectRoot, input) {
|
|
|
3919
4147
|
case "search":
|
|
3920
4148
|
return {
|
|
3921
4149
|
action: "search",
|
|
3922
|
-
items: await
|
|
4150
|
+
items: await triageSearch(projectRoot, input.query, input.filters)
|
|
3923
4151
|
};
|
|
3924
4152
|
default: {
|
|
3925
4153
|
const exhaustive = input;
|
|
@@ -3999,7 +4227,7 @@ async function listPending(projectRoot, filters) {
|
|
|
3999
4227
|
}
|
|
4000
4228
|
for (const source of sources) {
|
|
4001
4229
|
for (const type of typesToScan) {
|
|
4002
|
-
const dir =
|
|
4230
|
+
const dir = join11(source.root, type);
|
|
4003
4231
|
if (!existsSync5(dir)) {
|
|
4004
4232
|
continue;
|
|
4005
4233
|
}
|
|
@@ -4011,10 +4239,10 @@ async function listPending(projectRoot, filters) {
|
|
|
4011
4239
|
}
|
|
4012
4240
|
for (const name of entries) {
|
|
4013
4241
|
if (!name.endsWith(".md")) continue;
|
|
4014
|
-
const absolutePath =
|
|
4242
|
+
const absolutePath = join11(dir, name);
|
|
4015
4243
|
let content;
|
|
4016
4244
|
try {
|
|
4017
|
-
content = await
|
|
4245
|
+
content = await readFile7(absolutePath, "utf8");
|
|
4018
4246
|
} catch {
|
|
4019
4247
|
continue;
|
|
4020
4248
|
}
|
|
@@ -4124,7 +4352,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4124
4352
|
let targetAbs;
|
|
4125
4353
|
let writtenTarget = false;
|
|
4126
4354
|
try {
|
|
4127
|
-
const content = await
|
|
4355
|
+
const content = await readFile7(sourceAbs, "utf8");
|
|
4128
4356
|
const fm = parseFrontmatter(content);
|
|
4129
4357
|
const pluralType = fm.type;
|
|
4130
4358
|
if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
|
|
@@ -4138,7 +4366,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4138
4366
|
);
|
|
4139
4367
|
allocatedId = stableId;
|
|
4140
4368
|
const newFilename = `${stableId}--${slug}.md`;
|
|
4141
|
-
targetAbs =
|
|
4369
|
+
targetAbs = join11(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
|
|
4142
4370
|
await ensureParentDirectory(targetAbs);
|
|
4143
4371
|
const rewritten = rewriteFrontmatterMerge(
|
|
4144
4372
|
rewriteFrontmatterForPromote(content, stableId),
|
|
@@ -4196,7 +4424,7 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
|
|
|
4196
4424
|
try {
|
|
4197
4425
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4198
4426
|
if (existsSync5(sandboxed.abs)) {
|
|
4199
|
-
const content = await
|
|
4427
|
+
const content = await readFile7(sandboxed.abs, "utf8");
|
|
4200
4428
|
const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
|
|
4201
4429
|
const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
|
|
4202
4430
|
if (rejectedAbs !== null) {
|
|
@@ -4223,7 +4451,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
|
|
|
4223
4451
|
if (target === null) {
|
|
4224
4452
|
throw new Error(`modify target not found: ${pendingPath}`);
|
|
4225
4453
|
}
|
|
4226
|
-
const content = await
|
|
4454
|
+
const content = await readFile7(target.absPath, "utf8");
|
|
4227
4455
|
const fm = parseFrontmatter(content);
|
|
4228
4456
|
const currentLayer = fm.layer ?? "team";
|
|
4229
4457
|
if (fm.maturity === "verified" && changes.maturity === "proven" && fm.id !== void 0) {
|
|
@@ -4325,7 +4553,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
4325
4553
|
pluralType,
|
|
4326
4554
|
resolveWriteTargetStoreDir(toLayer, projectRoot)
|
|
4327
4555
|
);
|
|
4328
|
-
const toAbs =
|
|
4556
|
+
const toAbs = join11(
|
|
4329
4557
|
resolveStoreCanonicalBase(toLayer, projectRoot),
|
|
4330
4558
|
pluralType,
|
|
4331
4559
|
`${newStableId}--${slug}.md`
|
|
@@ -4425,7 +4653,7 @@ function getSearchDirectoryCache(cacheKey) {
|
|
|
4425
4653
|
return created;
|
|
4426
4654
|
}
|
|
4427
4655
|
async function listIndexedSearchEntries(source, type) {
|
|
4428
|
-
const dir =
|
|
4656
|
+
const dir = join11(source.root, type);
|
|
4429
4657
|
let entries;
|
|
4430
4658
|
try {
|
|
4431
4659
|
entries = await readdir2(dir);
|
|
@@ -4438,7 +4666,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4438
4666
|
const indexed = [];
|
|
4439
4667
|
for (const name of entries) {
|
|
4440
4668
|
if (!name.endsWith(".md")) continue;
|
|
4441
|
-
const absolutePath =
|
|
4669
|
+
const absolutePath = join11(dir, name);
|
|
4442
4670
|
let fingerprint;
|
|
4443
4671
|
try {
|
|
4444
4672
|
const st = await stat2(absolutePath);
|
|
@@ -4455,7 +4683,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4455
4683
|
}
|
|
4456
4684
|
let content;
|
|
4457
4685
|
try {
|
|
4458
|
-
content = await
|
|
4686
|
+
content = await readFile7(absolutePath, "utf8");
|
|
4459
4687
|
searchEntryIndexContentReads += 1;
|
|
4460
4688
|
} catch {
|
|
4461
4689
|
directoryCache.files.delete(absolutePath);
|
|
@@ -4482,9 +4710,41 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4482
4710
|
}
|
|
4483
4711
|
return indexed;
|
|
4484
4712
|
}
|
|
4485
|
-
|
|
4713
|
+
function matchesTriageQuery(indexed, lowerQuery, includeBody) {
|
|
4714
|
+
const haystacks = [
|
|
4715
|
+
indexed.fm.title ?? "",
|
|
4716
|
+
indexed.fm.summary ?? "",
|
|
4717
|
+
...indexed.fm.tags ?? [],
|
|
4718
|
+
indexed.name,
|
|
4719
|
+
includeBody ? indexed.body : ""
|
|
4720
|
+
].map((s) => s.toLowerCase());
|
|
4721
|
+
return haystacks.some((h) => h.includes(lowerQuery));
|
|
4722
|
+
}
|
|
4723
|
+
function pendingEntryToRankerItem(indexed) {
|
|
4724
|
+
const { fm, name } = indexed;
|
|
4725
|
+
const slug = name.replace(/\.md$/u, "");
|
|
4726
|
+
const summary = fm.summary ?? fm.title ?? slug;
|
|
4727
|
+
const description = {
|
|
4728
|
+
summary,
|
|
4729
|
+
intent_clues: [],
|
|
4730
|
+
tech_stack: [],
|
|
4731
|
+
impact: [],
|
|
4732
|
+
must_read_if: fm.title ?? summary,
|
|
4733
|
+
...fm.id !== void 0 ? { id: fm.id } : {},
|
|
4734
|
+
...fm.type !== void 0 ? { knowledge_type: fm.type } : {},
|
|
4735
|
+
maturity: fm.maturity ?? "draft",
|
|
4736
|
+
knowledge_layer: indexed.layer,
|
|
4737
|
+
...fm.semantic_scope !== void 0 ? { semantic_scope: fm.semantic_scope } : {},
|
|
4738
|
+
...fm.created_at !== void 0 ? { created_at: fm.created_at } : {},
|
|
4739
|
+
tags: fm.tags ?? [],
|
|
4740
|
+
relevance_scope: fm.relevance_scope ?? "broad",
|
|
4741
|
+
relevance_paths: fm.relevance_paths ?? []
|
|
4742
|
+
};
|
|
4743
|
+
return { stable_id: indexed.absolutePath, description };
|
|
4744
|
+
}
|
|
4745
|
+
async function triageSearch(projectRoot, query, filters) {
|
|
4486
4746
|
const lowerQuery = query.toLowerCase();
|
|
4487
|
-
const
|
|
4747
|
+
const includeBody = filters?.include_body === true;
|
|
4488
4748
|
const sources = [];
|
|
4489
4749
|
for (const layer of ["team", "personal"]) {
|
|
4490
4750
|
const isPersonal = layer === "personal";
|
|
@@ -4507,10 +4767,12 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
4507
4767
|
}
|
|
4508
4768
|
}
|
|
4509
4769
|
const typesToScan = filters?.type !== void 0 ? [filters.type] : PLURAL_TYPES;
|
|
4770
|
+
const matchedByKey = /* @__PURE__ */ new Map();
|
|
4771
|
+
const rankerItems = [];
|
|
4510
4772
|
for (const source of sources) {
|
|
4511
4773
|
for (const type of typesToScan) {
|
|
4512
4774
|
for (const indexed of await listIndexedSearchEntries(source, type)) {
|
|
4513
|
-
const {
|
|
4775
|
+
const { fm, layer, maturity } = indexed;
|
|
4514
4776
|
if (filters?.layer !== void 0 && filters.layer !== "both" && filters.layer !== layer) {
|
|
4515
4777
|
continue;
|
|
4516
4778
|
}
|
|
@@ -4531,42 +4793,56 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
4531
4793
|
if (!isVisibleByLifecycle(fm, filters)) {
|
|
4532
4794
|
continue;
|
|
4533
4795
|
}
|
|
4534
|
-
|
|
4535
|
-
const
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
...fm.tags ?? [],
|
|
4539
|
-
name,
|
|
4540
|
-
bodyForSearch
|
|
4541
|
-
].map((s) => s.toLowerCase());
|
|
4542
|
-
const matches = haystacks.some((h) => h.includes(lowerQuery));
|
|
4543
|
-
if (!matches) continue;
|
|
4544
|
-
const reportedPath = source.isStore ? absolutePath : source.isPersonal ? `~/${relative(resolvePersonalRoot(), absolutePath)}` : relative(projectRoot, absolutePath);
|
|
4545
|
-
items.push({
|
|
4546
|
-
area: source.isPending ? "pending" : "canonical",
|
|
4547
|
-
path: reportedPath,
|
|
4548
|
-
...source.isPersonal ? { path_absolute: absolutePath } : {},
|
|
4549
|
-
type,
|
|
4550
|
-
layer,
|
|
4551
|
-
maturity,
|
|
4552
|
-
// Only pending entries carry an origin tag (canonical hits live
|
|
4553
|
-
// outside the dual-pending-root convention).
|
|
4554
|
-
...source.isPending ? { origin: source.isPersonal ? "personal" : "team" } : {},
|
|
4555
|
-
...fm.tags !== void 0 && fm.tags.length > 0 ? { tags: fm.tags } : {},
|
|
4556
|
-
...fm.title !== void 0 ? { title: fm.title } : {},
|
|
4557
|
-
...fm.summary !== void 0 ? { summary: fm.summary } : {},
|
|
4558
|
-
...fm.status !== void 0 ? { status: fm.status } : {},
|
|
4559
|
-
...fm.deferred_until !== void 0 ? { deferred_until: fm.deferred_until } : {},
|
|
4560
|
-
// v2.0.0-rc.27 TASK-006 (audit §2.23): body emission when opted in.
|
|
4561
|
-
...filters?.include_body === true ? { body: bodyForSearch } : {},
|
|
4562
|
-
// Canonical hits always have an id; pending hits typically don't
|
|
4563
|
-
// yet — surface the frontmatter id when present so consumers can
|
|
4564
|
-
// dedupe across runs.
|
|
4565
|
-
...fm.id !== void 0 ? { stable_id: fm.id } : {}
|
|
4566
|
-
});
|
|
4796
|
+
if (!matchesTriageQuery(indexed, lowerQuery, includeBody)) continue;
|
|
4797
|
+
const item = pendingEntryToRankerItem(indexed);
|
|
4798
|
+
matchedByKey.set(item.stable_id, { indexed, source, type });
|
|
4799
|
+
rankerItems.push(item);
|
|
4567
4800
|
}
|
|
4568
4801
|
}
|
|
4569
4802
|
}
|
|
4803
|
+
if (rankerItems.length === 0) {
|
|
4804
|
+
return [];
|
|
4805
|
+
}
|
|
4806
|
+
let revision;
|
|
4807
|
+
try {
|
|
4808
|
+
revision = await computeReadSetRevision(projectRoot);
|
|
4809
|
+
} catch {
|
|
4810
|
+
revision = "triage-search";
|
|
4811
|
+
}
|
|
4812
|
+
const scoringContext = await buildScoringContext(projectRoot, revision, rankerItems, {
|
|
4813
|
+
queryText: query,
|
|
4814
|
+
targetPaths: []
|
|
4815
|
+
});
|
|
4816
|
+
const ranked = rankDescriptionItems(rankerItems, scoringContext, "triage");
|
|
4817
|
+
const items = [];
|
|
4818
|
+
for (const { item } of ranked) {
|
|
4819
|
+
const match = matchedByKey.get(item.stable_id);
|
|
4820
|
+
if (match === void 0) continue;
|
|
4821
|
+
const { indexed, source, type } = match;
|
|
4822
|
+
const { absolutePath, fm, layer, maturity } = indexed;
|
|
4823
|
+
const reportedPath = source.isStore ? absolutePath : source.isPersonal ? `~/${relative(resolvePersonalRoot(), absolutePath)}` : relative(projectRoot, absolutePath);
|
|
4824
|
+
items.push({
|
|
4825
|
+
area: source.isPending ? "pending" : "canonical",
|
|
4826
|
+
path: reportedPath,
|
|
4827
|
+
...source.isPersonal ? { path_absolute: absolutePath } : {},
|
|
4828
|
+
type,
|
|
4829
|
+
layer,
|
|
4830
|
+
maturity,
|
|
4831
|
+
// Only pending entries carry an origin tag (canonical hits live
|
|
4832
|
+
// outside the dual-pending-root convention).
|
|
4833
|
+
...source.isPending ? { origin: source.isPersonal ? "personal" : "team" } : {},
|
|
4834
|
+
...fm.tags !== void 0 && fm.tags.length > 0 ? { tags: fm.tags } : {},
|
|
4835
|
+
...fm.title !== void 0 ? { title: fm.title } : {},
|
|
4836
|
+
...fm.summary !== void 0 ? { summary: fm.summary } : {},
|
|
4837
|
+
...fm.status !== void 0 ? { status: fm.status } : {},
|
|
4838
|
+
...fm.deferred_until !== void 0 ? { deferred_until: fm.deferred_until } : {},
|
|
4839
|
+
// v2.0.0-rc.27 TASK-006 (audit §2.23): body emission when opted in.
|
|
4840
|
+
...includeBody ? { body: indexed.body } : {},
|
|
4841
|
+
// Canonical hits always have an id; pending hits typically don't yet —
|
|
4842
|
+
// surface the frontmatter id when present so consumers can dedupe.
|
|
4843
|
+
...fm.id !== void 0 ? { stable_id: fm.id } : {}
|
|
4844
|
+
});
|
|
4845
|
+
}
|
|
4570
4846
|
return items;
|
|
4571
4847
|
}
|
|
4572
4848
|
async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
@@ -4576,7 +4852,7 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
|
4576
4852
|
try {
|
|
4577
4853
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4578
4854
|
if (existsSync5(sandboxed.abs)) {
|
|
4579
|
-
const content = await
|
|
4855
|
+
const content = await readFile7(sandboxed.abs, "utf8");
|
|
4580
4856
|
stableId = parseFrontmatter(content).id;
|
|
4581
4857
|
const patch = {
|
|
4582
4858
|
status: "deferred",
|
|
@@ -4917,9 +5193,9 @@ function registerPending(server, tracker) {
|
|
|
4917
5193
|
}
|
|
4918
5194
|
|
|
4919
5195
|
// src/services/doctor.ts
|
|
4920
|
-
import { access as access4, readFile as
|
|
5196
|
+
import { access as access4, readFile as readFile17, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
4921
5197
|
import { constants as constants2 } from "fs";
|
|
4922
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5198
|
+
import { isAbsolute as isAbsolute2, join as join22, posix as posix5, resolve as resolve3 } from "path";
|
|
4923
5199
|
import {
|
|
4924
5200
|
createTranslator,
|
|
4925
5201
|
forensicReportSchema,
|
|
@@ -5133,8 +5409,8 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
|
|
|
5133
5409
|
}
|
|
5134
5410
|
|
|
5135
5411
|
// src/services/doctor-stable-id-collision.ts
|
|
5136
|
-
import { readFile as
|
|
5137
|
-
import { basename as basename2, join as
|
|
5412
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5413
|
+
import { basename as basename2, join as join12 } from "path";
|
|
5138
5414
|
import {
|
|
5139
5415
|
buildStoreResolveInput as buildStoreResolveInput4,
|
|
5140
5416
|
createStoreResolver as createStoreResolver4,
|
|
@@ -5161,7 +5437,7 @@ function resolveIntegrityStores(projectRoot) {
|
|
|
5161
5437
|
return {
|
|
5162
5438
|
store_uuid: entry.store_uuid,
|
|
5163
5439
|
alias: entry.alias,
|
|
5164
|
-
dir:
|
|
5440
|
+
dir: join12(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
|
|
5165
5441
|
};
|
|
5166
5442
|
});
|
|
5167
5443
|
return { dirs, personalUuids };
|
|
@@ -5180,7 +5456,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
|
|
|
5180
5456
|
for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
|
|
5181
5457
|
let source;
|
|
5182
5458
|
try {
|
|
5183
|
-
source = await
|
|
5459
|
+
source = await readFile8(ref.file, "utf8");
|
|
5184
5460
|
} catch {
|
|
5185
5461
|
continue;
|
|
5186
5462
|
}
|
|
@@ -5265,7 +5541,7 @@ function createLayerMismatchCheck(t, inspection) {
|
|
|
5265
5541
|
// src/services/doctor-relevance-paths.ts
|
|
5266
5542
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5267
5543
|
import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
|
|
5268
|
-
import { join as
|
|
5544
|
+
import { join as join13, sep as sep3 } from "path";
|
|
5269
5545
|
import { minimatch } from "minimatch";
|
|
5270
5546
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
5271
5547
|
var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
|
|
@@ -5308,7 +5584,7 @@ function collectWorkspacePaths(projectRoot) {
|
|
|
5308
5584
|
continue;
|
|
5309
5585
|
}
|
|
5310
5586
|
for (const entry of entries) {
|
|
5311
|
-
const abs =
|
|
5587
|
+
const abs = join13(current, entry.name);
|
|
5312
5588
|
const rel = toPosix(abs.slice(projectRoot.length + 1));
|
|
5313
5589
|
if (rel.length === 0) continue;
|
|
5314
5590
|
if (entry.isDirectory()) {
|
|
@@ -5501,16 +5777,16 @@ function createNarrowNoPathsCheck(t, inspection) {
|
|
|
5501
5777
|
}
|
|
5502
5778
|
|
|
5503
5779
|
// src/services/doctor-broad-index.ts
|
|
5504
|
-
import { readFile as
|
|
5505
|
-
import { join as
|
|
5780
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5781
|
+
import { join as join14 } from "path";
|
|
5506
5782
|
var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
|
|
5507
5783
|
var BROAD_INDEX_BACKSTOP_MIN = 20;
|
|
5508
5784
|
var BROAD_INDEX_BACKSTOP_MAX = 500;
|
|
5509
5785
|
var BROAD_INDEX_DRIFT_RATIO = 0.8;
|
|
5510
5786
|
async function readBroadIndexBackstop(projectRoot) {
|
|
5511
|
-
const configPath =
|
|
5787
|
+
const configPath = join14(projectRoot, ".fabric", "fabric-config.json");
|
|
5512
5788
|
try {
|
|
5513
|
-
const raw = await
|
|
5789
|
+
const raw = await readFile9(configPath, "utf8");
|
|
5514
5790
|
const parsed = JSON.parse(raw);
|
|
5515
5791
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
5516
5792
|
const v = parsed.broad_index_backstop;
|
|
@@ -5827,8 +6103,8 @@ function createBroadReviewRecheckCheck(t, inspection) {
|
|
|
5827
6103
|
}
|
|
5828
6104
|
|
|
5829
6105
|
// src/services/doctor-scope-lint.ts
|
|
5830
|
-
import { readFile as
|
|
5831
|
-
import { join as
|
|
6106
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
6107
|
+
import { join as join15 } from "path";
|
|
5832
6108
|
import {
|
|
5833
6109
|
buildStoreResolveInput as buildStoreResolveInput5,
|
|
5834
6110
|
createStoreResolver as createStoreResolver5,
|
|
@@ -5863,7 +6139,7 @@ async function resolveLintStores(projectRoot) {
|
|
|
5863
6139
|
const globalRoot = resolveGlobalRoot4();
|
|
5864
6140
|
return Promise.all(readSet.stores.map(async (entry) => {
|
|
5865
6141
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
5866
|
-
const dir =
|
|
6142
|
+
const dir = join15(
|
|
5867
6143
|
globalRoot,
|
|
5868
6144
|
storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
|
|
5869
6145
|
);
|
|
@@ -5895,7 +6171,7 @@ async function lintStoreScopes(projectRoot) {
|
|
|
5895
6171
|
}
|
|
5896
6172
|
let source;
|
|
5897
6173
|
try {
|
|
5898
|
-
source = await
|
|
6174
|
+
source = await readFile10(ref.file, "utf8");
|
|
5899
6175
|
} catch {
|
|
5900
6176
|
continue;
|
|
5901
6177
|
}
|
|
@@ -6018,7 +6294,7 @@ function createUnboundProjectCheck(t, violation) {
|
|
|
6018
6294
|
|
|
6019
6295
|
// src/services/doctor-store-counters.ts
|
|
6020
6296
|
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
6021
|
-
import { join as
|
|
6297
|
+
import { join as join16 } from "path";
|
|
6022
6298
|
import {
|
|
6023
6299
|
buildStoreResolveInput as buildStoreResolveInput6,
|
|
6024
6300
|
createStoreResolver as createStoreResolver6,
|
|
@@ -6045,7 +6321,7 @@ function resolveCounterStores(projectRoot) {
|
|
|
6045
6321
|
return readSet.stores.map((entry) => ({
|
|
6046
6322
|
uuid: entry.store_uuid,
|
|
6047
6323
|
alias: entry.alias,
|
|
6048
|
-
dir:
|
|
6324
|
+
dir: join16(
|
|
6049
6325
|
globalRoot,
|
|
6050
6326
|
storeRelativePathForMount5(
|
|
6051
6327
|
input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
|
|
@@ -6073,7 +6349,7 @@ function readEntryId(file) {
|
|
|
6073
6349
|
function computeStoreDiskMax(storeDir) {
|
|
6074
6350
|
const max = defaultAgentsMetaCounters();
|
|
6075
6351
|
for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
6076
|
-
const dir =
|
|
6352
|
+
const dir = join16(storeDir, STORE_LAYOUT2.knowledgeDir, type);
|
|
6077
6353
|
if (!existsSync7(dir)) {
|
|
6078
6354
|
continue;
|
|
6079
6355
|
}
|
|
@@ -6087,7 +6363,7 @@ function computeStoreDiskMax(storeDir) {
|
|
|
6087
6363
|
if (!name.endsWith(".md")) {
|
|
6088
6364
|
continue;
|
|
6089
6365
|
}
|
|
6090
|
-
const parsed = parseKnowledgeId2(readEntryId(
|
|
6366
|
+
const parsed = parseKnowledgeId2(readEntryId(join16(dir, name)) ?? "");
|
|
6091
6367
|
if (parsed === null) {
|
|
6092
6368
|
continue;
|
|
6093
6369
|
}
|
|
@@ -6170,7 +6446,7 @@ function createStoreCounterCheck(t, drifts) {
|
|
|
6170
6446
|
|
|
6171
6447
|
// src/services/doctor-store-orphan.ts
|
|
6172
6448
|
import { readdirSync as readdirSync3 } from "fs";
|
|
6173
|
-
import { join as
|
|
6449
|
+
import { join as join17 } from "path";
|
|
6174
6450
|
import {
|
|
6175
6451
|
STORES_ROOT_DIR,
|
|
6176
6452
|
addMountedStore,
|
|
@@ -6194,15 +6470,15 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
|
|
|
6194
6470
|
return [];
|
|
6195
6471
|
}
|
|
6196
6472
|
const registered = new Set(config.stores.map((s) => s.store_uuid));
|
|
6197
|
-
const storesRoot =
|
|
6473
|
+
const storesRoot = join17(globalRoot, STORES_ROOT_DIR);
|
|
6198
6474
|
const orphans = [];
|
|
6199
6475
|
for (const group of listDir(storesRoot)) {
|
|
6200
6476
|
if (group === STORE_BY_ALIAS_DIR) {
|
|
6201
6477
|
continue;
|
|
6202
6478
|
}
|
|
6203
|
-
const groupDir =
|
|
6479
|
+
const groupDir = join17(storesRoot, group);
|
|
6204
6480
|
for (const mount of listDir(groupDir)) {
|
|
6205
|
-
const dir =
|
|
6481
|
+
const dir = join17(groupDir, mount);
|
|
6206
6482
|
const identity = readStoreIdentity(dir);
|
|
6207
6483
|
if (identity === null || registered.has(identity.store_uuid)) {
|
|
6208
6484
|
continue;
|
|
@@ -6339,8 +6615,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
6339
6615
|
}
|
|
6340
6616
|
|
|
6341
6617
|
// src/services/doctor-skill-lints.ts
|
|
6342
|
-
import { readdir as readdir3, readFile as
|
|
6343
|
-
import { join as
|
|
6618
|
+
import { readdir as readdir3, readFile as readFile11 } from "fs/promises";
|
|
6619
|
+
import { join as join18, posix as posix2 } from "path";
|
|
6344
6620
|
var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review"];
|
|
6345
6621
|
var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
|
|
6346
6622
|
var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
|
|
@@ -6370,8 +6646,8 @@ async function listMarkdownFiles(dir) {
|
|
|
6370
6646
|
async function inspectSkillRefMirror(projectRoot) {
|
|
6371
6647
|
const driftedPaths = [];
|
|
6372
6648
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6373
|
-
const claudeRef =
|
|
6374
|
-
const codexRef =
|
|
6649
|
+
const claudeRef = join18(projectRoot, ".claude", "skills", slug, "ref");
|
|
6650
|
+
const codexRef = join18(projectRoot, ".codex", "skills", slug, "ref");
|
|
6375
6651
|
const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
|
|
6376
6652
|
if (claudeFiles === null || codexFiles === null) continue;
|
|
6377
6653
|
const claudeSet = new Set(claudeFiles);
|
|
@@ -6388,8 +6664,8 @@ async function inspectSkillRefMirror(projectRoot) {
|
|
|
6388
6664
|
let codexBody;
|
|
6389
6665
|
try {
|
|
6390
6666
|
[claudeBody, codexBody] = await Promise.all([
|
|
6391
|
-
|
|
6392
|
-
|
|
6667
|
+
readFile11(join18(claudeRef, fname), "utf8"),
|
|
6668
|
+
readFile11(join18(codexRef, fname), "utf8")
|
|
6393
6669
|
]);
|
|
6394
6670
|
} catch {
|
|
6395
6671
|
continue;
|
|
@@ -6408,10 +6684,10 @@ async function inspectSkillTokenBudget(projectRoot) {
|
|
|
6408
6684
|
const overSize = [];
|
|
6409
6685
|
let highestSeverity = "ok";
|
|
6410
6686
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6411
|
-
const skillMdPath =
|
|
6687
|
+
const skillMdPath = join18(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6412
6688
|
let body;
|
|
6413
6689
|
try {
|
|
6414
|
-
body = await
|
|
6690
|
+
body = await readFile11(skillMdPath, "utf8");
|
|
6415
6691
|
} catch {
|
|
6416
6692
|
continue;
|
|
6417
6693
|
}
|
|
@@ -6432,10 +6708,10 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6432
6708
|
const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
|
|
6433
6709
|
const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
|
|
6434
6710
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6435
|
-
const skillMdPath =
|
|
6711
|
+
const skillMdPath = join18(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6436
6712
|
let body;
|
|
6437
6713
|
try {
|
|
6438
|
-
body = await
|
|
6714
|
+
body = await readFile11(skillMdPath, "utf8");
|
|
6439
6715
|
} catch {
|
|
6440
6716
|
continue;
|
|
6441
6717
|
}
|
|
@@ -6466,7 +6742,7 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6466
6742
|
async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
6467
6743
|
const candidates = [];
|
|
6468
6744
|
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
6469
|
-
const rootAbs =
|
|
6745
|
+
const rootAbs = join18(projectRoot, rootRel);
|
|
6470
6746
|
let dirEntries;
|
|
6471
6747
|
try {
|
|
6472
6748
|
dirEntries = await readdir3(rootAbs, { withFileTypes: true });
|
|
@@ -6475,10 +6751,10 @@ async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
|
6475
6751
|
}
|
|
6476
6752
|
for (const dirEntry of dirEntries) {
|
|
6477
6753
|
if (!dirEntry.isDirectory()) continue;
|
|
6478
|
-
const skillFile =
|
|
6754
|
+
const skillFile = join18(rootAbs, dirEntry.name, "SKILL.md");
|
|
6479
6755
|
let raw;
|
|
6480
6756
|
try {
|
|
6481
|
-
raw = await
|
|
6757
|
+
raw = await readFile11(skillFile, "utf8");
|
|
6482
6758
|
} catch {
|
|
6483
6759
|
continue;
|
|
6484
6760
|
}
|
|
@@ -6601,8 +6877,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
|
|
|
6601
6877
|
}
|
|
6602
6878
|
|
|
6603
6879
|
// src/services/doctor-retired-references-lint.ts
|
|
6604
|
-
import { readdir as readdir4, readFile as
|
|
6605
|
-
import { join as
|
|
6880
|
+
import { readdir as readdir4, readFile as readFile12, stat as stat3 } from "fs/promises";
|
|
6881
|
+
import { join as join19, posix as posix3, relative as relative2 } from "path";
|
|
6606
6882
|
var RETIRED_TOKENS = [
|
|
6607
6883
|
{ token: "fab_plan_context", replacement: "fab_recall", reason: "retrieval collapsed to one lean fab_recall (KT-DEC-0026)" },
|
|
6608
6884
|
{ token: "fab_get_knowledge_sections", replacement: "fab_recall", reason: "two-step fetch retired (KT-DEC-0026)" },
|
|
@@ -6625,7 +6901,7 @@ var RETIRED_TOKENS = [
|
|
|
6625
6901
|
];
|
|
6626
6902
|
var HOOK_DIRS = [".claude/hooks", ".codex/hooks"];
|
|
6627
6903
|
var SKILL_DIRS = [".claude/skills", ".codex/skills"];
|
|
6628
|
-
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md",
|
|
6904
|
+
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md", join19(".fabric", "AGENTS.md")];
|
|
6629
6905
|
function isCommentLine(line) {
|
|
6630
6906
|
const t = line.trim();
|
|
6631
6907
|
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
@@ -6639,7 +6915,7 @@ async function walkFiles(dir, exts) {
|
|
|
6639
6915
|
}
|
|
6640
6916
|
const out = [];
|
|
6641
6917
|
for (const e of entries) {
|
|
6642
|
-
const full =
|
|
6918
|
+
const full = join19(dir, e.name);
|
|
6643
6919
|
if (e.isDirectory()) {
|
|
6644
6920
|
out.push(...await walkFiles(full, exts));
|
|
6645
6921
|
} else if (exts.some((ext) => e.name.endsWith(ext))) {
|
|
@@ -6665,29 +6941,29 @@ async function inspectRetiredReferences(projectRoot) {
|
|
|
6665
6941
|
let scannedFiles = 0;
|
|
6666
6942
|
const toRel = (p) => posix3.normalize(relative2(projectRoot, p).split("\\").join("/"));
|
|
6667
6943
|
for (const rel of BOOTSTRAP_FILES) {
|
|
6668
|
-
const abs =
|
|
6944
|
+
const abs = join19(projectRoot, rel);
|
|
6669
6945
|
try {
|
|
6670
6946
|
if ((await stat3(abs)).isFile()) {
|
|
6671
6947
|
scannedFiles += 1;
|
|
6672
|
-
scanText(toRel(abs), await
|
|
6948
|
+
scanText(toRel(abs), await readFile12(abs, "utf8"), false, hits);
|
|
6673
6949
|
}
|
|
6674
6950
|
} catch {
|
|
6675
6951
|
}
|
|
6676
6952
|
}
|
|
6677
6953
|
for (const dir of SKILL_DIRS) {
|
|
6678
|
-
for (const file of await walkFiles(
|
|
6954
|
+
for (const file of await walkFiles(join19(projectRoot, dir), [".md"])) {
|
|
6679
6955
|
scannedFiles += 1;
|
|
6680
6956
|
try {
|
|
6681
|
-
scanText(toRel(file), await
|
|
6957
|
+
scanText(toRel(file), await readFile12(file, "utf8"), false, hits);
|
|
6682
6958
|
} catch {
|
|
6683
6959
|
}
|
|
6684
6960
|
}
|
|
6685
6961
|
}
|
|
6686
6962
|
for (const dir of HOOK_DIRS) {
|
|
6687
|
-
for (const file of await walkFiles(
|
|
6963
|
+
for (const file of await walkFiles(join19(projectRoot, dir), [".cjs"])) {
|
|
6688
6964
|
scannedFiles += 1;
|
|
6689
6965
|
try {
|
|
6690
|
-
scanText(toRel(file), await
|
|
6966
|
+
scanText(toRel(file), await readFile12(file, "utf8"), true, hits);
|
|
6691
6967
|
} catch {
|
|
6692
6968
|
}
|
|
6693
6969
|
}
|
|
@@ -6722,8 +6998,8 @@ function createRetiredReferenceCheck(t, inspection) {
|
|
|
6722
6998
|
|
|
6723
6999
|
// src/services/doctor-hooks-lints.ts
|
|
6724
7000
|
import { constants } from "fs";
|
|
6725
|
-
import { access, readdir as readdir5, readFile as
|
|
6726
|
-
import { join as
|
|
7001
|
+
import { access, readdir as readdir5, readFile as readFile13, stat as stat4 } from "fs/promises";
|
|
7002
|
+
import { join as join20, posix as posix4 } from "path";
|
|
6727
7003
|
import { Script } from "vm";
|
|
6728
7004
|
var HOOKS_RUNTIME_CLIENT_DIRS = [
|
|
6729
7005
|
{ client: "claude", dir: ".claude/hooks" },
|
|
@@ -6785,14 +7061,14 @@ async function isFile(absPath) {
|
|
|
6785
7061
|
}
|
|
6786
7062
|
}
|
|
6787
7063
|
async function inspectHooksWired(projectRoot) {
|
|
6788
|
-
const claudeEntries = await readDirectoryFileNames(
|
|
7064
|
+
const claudeEntries = await readDirectoryFileNames(join20(projectRoot, ".claude"));
|
|
6789
7065
|
if (claudeEntries === null) {
|
|
6790
7066
|
return { status: "skipped", missingHooks: [] };
|
|
6791
7067
|
}
|
|
6792
|
-
const settingsPath =
|
|
7068
|
+
const settingsPath = join20(projectRoot, ".claude", "settings.json");
|
|
6793
7069
|
let parsed;
|
|
6794
7070
|
try {
|
|
6795
|
-
parsed = JSON.parse(await
|
|
7071
|
+
parsed = JSON.parse(await readFile13(settingsPath, "utf8"));
|
|
6796
7072
|
} catch {
|
|
6797
7073
|
return { status: "missing-settings", missingHooks: [] };
|
|
6798
7074
|
}
|
|
@@ -6815,8 +7091,8 @@ async function inspectHooksWired(projectRoot) {
|
|
|
6815
7091
|
}
|
|
6816
7092
|
async function inspectHookCacheWritability(projectRoot) {
|
|
6817
7093
|
const relPath = posix4.join(".fabric", ".cache");
|
|
6818
|
-
const fabricDir =
|
|
6819
|
-
const cacheDir =
|
|
7094
|
+
const fabricDir = join20(projectRoot, ".fabric");
|
|
7095
|
+
const cacheDir = join20(projectRoot, ".fabric", ".cache");
|
|
6820
7096
|
try {
|
|
6821
7097
|
try {
|
|
6822
7098
|
const cacheStats = await stat4(cacheDir);
|
|
@@ -6865,12 +7141,12 @@ async function inspectHookCacheWritability(projectRoot) {
|
|
|
6865
7141
|
async function inspectHooksContentDrift(projectRoot) {
|
|
6866
7142
|
const hookFilesByBasename = /* @__PURE__ */ new Map();
|
|
6867
7143
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
6868
|
-
const absDir =
|
|
7144
|
+
const absDir = join20(projectRoot, dir);
|
|
6869
7145
|
const entries = await readDirectoryFileNames(absDir);
|
|
6870
7146
|
if (entries === null) continue;
|
|
6871
7147
|
for (const name of entries) {
|
|
6872
7148
|
if (!name.endsWith(".cjs")) continue;
|
|
6873
|
-
const abs =
|
|
7149
|
+
const abs = join20(absDir, name);
|
|
6874
7150
|
if (!await isFile(abs)) continue;
|
|
6875
7151
|
const arr = hookFilesByBasename.get(name) ?? [];
|
|
6876
7152
|
arr.push({ client, abs });
|
|
@@ -6885,7 +7161,7 @@ async function inspectHooksContentDrift(projectRoot) {
|
|
|
6885
7161
|
const hashes = [];
|
|
6886
7162
|
for (const { client, abs } of copies) {
|
|
6887
7163
|
try {
|
|
6888
|
-
const body = await
|
|
7164
|
+
const body = await readFile13(abs, "utf8");
|
|
6889
7165
|
hashes.push({ client, sha: sha256(body) });
|
|
6890
7166
|
} catch {
|
|
6891
7167
|
}
|
|
@@ -6907,18 +7183,18 @@ async function inspectHooksRuntime(projectRoot) {
|
|
|
6907
7183
|
const issues = [];
|
|
6908
7184
|
let scanned = 0;
|
|
6909
7185
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
6910
|
-
const absDir =
|
|
7186
|
+
const absDir = join20(projectRoot, dir);
|
|
6911
7187
|
const entries = await readDirectoryFileNames(absDir);
|
|
6912
7188
|
if (entries === null) continue;
|
|
6913
7189
|
for (const name of entries) {
|
|
6914
7190
|
if (!name.endsWith(".cjs")) continue;
|
|
6915
|
-
const abs =
|
|
7191
|
+
const abs = join20(absDir, name);
|
|
6916
7192
|
const displayPath = `${dir}/${name}`;
|
|
6917
7193
|
if (!await isFile(abs)) continue;
|
|
6918
7194
|
scanned += 1;
|
|
6919
7195
|
let body;
|
|
6920
7196
|
try {
|
|
6921
|
-
body = await
|
|
7197
|
+
body = await readFile13(abs, "utf8");
|
|
6922
7198
|
} catch (err) {
|
|
6923
7199
|
issues.push({
|
|
6924
7200
|
path: displayPath,
|
|
@@ -7055,8 +7331,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
|
|
|
7055
7331
|
}
|
|
7056
7332
|
|
|
7057
7333
|
// src/services/doctor-bootstrap-lints.ts
|
|
7058
|
-
import { access as access2, readFile as
|
|
7059
|
-
import { join as
|
|
7334
|
+
import { access as access2, readFile as readFile14 } from "fs/promises";
|
|
7335
|
+
import { join as join21 } from "path";
|
|
7060
7336
|
import {
|
|
7061
7337
|
BOOTSTRAP_MARKER_BEGIN,
|
|
7062
7338
|
BOOTSTRAP_MARKER_END,
|
|
@@ -7088,17 +7364,17 @@ async function fileExists(path) {
|
|
|
7088
7364
|
}
|
|
7089
7365
|
async function inspectBootstrapAnchor(projectRoot) {
|
|
7090
7366
|
const [hasAgentsMd, hasClaudeMd] = await Promise.all([
|
|
7091
|
-
fileExists(
|
|
7092
|
-
fileExists(
|
|
7367
|
+
fileExists(join21(projectRoot, "AGENTS.md")),
|
|
7368
|
+
fileExists(join21(projectRoot, "CLAUDE.md"))
|
|
7093
7369
|
]);
|
|
7094
7370
|
return { hasAgentsMd, hasClaudeMd };
|
|
7095
7371
|
}
|
|
7096
7372
|
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
7097
|
-
const abs =
|
|
7373
|
+
const abs = join21(target, ".fabric", "AGENTS.md");
|
|
7098
7374
|
const canonical = resolveBootstrapCanonical();
|
|
7099
7375
|
let onDisk;
|
|
7100
7376
|
try {
|
|
7101
|
-
onDisk = await
|
|
7377
|
+
onDisk = await readFile14(abs, "utf8");
|
|
7102
7378
|
} catch {
|
|
7103
7379
|
return { status: "missing", canonical, onDisk: null };
|
|
7104
7380
|
}
|
|
@@ -7124,17 +7400,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
|
|
|
7124
7400
|
);
|
|
7125
7401
|
}
|
|
7126
7402
|
async function inspectL2ManagedBlockDrift(target) {
|
|
7127
|
-
const snapshotPath =
|
|
7403
|
+
const snapshotPath = join21(target, ".fabric", "AGENTS.md");
|
|
7128
7404
|
let snapshot;
|
|
7129
7405
|
try {
|
|
7130
|
-
snapshot = await
|
|
7406
|
+
snapshot = await readFile14(snapshotPath, "utf8");
|
|
7131
7407
|
} catch {
|
|
7132
7408
|
return { status: "ok", drifted: [] };
|
|
7133
7409
|
}
|
|
7134
|
-
const projectRulesPath =
|
|
7410
|
+
const projectRulesPath = join21(target, ".fabric", "project-rules.md");
|
|
7135
7411
|
let expectedBody = snapshot;
|
|
7136
7412
|
try {
|
|
7137
|
-
const projectRules = await
|
|
7413
|
+
const projectRules = await readFile14(projectRulesPath, "utf8");
|
|
7138
7414
|
expectedBody = `${snapshot}
|
|
7139
7415
|
---
|
|
7140
7416
|
${projectRules}`;
|
|
@@ -7143,12 +7419,12 @@ ${projectRules}`;
|
|
|
7143
7419
|
const drifted = [];
|
|
7144
7420
|
let anyManagedBlockFound = false;
|
|
7145
7421
|
const blockTargets = [
|
|
7146
|
-
|
|
7422
|
+
join21(target, "AGENTS.md")
|
|
7147
7423
|
];
|
|
7148
7424
|
for (const abs of blockTargets) {
|
|
7149
7425
|
let content;
|
|
7150
7426
|
try {
|
|
7151
|
-
content = await
|
|
7427
|
+
content = await readFile14(abs, "utf8");
|
|
7152
7428
|
} catch {
|
|
7153
7429
|
continue;
|
|
7154
7430
|
}
|
|
@@ -7171,9 +7447,9 @@ ${projectRules}`;
|
|
|
7171
7447
|
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
7172
7448
|
}
|
|
7173
7449
|
}
|
|
7174
|
-
const claudeMdPath =
|
|
7450
|
+
const claudeMdPath = join21(target, "CLAUDE.md");
|
|
7175
7451
|
try {
|
|
7176
|
-
const claudeContent = await
|
|
7452
|
+
const claudeContent = await readFile14(claudeMdPath, "utf8");
|
|
7177
7453
|
anyManagedBlockFound = true;
|
|
7178
7454
|
const lines = claudeContent.split(/\r?\n/u);
|
|
7179
7455
|
const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
|
|
@@ -7241,7 +7517,7 @@ import { appendFile as appendFile3 } from "fs/promises";
|
|
|
7241
7517
|
import { minimatch as minimatch2 } from "minimatch";
|
|
7242
7518
|
|
|
7243
7519
|
// src/services/cite-rollup.ts
|
|
7244
|
-
import { readFile as
|
|
7520
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
7245
7521
|
import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
7246
7522
|
var citeRollupQueue = createLedgerWriteQueue3();
|
|
7247
7523
|
async function appendCiteRollupRow(projectRoot, row) {
|
|
@@ -7253,7 +7529,7 @@ async function readCiteRollup(projectRoot) {
|
|
|
7253
7529
|
const path = getCiteRollupPath(projectRoot);
|
|
7254
7530
|
let raw;
|
|
7255
7531
|
try {
|
|
7256
|
-
raw = await
|
|
7532
|
+
raw = await readFile15(path, "utf8");
|
|
7257
7533
|
} catch (error) {
|
|
7258
7534
|
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
7259
7535
|
throw error;
|
|
@@ -8383,7 +8659,7 @@ async function runDoctorReport(target) {
|
|
|
8383
8659
|
const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
|
|
8384
8660
|
const targetFiles = Object.fromEntries(
|
|
8385
8661
|
await Promise.all(
|
|
8386
|
-
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(
|
|
8662
|
+
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join22(projectRoot, path))])
|
|
8387
8663
|
)
|
|
8388
8664
|
);
|
|
8389
8665
|
const checks = [
|
|
@@ -8587,7 +8863,7 @@ async function runDoctorFix(target) {
|
|
|
8587
8863
|
const fixed = [];
|
|
8588
8864
|
const ledgerWarnings = [];
|
|
8589
8865
|
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
8590
|
-
const snapshotPath =
|
|
8866
|
+
const snapshotPath = join22(projectRoot, ".fabric", "AGENTS.md");
|
|
8591
8867
|
await ensureParentDirectory(snapshotPath);
|
|
8592
8868
|
await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
|
|
8593
8869
|
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
@@ -8660,7 +8936,7 @@ async function runDoctorFix(target) {
|
|
|
8660
8936
|
if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
|
|
8661
8937
|
const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
|
|
8662
8938
|
if (lockInspection.present && !lockInspection.pidAlive) {
|
|
8663
|
-
const lockFilePath =
|
|
8939
|
+
const lockFilePath = join22(projectRoot, ".fabric", ".serve.lock");
|
|
8664
8940
|
try {
|
|
8665
8941
|
await unlink4(lockFilePath);
|
|
8666
8942
|
} catch (err) {
|
|
@@ -8777,7 +9053,7 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
|
|
|
8777
9053
|
}
|
|
8778
9054
|
async function applySessionHintsStaleCleanup(projectRoot, candidate) {
|
|
8779
9055
|
const detail = `deleted (${candidate.age_days}d old)`;
|
|
8780
|
-
const absPath =
|
|
9056
|
+
const absPath = join22(projectRoot, candidate.path);
|
|
8781
9057
|
try {
|
|
8782
9058
|
const { unlink: unlink5 } = await import("fs/promises");
|
|
8783
9059
|
await unlink5(absPath);
|
|
@@ -8802,9 +9078,9 @@ function truncateErrorMessage(error) {
|
|
|
8802
9078
|
return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
|
|
8803
9079
|
}
|
|
8804
9080
|
async function inspectForensic(projectRoot) {
|
|
8805
|
-
const path =
|
|
9081
|
+
const path = join22(projectRoot, ".fabric", "forensic.json");
|
|
8806
9082
|
try {
|
|
8807
|
-
const parsed = forensicReportSchema.parse(JSON.parse(await
|
|
9083
|
+
const parsed = forensicReportSchema.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
8808
9084
|
return { present: true, valid: true, report: parsed };
|
|
8809
9085
|
} catch (error) {
|
|
8810
9086
|
if (isMissingFileError(error)) {
|
|
@@ -8834,7 +9110,7 @@ async function inspectEventLedger(projectRoot) {
|
|
|
8834
9110
|
try {
|
|
8835
9111
|
await access4(path, constants2.W_OK);
|
|
8836
9112
|
const { warnings } = await readEventLedger(projectRoot);
|
|
8837
|
-
const raw = await
|
|
9113
|
+
const raw = await readFile17(path, "utf8");
|
|
8838
9114
|
const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
|
|
8839
9115
|
const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
|
|
8840
9116
|
const schemaVersionSamples = [];
|
|
@@ -9379,7 +9655,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
|
|
|
9379
9655
|
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
9380
9656
|
const detected = [];
|
|
9381
9657
|
for (const name of candidates) {
|
|
9382
|
-
if (await pathExists(
|
|
9658
|
+
if (await pathExists(join22(projectRoot, name))) {
|
|
9383
9659
|
detected.push(name);
|
|
9384
9660
|
}
|
|
9385
9661
|
}
|
|
@@ -9464,7 +9740,7 @@ async function buildLastActiveIndex(projectRoot) {
|
|
|
9464
9740
|
return map;
|
|
9465
9741
|
}
|
|
9466
9742
|
async function inspectSessionHintsStale(projectRoot, now) {
|
|
9467
|
-
const cacheDir =
|
|
9743
|
+
const cacheDir = join22(projectRoot, ".fabric", ".cache");
|
|
9468
9744
|
let entries;
|
|
9469
9745
|
try {
|
|
9470
9746
|
entries = await readdirAsync(cacheDir, { withFileTypes: true });
|
|
@@ -9476,7 +9752,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
|
|
|
9476
9752
|
if (!entry.isFile()) continue;
|
|
9477
9753
|
if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
|
|
9478
9754
|
if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
|
|
9479
|
-
const absPath =
|
|
9755
|
+
const absPath = join22(cacheDir, entry.name);
|
|
9480
9756
|
let mtimeMs = 0;
|
|
9481
9757
|
try {
|
|
9482
9758
|
mtimeMs = (await statAsync(absPath)).mtimeMs;
|
|
@@ -9508,9 +9784,9 @@ function inspectStaleServeLock(projectRoot, now) {
|
|
|
9508
9784
|
};
|
|
9509
9785
|
}
|
|
9510
9786
|
async function readUnderseedThresholdFromConfig(projectRoot) {
|
|
9511
|
-
const configPath =
|
|
9787
|
+
const configPath = join22(projectRoot, ".fabric", "fabric-config.json");
|
|
9512
9788
|
try {
|
|
9513
|
-
const raw = await
|
|
9789
|
+
const raw = await readFile17(configPath, "utf8");
|
|
9514
9790
|
const parsed = JSON.parse(raw);
|
|
9515
9791
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
9516
9792
|
const v = parsed.underseed_node_threshold;
|
|
@@ -9626,10 +9902,10 @@ async function inspectOnboardCoverage(projectRoot) {
|
|
|
9626
9902
|
return { filled, missing, opted_out: optedOut };
|
|
9627
9903
|
}
|
|
9628
9904
|
async function readOnboardOptedOut(projectRoot) {
|
|
9629
|
-
const path =
|
|
9905
|
+
const path = join22(projectRoot, ".fabric", "fabric-config.json");
|
|
9630
9906
|
let raw;
|
|
9631
9907
|
try {
|
|
9632
|
-
raw = await
|
|
9908
|
+
raw = await readFile17(path, "utf8");
|
|
9633
9909
|
} catch {
|
|
9634
9910
|
return [];
|
|
9635
9911
|
}
|
|
@@ -9731,22 +10007,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
|
|
|
9731
10007
|
}
|
|
9732
10008
|
}
|
|
9733
10009
|
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
9734
|
-
const snapshotPath =
|
|
10010
|
+
const snapshotPath = join22(projectRoot, ".fabric", "AGENTS.md");
|
|
9735
10011
|
if (!await pathExists(snapshotPath)) {
|
|
9736
10012
|
return;
|
|
9737
10013
|
}
|
|
9738
10014
|
let snapshot;
|
|
9739
10015
|
try {
|
|
9740
|
-
snapshot = await
|
|
10016
|
+
snapshot = await readFile17(snapshotPath, "utf8");
|
|
9741
10017
|
} catch {
|
|
9742
10018
|
return;
|
|
9743
10019
|
}
|
|
9744
|
-
const projectRulesPath =
|
|
10020
|
+
const projectRulesPath = join22(projectRoot, ".fabric", "project-rules.md");
|
|
9745
10021
|
const hasProjectRules = await pathExists(projectRulesPath);
|
|
9746
10022
|
let expectedBody = snapshot;
|
|
9747
10023
|
if (hasProjectRules) {
|
|
9748
10024
|
try {
|
|
9749
|
-
const projectRules = await
|
|
10025
|
+
const projectRules = await readFile17(projectRulesPath, "utf8");
|
|
9750
10026
|
expectedBody = `${snapshot}
|
|
9751
10027
|
---
|
|
9752
10028
|
${projectRules}`;
|
|
@@ -9757,7 +10033,7 @@ ${projectRules}`;
|
|
|
9757
10033
|
${expectedBody}
|
|
9758
10034
|
${BOOTSTRAP_MARKER_END2}`;
|
|
9759
10035
|
const blockTargets = [
|
|
9760
|
-
|
|
10036
|
+
join22(projectRoot, "AGENTS.md")
|
|
9761
10037
|
];
|
|
9762
10038
|
for (const abs of blockTargets) {
|
|
9763
10039
|
if (!await pathExists(abs)) {
|
|
@@ -9765,7 +10041,7 @@ ${BOOTSTRAP_MARKER_END2}`;
|
|
|
9765
10041
|
}
|
|
9766
10042
|
let existing;
|
|
9767
10043
|
try {
|
|
9768
|
-
existing = await
|
|
10044
|
+
existing = await readFile17(abs, "utf8");
|
|
9769
10045
|
} catch {
|
|
9770
10046
|
continue;
|
|
9771
10047
|
}
|
|
@@ -9790,11 +10066,11 @@ ${managedBlock}
|
|
|
9790
10066
|
}
|
|
9791
10067
|
await atomicWriteText4(abs, next);
|
|
9792
10068
|
}
|
|
9793
|
-
const claudeMdPath =
|
|
10069
|
+
const claudeMdPath = join22(projectRoot, "CLAUDE.md");
|
|
9794
10070
|
if (await pathExists(claudeMdPath)) {
|
|
9795
10071
|
let claudeContent;
|
|
9796
10072
|
try {
|
|
9797
|
-
claudeContent = await
|
|
10073
|
+
claudeContent = await readFile17(claudeMdPath, "utf8");
|
|
9798
10074
|
} catch {
|
|
9799
10075
|
return;
|
|
9800
10076
|
}
|
|
@@ -9821,7 +10097,7 @@ ${managedBlock}
|
|
|
9821
10097
|
async function ensureEventLedger(projectRoot) {
|
|
9822
10098
|
const path = getEventLedgerPath(projectRoot);
|
|
9823
10099
|
await ensureParentDirectory(path);
|
|
9824
|
-
await
|
|
10100
|
+
await writeFile4(path, "", { encoding: "utf8", flag: "a" });
|
|
9825
10101
|
}
|
|
9826
10102
|
function createFixMessage(fixed, report) {
|
|
9827
10103
|
const fixedText = fixed.length === 0 ? "No deterministic doctor fixes were needed." : `Applied ${fixed.length} deterministic doctor fix${fixed.length === 1 ? "" : "es"}.`;
|
|
@@ -9860,7 +10136,7 @@ async function collectEntryPoints(root) {
|
|
|
9860
10136
|
continue;
|
|
9861
10137
|
}
|
|
9862
10138
|
for (const entry of await readdirAsync(current, { withFileTypes: true })) {
|
|
9863
|
-
const absolutePath =
|
|
10139
|
+
const absolutePath = join22(current, entry.name);
|
|
9864
10140
|
const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
|
|
9865
10141
|
if (relativePath.length === 0) {
|
|
9866
10142
|
continue;
|
|
@@ -9936,7 +10212,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
|
|
|
9936
10212
|
scanned += 1;
|
|
9937
10213
|
let source;
|
|
9938
10214
|
try {
|
|
9939
|
-
source = await
|
|
10215
|
+
source = await readFile17(absPath, "utf8");
|
|
9940
10216
|
} catch {
|
|
9941
10217
|
continue;
|
|
9942
10218
|
}
|
|
@@ -10074,8 +10350,8 @@ async function runDoctorConflictLint(projectRoot, opts = {}) {
|
|
|
10074
10350
|
}
|
|
10075
10351
|
|
|
10076
10352
|
// src/services/why-not-surfaced.ts
|
|
10077
|
-
import { readFile as
|
|
10078
|
-
import { basename as basename3, join as
|
|
10353
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
10354
|
+
import { basename as basename3, join as join23 } from "path";
|
|
10079
10355
|
import {
|
|
10080
10356
|
buildStoreResolveInput as buildStoreResolveInput7,
|
|
10081
10357
|
createStoreResolver as createStoreResolver7,
|
|
@@ -10115,7 +10391,7 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10115
10391
|
const allStores = input.mountedStores.map((s) => ({
|
|
10116
10392
|
store_uuid: s.store_uuid,
|
|
10117
10393
|
alias: s.alias,
|
|
10118
|
-
dir:
|
|
10394
|
+
dir: join23(globalRoot, storeRelativePathForMount6(s))
|
|
10119
10395
|
}));
|
|
10120
10396
|
const refs = await readKnowledgeAcrossStores4(allStores);
|
|
10121
10397
|
const candidate = refs.find((ref) => fileMatchesId(ref.file, localId));
|
|
@@ -10124,7 +10400,7 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10124
10400
|
}
|
|
10125
10401
|
let source;
|
|
10126
10402
|
try {
|
|
10127
|
-
source = await
|
|
10403
|
+
source = await readFile18(candidate.file, "utf8");
|
|
10128
10404
|
} catch {
|
|
10129
10405
|
return base;
|
|
10130
10406
|
}
|
|
@@ -10212,7 +10488,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
|
|
|
10212
10488
|
|
|
10213
10489
|
// src/services/read-ledger.ts
|
|
10214
10490
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10215
|
-
import { access as access5, copyFile, readFile as
|
|
10491
|
+
import { access as access5, copyFile, readFile as readFile19, rm } from "fs/promises";
|
|
10216
10492
|
import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
10217
10493
|
async function resolveLedgerPaths(projectRoot) {
|
|
10218
10494
|
const primaryPath = getLedgerPath(projectRoot);
|
|
@@ -10240,7 +10516,7 @@ async function readLegacyLedger(projectRoot) {
|
|
|
10240
10516
|
const { readPath } = await resolveLedgerPaths(projectRoot);
|
|
10241
10517
|
let raw;
|
|
10242
10518
|
try {
|
|
10243
|
-
raw = await
|
|
10519
|
+
raw = await readFile19(readPath, "utf8");
|
|
10244
10520
|
} catch (error) {
|
|
10245
10521
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
10246
10522
|
return [];
|
|
@@ -10495,8 +10771,8 @@ function formatError(error) {
|
|
|
10495
10771
|
}
|
|
10496
10772
|
function formatPreexistingRootMessage(projectRoot) {
|
|
10497
10773
|
const preexisting = [];
|
|
10498
|
-
if (existsSync9(
|
|
10499
|
-
if (existsSync9(
|
|
10774
|
+
if (existsSync9(join24(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
|
|
10775
|
+
if (existsSync9(join24(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
|
|
10500
10776
|
if (preexisting.length === 0) return null;
|
|
10501
10777
|
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.`;
|
|
10502
10778
|
}
|
|
@@ -10523,7 +10799,7 @@ function createFabricServer(tracker) {
|
|
|
10523
10799
|
const server = new McpServer(
|
|
10524
10800
|
{
|
|
10525
10801
|
name: "fabric-knowledge-server",
|
|
10526
|
-
version: "2.3.0-rc.
|
|
10802
|
+
version: "2.3.0-rc.2"
|
|
10527
10803
|
},
|
|
10528
10804
|
{
|
|
10529
10805
|
instructions: FABRIC_SERVER_INSTRUCTIONS
|
|
@@ -10543,10 +10819,10 @@ function createFabricServer(tracker) {
|
|
|
10543
10819
|
},
|
|
10544
10820
|
async (_uri) => {
|
|
10545
10821
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
10546
|
-
const path =
|
|
10822
|
+
const path = join24(projectRoot, ".fabric", "bootstrap", "README.md");
|
|
10547
10823
|
let text = "";
|
|
10548
10824
|
if (existsSync9(path)) {
|
|
10549
|
-
text = await
|
|
10825
|
+
text = await readFile20(path, "utf8");
|
|
10550
10826
|
}
|
|
10551
10827
|
return {
|
|
10552
10828
|
contents: [
|