@fenglimg/fabric-server 2.3.0-rc.4 → 2.3.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +498 -195
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { existsSync as existsSync10 } from "fs";
|
|
3
|
-
import { readFile as
|
|
4
|
-
import { join as
|
|
3
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
4
|
+
import { join as join27, 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";
|
|
@@ -905,8 +905,10 @@ import { createHash as createHash2 } from "crypto";
|
|
|
905
905
|
import { readFile as readFile3 } from "fs/promises";
|
|
906
906
|
import { join as join5 } from "path";
|
|
907
907
|
import {
|
|
908
|
+
STORE_KNOWLEDGE_TYPE_DIRS,
|
|
908
909
|
STORE_LAYOUT,
|
|
909
910
|
STORE_PENDING_DIR,
|
|
911
|
+
STORE_PROJECT_ID_PATTERN,
|
|
910
912
|
buildStoreResolveInput,
|
|
911
913
|
createStoreResolver,
|
|
912
914
|
isPersonalLeakIntoSharedStore,
|
|
@@ -955,8 +957,25 @@ function resolveWriteTargetStoreDir(layer, projectRoot, semanticScope) {
|
|
|
955
957
|
function resolveStorePendingBase(layer, projectRoot, semanticScope) {
|
|
956
958
|
return join5(resolveWriteTargetStoreDir(layer, projectRoot, semanticScope), STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR);
|
|
957
959
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
+
var STORE_PROJECTS_DIR = "projects";
|
|
961
|
+
function isValidWriteProjectSegment(project) {
|
|
962
|
+
if (project.length === 0) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
if (project === STORE_PROJECTS_DIR) {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
if (STORE_KNOWLEDGE_TYPE_DIRS.includes(project)) {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
return STORE_PROJECT_ID_PATTERN.test(project);
|
|
972
|
+
}
|
|
973
|
+
function resolveStoreCanonicalBase(layer, projectRoot, project) {
|
|
974
|
+
const base = join5(resolveWriteTargetStoreDir(layer, projectRoot), STORE_LAYOUT.knowledgeDir);
|
|
975
|
+
if (layer === "team" && project !== void 0 && isValidWriteProjectSegment(project)) {
|
|
976
|
+
return join5(base, STORE_PROJECTS_DIR, project);
|
|
977
|
+
}
|
|
978
|
+
return base;
|
|
960
979
|
}
|
|
961
980
|
function resolveWriteScopeMeta(layer, projectRoot, semanticScope) {
|
|
962
981
|
const input = buildStoreResolveInput(projectRoot);
|
|
@@ -998,7 +1017,6 @@ import {
|
|
|
998
1017
|
deriveAgentsMetaStableId,
|
|
999
1018
|
isKnowledgeStableId,
|
|
1000
1019
|
KnowledgeTypeSchema,
|
|
1001
|
-
LayerSchema,
|
|
1002
1020
|
MaturitySchema,
|
|
1003
1021
|
parseKnowledgeId,
|
|
1004
1022
|
StableIdSchema
|
|
@@ -1081,8 +1099,7 @@ function extractRuleDescription(source) {
|
|
|
1081
1099
|
id: knowledge?.id,
|
|
1082
1100
|
knowledge_type: knowledge?.knowledge_type,
|
|
1083
1101
|
maturity: knowledge?.maturity,
|
|
1084
|
-
|
|
1085
|
-
layer_reason: knowledge?.layer_reason,
|
|
1102
|
+
// W4/Track1 (D1): no `knowledge_layer` — layer derives from the id prefix.
|
|
1086
1103
|
created_at: knowledge?.created_at,
|
|
1087
1104
|
tags: knowledge?.tags,
|
|
1088
1105
|
// v2.0-rc.5 (C1): default-safe values when there is no frontmatter at all;
|
|
@@ -1107,12 +1124,10 @@ function extractDescriptionFromFrontmatter(frontmatter) {
|
|
|
1107
1124
|
tech_stack: extractInlineArray(frontmatter, "tech_stack"),
|
|
1108
1125
|
impact: extractInlineArray(frontmatter, "impact"),
|
|
1109
1126
|
must_read_if: extractScalar(frontmatter, "must_read_if") ?? summary,
|
|
1110
|
-
entities: extractInlineArray(frontmatter, "entities"),
|
|
1111
1127
|
id: knowledge.id,
|
|
1112
1128
|
knowledge_type: knowledge.knowledge_type,
|
|
1113
1129
|
maturity: knowledge.maturity,
|
|
1114
|
-
|
|
1115
|
-
layer_reason: knowledge.layer_reason,
|
|
1130
|
+
// W4/Track1 (D1): no `knowledge_layer` — layer derives from the id prefix.
|
|
1116
1131
|
created_at: knowledge.created_at,
|
|
1117
1132
|
tags: knowledge.tags,
|
|
1118
1133
|
relevance_scope: knowledge.relevance_scope,
|
|
@@ -1143,8 +1158,6 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
|
|
|
1143
1158
|
const rawId = extractScalar(frontmatter, "id");
|
|
1144
1159
|
const rawType = extractScalar(frontmatter, "type");
|
|
1145
1160
|
const rawMaturity = extractScalar(frontmatter, "maturity");
|
|
1146
|
-
const rawLayer = extractScalar(frontmatter, "layer");
|
|
1147
|
-
const rawLayerReason = extractScalar(frontmatter, "layer_reason");
|
|
1148
1161
|
const rawCreatedAt = extractScalar(frontmatter, "created_at");
|
|
1149
1162
|
let id;
|
|
1150
1163
|
if (rawId !== void 0) {
|
|
@@ -1181,16 +1194,6 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
|
|
|
1181
1194
|
maturity = parsed.data;
|
|
1182
1195
|
} else {
|
|
1183
1196
|
process.stderr.write(`[fabric] frontmatter: unknown maturity ${JSON.stringify(rawMaturity)}; skipping
|
|
1184
|
-
`);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
let knowledge_layer;
|
|
1188
|
-
if (rawLayer !== void 0) {
|
|
1189
|
-
const parsed = LayerSchema.safeParse(rawLayer);
|
|
1190
|
-
if (parsed.success) {
|
|
1191
|
-
knowledge_layer = parsed.data;
|
|
1192
|
-
} else {
|
|
1193
|
-
process.stderr.write(`[fabric] frontmatter: unknown layer ${JSON.stringify(rawLayer)}; skipping
|
|
1194
1197
|
`);
|
|
1195
1198
|
}
|
|
1196
1199
|
}
|
|
@@ -1203,23 +1206,12 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
|
|
|
1203
1206
|
`);
|
|
1204
1207
|
}
|
|
1205
1208
|
}
|
|
1206
|
-
if (id !== void 0 && knowledge_layer !== void 0) {
|
|
1207
|
-
const decoded = parseKnowledgeId(id);
|
|
1208
|
-
if (decoded !== null && decoded.layer !== knowledge_layer) {
|
|
1209
|
-
process.stderr.write(
|
|
1210
|
-
`[fabric] frontmatter: id ${id} encodes layer ${decoded.layer} but layer field says ${knowledge_layer}; dropping both
|
|
1211
|
-
`
|
|
1212
|
-
);
|
|
1213
|
-
id = void 0;
|
|
1214
|
-
knowledge_layer = void 0;
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
1209
|
const tags = extractInlineArray(frontmatter, "tags");
|
|
1218
1210
|
const rawRelevanceScope = extractScalar(frontmatter, "relevance_scope");
|
|
1219
1211
|
const relevance_scope = rawRelevanceScope === "narrow" || rawRelevanceScope === "broad" ? rawRelevanceScope : "broad";
|
|
1220
1212
|
const relevance_paths = extractInlineArray(frontmatter, "relevance_paths");
|
|
1221
1213
|
const rawRelated = extractInlineArray(frontmatter, "related");
|
|
1222
|
-
const sourceLayer =
|
|
1214
|
+
const sourceLayer = id !== void 0 ? parseKnowledgeId(id)?.layer ?? "team" : "team";
|
|
1223
1215
|
const related = rawRelated.filter((targetId) => {
|
|
1224
1216
|
if (isForbiddenCrossLayerEdge(sourceLayer, targetId)) {
|
|
1225
1217
|
process.stderr.write(
|
|
@@ -1234,8 +1226,6 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
|
|
|
1234
1226
|
id,
|
|
1235
1227
|
knowledge_type,
|
|
1236
1228
|
maturity,
|
|
1237
|
-
knowledge_layer,
|
|
1238
|
-
layer_reason: rawLayerReason,
|
|
1239
1229
|
created_at,
|
|
1240
1230
|
tags: tags.length > 0 ? tags : void 0,
|
|
1241
1231
|
relevance_scope,
|
|
@@ -1286,7 +1276,13 @@ async function readSetFingerprint(refs) {
|
|
|
1286
1276
|
return parts.sort().join("\n");
|
|
1287
1277
|
}
|
|
1288
1278
|
var SEMANTIC_SCOPE_LINE = /^semantic_scope:\s*"?([^"\n]+?)"?\s*$/mu;
|
|
1289
|
-
function readSemanticScope(source, layer) {
|
|
1279
|
+
function readSemanticScope(source, layer, project) {
|
|
1280
|
+
if (layer === "personal") {
|
|
1281
|
+
return "personal";
|
|
1282
|
+
}
|
|
1283
|
+
if (typeof project === "string" && project.length > 0) {
|
|
1284
|
+
return `project:${project}`;
|
|
1285
|
+
}
|
|
1290
1286
|
const match = SEMANTIC_SCOPE_LINE.exec(source);
|
|
1291
1287
|
return match?.[1] ?? layer;
|
|
1292
1288
|
}
|
|
@@ -1365,7 +1361,7 @@ async function walkReadSetStoresUncached(snapshot) {
|
|
|
1365
1361
|
type: ref.type,
|
|
1366
1362
|
alias: ref.alias,
|
|
1367
1363
|
layer,
|
|
1368
|
-
semanticScope: readSemanticScope(source, layer),
|
|
1364
|
+
semanticScope: readSemanticScope(source, layer, ref.project),
|
|
1369
1365
|
source
|
|
1370
1366
|
};
|
|
1371
1367
|
}));
|
|
@@ -1383,7 +1379,9 @@ async function buildCrossStoreRawItems(projectRoot) {
|
|
|
1383
1379
|
stable_id: entry.qualifiedId,
|
|
1384
1380
|
description: {
|
|
1385
1381
|
...baseDescription,
|
|
1386
|
-
|
|
1382
|
+
// W4/Track1 (D1): no `knowledge_layer` backfill — a candidate's layer is
|
|
1383
|
+
// derived from its stable_id prefix (layerFromStableId in plan-context),
|
|
1384
|
+
// the single source of truth (KT-DEC-0004).
|
|
1387
1385
|
semantic_scope: entry.semanticScope
|
|
1388
1386
|
}
|
|
1389
1387
|
});
|
|
@@ -2893,11 +2891,12 @@ var LEDGER_DUAL_WRITE_METRIC_NAMES = {
|
|
|
2893
2891
|
};
|
|
2894
2892
|
|
|
2895
2893
|
// src/services/plan-context.ts
|
|
2896
|
-
import { mkdir as
|
|
2894
|
+
import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
|
|
2897
2895
|
import { join as join10 } from "path";
|
|
2898
2896
|
|
|
2899
2897
|
// src/services/vector-retrieval.ts
|
|
2900
2898
|
import { mkdirSync } from "fs";
|
|
2899
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
2901
2900
|
import { createRequire } from "module";
|
|
2902
2901
|
import { join as join9 } from "path";
|
|
2903
2902
|
import { resolveGlobalRoot as resolveGlobalRoot3 } from "@fenglimg/fabric-shared";
|
|
@@ -3008,11 +3007,63 @@ function cacheDocVector(text, vector) {
|
|
|
3008
3007
|
docVectorCache.delete(lru);
|
|
3009
3008
|
}
|
|
3010
3009
|
}
|
|
3011
|
-
|
|
3010
|
+
var VECTOR_CACHE_VERSION = 1;
|
|
3011
|
+
var VECTOR_CACHE_DIR = ".fabric/cache/vectors";
|
|
3012
|
+
function vectorCachePath(projectRoot, revision) {
|
|
3013
|
+
const safe = revision.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
3014
|
+
return join9(projectRoot, VECTOR_CACHE_DIR, `${safe}.json`);
|
|
3015
|
+
}
|
|
3016
|
+
async function loadVectorCacheFromDisk(ctx) {
|
|
3017
|
+
try {
|
|
3018
|
+
const raw = await readFile6(vectorCachePath(ctx.projectRoot, ctx.corpusRevision), "utf8");
|
|
3019
|
+
const parsed = JSON.parse(raw);
|
|
3020
|
+
if (parsed.version !== VECTOR_CACHE_VERSION) return null;
|
|
3021
|
+
if (parsed.embedding_model !== ctx.embeddingModel) return null;
|
|
3022
|
+
if (parsed.corpus_revision !== ctx.corpusRevision) return null;
|
|
3023
|
+
if (!Array.isArray(parsed.vectors) || parsed.vectors.length === 0) return null;
|
|
3024
|
+
const dimension = parsed.dimension;
|
|
3025
|
+
if (!Number.isInteger(dimension) || dimension <= 0) return null;
|
|
3026
|
+
const out = /* @__PURE__ */ new Map();
|
|
3027
|
+
for (const entry of parsed.vectors) {
|
|
3028
|
+
if (!Array.isArray(entry) || entry.length !== 2) return null;
|
|
3029
|
+
const [text, vector] = entry;
|
|
3030
|
+
if (typeof text !== "string" || !Array.isArray(vector)) return null;
|
|
3031
|
+
if (vector.length !== dimension) return null;
|
|
3032
|
+
out.set(text, vector);
|
|
3033
|
+
}
|
|
3034
|
+
return out;
|
|
3035
|
+
} catch {
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
async function saveVectorCacheToDisk(ctx, vectors, dimension) {
|
|
3040
|
+
try {
|
|
3041
|
+
const payload = {
|
|
3042
|
+
version: VECTOR_CACHE_VERSION,
|
|
3043
|
+
embedding_model: ctx.embeddingModel,
|
|
3044
|
+
dimension,
|
|
3045
|
+
corpus_revision: ctx.corpusRevision,
|
|
3046
|
+
vectors
|
|
3047
|
+
};
|
|
3048
|
+
const path = vectorCachePath(ctx.projectRoot, ctx.corpusRevision);
|
|
3049
|
+
await mkdir4(join9(ctx.projectRoot, VECTOR_CACHE_DIR), { recursive: true });
|
|
3050
|
+
await writeFile2(path, JSON.stringify(payload), "utf8");
|
|
3051
|
+
} catch {
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
async function buildVectorScores(embedder, queryText, items, cache2) {
|
|
3012
3055
|
if (embedder === null || queryText.trim().length === 0 || items.length === 0) {
|
|
3013
3056
|
return null;
|
|
3014
3057
|
}
|
|
3015
3058
|
try {
|
|
3059
|
+
if (cache2 !== void 0) {
|
|
3060
|
+
const fromDisk = await loadVectorCacheFromDisk(cache2);
|
|
3061
|
+
if (fromDisk !== null) {
|
|
3062
|
+
for (const [text, vector] of fromDisk) {
|
|
3063
|
+
cacheDocVector(text, vector);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3016
3067
|
const missTexts = [];
|
|
3017
3068
|
for (const item of items) {
|
|
3018
3069
|
if (!docVectorCache.has(item.text)) {
|
|
@@ -3028,6 +3079,24 @@ async function buildVectorScores(embedder, queryText, items) {
|
|
|
3028
3079
|
for (let m = 0; m < missTexts.length; m += 1) {
|
|
3029
3080
|
cacheDocVector(missTexts[m], embedded[m + 1]);
|
|
3030
3081
|
}
|
|
3082
|
+
const staleTexts = [];
|
|
3083
|
+
for (const item of items) {
|
|
3084
|
+
const cached = docVectorCache.get(item.text);
|
|
3085
|
+
if (cached !== void 0 && cached.length !== queryVec.length) {
|
|
3086
|
+
docVectorCache.delete(item.text);
|
|
3087
|
+
staleTexts.push(item.text);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
if (staleTexts.length > 0) {
|
|
3091
|
+
const reEmbedded = await embedder.embed(staleTexts);
|
|
3092
|
+
if (reEmbedded.length !== staleTexts.length) {
|
|
3093
|
+
return null;
|
|
3094
|
+
}
|
|
3095
|
+
for (let s = 0; s < staleTexts.length; s += 1) {
|
|
3096
|
+
cacheDocVector(staleTexts[s], reEmbedded[s]);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
const embeddedNewDoc = missTexts.length > 0 || staleTexts.length > 0;
|
|
3031
3100
|
const scores = /* @__PURE__ */ new Map();
|
|
3032
3101
|
for (const item of items) {
|
|
3033
3102
|
const docVec = docVectorCache.get(item.text);
|
|
@@ -3036,6 +3105,20 @@ async function buildVectorScores(embedder, queryText, items) {
|
|
|
3036
3105
|
}
|
|
3037
3106
|
scores.set(item.stable_id, cosineSimilarity(queryVec, docVec));
|
|
3038
3107
|
}
|
|
3108
|
+
if (cache2 !== void 0 && embeddedNewDoc) {
|
|
3109
|
+
const snapshot = [];
|
|
3110
|
+
for (const item of items) {
|
|
3111
|
+
const vec = docVectorCache.get(item.text);
|
|
3112
|
+
if (vec !== void 0) {
|
|
3113
|
+
snapshot.push([item.text, vec]);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
const dimension = snapshot.length > 0 ? snapshot[0][1].length : 0;
|
|
3117
|
+
const uniform = dimension > 0 && snapshot.every(([, v]) => v.length === dimension);
|
|
3118
|
+
if (uniform) {
|
|
3119
|
+
await saveVectorCacheToDisk(cache2, snapshot, dimension);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3039
3122
|
return scores;
|
|
3040
3123
|
} catch {
|
|
3041
3124
|
return null;
|
|
@@ -3043,6 +3126,11 @@ async function buildVectorScores(embedder, queryText, items) {
|
|
|
3043
3126
|
}
|
|
3044
3127
|
|
|
3045
3128
|
// src/services/plan-context.ts
|
|
3129
|
+
function layerFromStableId(qualifiedId) {
|
|
3130
|
+
const colon = qualifiedId.lastIndexOf(":");
|
|
3131
|
+
const localId = colon === -1 ? qualifiedId : qualifiedId.slice(colon + 1);
|
|
3132
|
+
return localId.startsWith("KP-") ? "personal" : "team";
|
|
3133
|
+
}
|
|
3046
3134
|
var SELECTION_TOKEN_TTL_DEFAULT_MS = 30 * 60 * 1e3;
|
|
3047
3135
|
var selectionTokenCache = /* @__PURE__ */ new Map();
|
|
3048
3136
|
var SELECTION_TOKEN_CACHE_MAX = 1e3;
|
|
@@ -3099,7 +3187,7 @@ async function planContext(projectRoot, input) {
|
|
|
3099
3187
|
const storeRawItems = await buildCrossStoreRawItems(projectRoot).catch(() => []);
|
|
3100
3188
|
const { rawItems: allRawItems, suppressedStableIds } = partitionEmptyShells(storeRawItems);
|
|
3101
3189
|
const effectiveLayerFilter = input.layer_filter ?? readDefaultLayerFilter(projectRoot);
|
|
3102
|
-
const rawItems = effectiveLayerFilter === "both" ? allRawItems : allRawItems.filter((item) => item.
|
|
3190
|
+
const rawItems = effectiveLayerFilter === "both" ? allRawItems : allRawItems.filter((item) => layerFromStableId(item.stable_id) === effectiveLayerFilter);
|
|
3103
3191
|
const scoringContext = await buildScoringContext(projectRoot, revision, rawItems, {
|
|
3104
3192
|
queryText,
|
|
3105
3193
|
targetPaths: input.target_paths ?? dedupePaths(input.paths)
|
|
@@ -3320,7 +3408,7 @@ function bm25CachePath(projectRoot, revision) {
|
|
|
3320
3408
|
}
|
|
3321
3409
|
async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
3322
3410
|
try {
|
|
3323
|
-
const raw = await
|
|
3411
|
+
const raw = await readFile7(bm25CachePath(projectRoot, revision), "utf8");
|
|
3324
3412
|
const parsed = JSON.parse(raw);
|
|
3325
3413
|
if (parsed.version !== 1) return null;
|
|
3326
3414
|
return rehydrateBm25Model(parsed);
|
|
@@ -3331,8 +3419,8 @@ async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
|
3331
3419
|
async function saveBm25ModelToDisk(projectRoot, revision, model) {
|
|
3332
3420
|
try {
|
|
3333
3421
|
const path = bm25CachePath(projectRoot, revision);
|
|
3334
|
-
await
|
|
3335
|
-
await
|
|
3422
|
+
await mkdir5(join10(projectRoot, BM25_CACHE_DIR), { recursive: true });
|
|
3423
|
+
await writeFile3(path, JSON.stringify(serializeBm25Model(model)), "utf8");
|
|
3336
3424
|
} catch {
|
|
3337
3425
|
}
|
|
3338
3426
|
}
|
|
@@ -3373,7 +3461,7 @@ function buildScopeRankMap(items, projectRoot) {
|
|
|
3373
3461
|
const colon = it.stable_id.indexOf(":");
|
|
3374
3462
|
const alias = colon === -1 ? "" : it.stable_id.slice(0, colon);
|
|
3375
3463
|
const localId = colon === -1 ? it.stable_id : it.stable_id.slice(colon + 1);
|
|
3376
|
-
const semanticScope = it.description.semantic_scope ?? it.
|
|
3464
|
+
const semanticScope = it.description.semantic_scope ?? layerFromStableId(it.stable_id);
|
|
3377
3465
|
return {
|
|
3378
3466
|
global_ref: it.stable_id,
|
|
3379
3467
|
store_uuid: aliasToUuid.get(alias) ?? alias,
|
|
@@ -3423,7 +3511,8 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
|
|
|
3423
3511
|
rawItems.map((item) => ({
|
|
3424
3512
|
stable_id: item.stable_id,
|
|
3425
3513
|
text: docTexts.get(item.stable_id) ?? documentTextForItem(item.description)
|
|
3426
|
-
}))
|
|
3514
|
+
})),
|
|
3515
|
+
{ projectRoot, corpusRevision: revision, embeddingModel: embedConfig.model }
|
|
3427
3516
|
);
|
|
3428
3517
|
if (vectorScores !== null) {
|
|
3429
3518
|
scoringContext.vectorScores = vectorScores;
|
|
@@ -3531,16 +3620,13 @@ function documentTextForItem(description) {
|
|
|
3531
3620
|
...description.intent_clues,
|
|
3532
3621
|
...description.tech_stack,
|
|
3533
3622
|
...description.impact,
|
|
3534
|
-
...description.entities ?? [],
|
|
3535
3623
|
...description.tags ?? []
|
|
3536
3624
|
].join(" ");
|
|
3537
3625
|
}
|
|
3538
3626
|
function documentFieldsForItem(description) {
|
|
3539
3627
|
return {
|
|
3540
3628
|
title: tokenize2(description.summary),
|
|
3541
|
-
tags: tokenize2(
|
|
3542
|
-
[...description.tags ?? [], ...description.tech_stack, ...description.entities ?? []].join(" ")
|
|
3543
|
-
),
|
|
3629
|
+
tags: tokenize2([...description.tags ?? [], ...description.tech_stack].join(" ")),
|
|
3544
3630
|
summary: tokenize2([description.must_read_if, ...description.intent_clues].join(" ")),
|
|
3545
3631
|
body: tokenize2(description.impact.join(" "))
|
|
3546
3632
|
};
|
|
@@ -4116,7 +4202,7 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
|
|
|
4116
4202
|
// src/services/review.ts
|
|
4117
4203
|
import { execFileSync } from "child_process";
|
|
4118
4204
|
import { existsSync as existsSync5 } from "fs";
|
|
4119
|
-
import { readFile as
|
|
4205
|
+
import { readFile as readFile9, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
4120
4206
|
import { homedir } from "os";
|
|
4121
4207
|
import { basename, isAbsolute, join as join12, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
4122
4208
|
|
|
@@ -4154,11 +4240,11 @@ async function hasUnresolvedDismissal(projectRoot, id) {
|
|
|
4154
4240
|
}
|
|
4155
4241
|
|
|
4156
4242
|
// src/services/review.ts
|
|
4157
|
-
import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@fenglimg/fabric-shared";
|
|
4243
|
+
import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2, loadProjectConfig as loadProjectConfig3 } from "@fenglimg/fabric-shared";
|
|
4158
4244
|
|
|
4159
4245
|
// src/services/pending-dedupe.ts
|
|
4160
4246
|
import { existsSync as existsSync4 } from "fs";
|
|
4161
|
-
import { readdir, readFile as
|
|
4247
|
+
import { readdir, readFile as readFile8, unlink } from "fs/promises";
|
|
4162
4248
|
import { join as join11 } from "path";
|
|
4163
4249
|
var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
|
|
4164
4250
|
var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
|
|
@@ -4256,7 +4342,7 @@ async function mergePendingTwins(projectRoot) {
|
|
|
4256
4342
|
const abs = join11(dir, name);
|
|
4257
4343
|
let content;
|
|
4258
4344
|
try {
|
|
4259
|
-
content = await
|
|
4345
|
+
content = await readFile8(abs, "utf8");
|
|
4260
4346
|
} catch {
|
|
4261
4347
|
continue;
|
|
4262
4348
|
}
|
|
@@ -4451,7 +4537,7 @@ async function listPending(projectRoot, filters) {
|
|
|
4451
4537
|
const absolutePath = join12(dir, name);
|
|
4452
4538
|
let content;
|
|
4453
4539
|
try {
|
|
4454
|
-
content = await
|
|
4540
|
+
content = await readFile9(absolutePath, "utf8");
|
|
4455
4541
|
} catch {
|
|
4456
4542
|
continue;
|
|
4457
4543
|
}
|
|
@@ -4561,7 +4647,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4561
4647
|
let targetAbs;
|
|
4562
4648
|
let writtenTarget = false;
|
|
4563
4649
|
try {
|
|
4564
|
-
const content = await
|
|
4650
|
+
const content = await readFile9(sourceAbs, "utf8");
|
|
4565
4651
|
const fm = parseFrontmatter(content);
|
|
4566
4652
|
const pluralType = fm.type;
|
|
4567
4653
|
if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
|
|
@@ -4575,7 +4661,12 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4575
4661
|
);
|
|
4576
4662
|
allocatedId = stableId;
|
|
4577
4663
|
const newFilename = `${stableId}--${slug}.md`;
|
|
4578
|
-
|
|
4664
|
+
const promoteProject = layer === "team" ? loadProjectConfig3(projectRoot)?.active_project : void 0;
|
|
4665
|
+
targetAbs = join12(
|
|
4666
|
+
resolveStoreCanonicalBase(layer, projectRoot, promoteProject),
|
|
4667
|
+
pluralType,
|
|
4668
|
+
newFilename
|
|
4669
|
+
);
|
|
4579
4670
|
await ensureParentDirectory(targetAbs);
|
|
4580
4671
|
const rewritten = rewriteFrontmatterMerge(
|
|
4581
4672
|
rewriteFrontmatterForPromote(content, stableId),
|
|
@@ -4633,7 +4724,7 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
|
|
|
4633
4724
|
try {
|
|
4634
4725
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4635
4726
|
if (existsSync5(sandboxed.abs)) {
|
|
4636
|
-
const content = await
|
|
4727
|
+
const content = await readFile9(sandboxed.abs, "utf8");
|
|
4637
4728
|
const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
|
|
4638
4729
|
const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
|
|
4639
4730
|
if (rejectedAbs !== null) {
|
|
@@ -4660,7 +4751,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
|
|
|
4660
4751
|
if (target === null) {
|
|
4661
4752
|
throw new Error(`modify target not found: ${pendingPath}`);
|
|
4662
4753
|
}
|
|
4663
|
-
const content = await
|
|
4754
|
+
const content = await readFile9(target.absPath, "utf8");
|
|
4664
4755
|
const fm = parseFrontmatter(content);
|
|
4665
4756
|
const currentLayer = fm.layer ?? "team";
|
|
4666
4757
|
if (fm.maturity === "verified" && changes.maturity === "proven" && fm.id !== void 0) {
|
|
@@ -4762,8 +4853,9 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
4762
4853
|
pluralType,
|
|
4763
4854
|
resolveWriteTargetStoreDir(toLayer, projectRoot)
|
|
4764
4855
|
);
|
|
4856
|
+
const flipProject = toLayer === "team" ? loadProjectConfig3(projectRoot)?.active_project : void 0;
|
|
4765
4857
|
const toAbs = join12(
|
|
4766
|
-
resolveStoreCanonicalBase(toLayer, projectRoot),
|
|
4858
|
+
resolveStoreCanonicalBase(toLayer, projectRoot, flipProject),
|
|
4767
4859
|
pluralType,
|
|
4768
4860
|
`${newStableId}--${slug}.md`
|
|
4769
4861
|
);
|
|
@@ -4785,20 +4877,22 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
4785
4877
|
{ ...effectivePatch, last_review_confirmed_at: (/* @__PURE__ */ new Date()).toISOString() },
|
|
4786
4878
|
{ id: newStableId }
|
|
4787
4879
|
);
|
|
4788
|
-
|
|
4880
|
+
let moved = false;
|
|
4789
4881
|
if (target.isInProjectTree) {
|
|
4790
4882
|
const relSource = relative(projectRoot, target.absPath);
|
|
4883
|
+
const relDest = relative(projectRoot, toAbs);
|
|
4791
4884
|
try {
|
|
4792
|
-
execFileSync("git", ["
|
|
4885
|
+
execFileSync("git", ["mv", "-f", relSource, relDest], {
|
|
4793
4886
|
cwd: projectRoot,
|
|
4794
4887
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4795
4888
|
});
|
|
4889
|
+
moved = true;
|
|
4796
4890
|
} catch {
|
|
4797
|
-
|
|
4798
|
-
await unlink2(target.absPath);
|
|
4799
|
-
}
|
|
4891
|
+
moved = false;
|
|
4800
4892
|
}
|
|
4801
|
-
}
|
|
4893
|
+
}
|
|
4894
|
+
await atomicWriteText(toAbs, rewritten);
|
|
4895
|
+
if (!moved && existsSync5(target.absPath) && target.absPath !== toAbs) {
|
|
4802
4896
|
await unlink2(target.absPath);
|
|
4803
4897
|
}
|
|
4804
4898
|
const flipReason = `layer_flip:${priorStableId ?? "<unassigned>"}->${newStableId}`;
|
|
@@ -4892,7 +4986,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4892
4986
|
}
|
|
4893
4987
|
let content;
|
|
4894
4988
|
try {
|
|
4895
|
-
content = await
|
|
4989
|
+
content = await readFile9(absolutePath, "utf8");
|
|
4896
4990
|
searchEntryIndexContentReads += 1;
|
|
4897
4991
|
} catch {
|
|
4898
4992
|
directoryCache.files.delete(absolutePath);
|
|
@@ -4942,7 +5036,8 @@ function pendingEntryToRankerItem(indexed) {
|
|
|
4942
5036
|
...fm.id !== void 0 ? { id: fm.id } : {},
|
|
4943
5037
|
...fm.type !== void 0 ? { knowledge_type: fm.type } : {},
|
|
4944
5038
|
maturity: fm.maturity ?? "draft",
|
|
4945
|
-
|
|
5039
|
+
// W4/Track1 (D1): no `knowledge_layer` field — layer is derived from the
|
|
5040
|
+
// stable_id prefix (KT-DEC-0004), never carried on the description.
|
|
4946
5041
|
...fm.semantic_scope !== void 0 ? { semantic_scope: fm.semantic_scope } : {},
|
|
4947
5042
|
...fm.created_at !== void 0 ? { created_at: fm.created_at } : {},
|
|
4948
5043
|
tags: fm.tags ?? [],
|
|
@@ -5061,7 +5156,7 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
|
5061
5156
|
try {
|
|
5062
5157
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
5063
5158
|
if (existsSync5(sandboxed.abs)) {
|
|
5064
|
-
const content = await
|
|
5159
|
+
const content = await readFile9(sandboxed.abs, "utf8");
|
|
5065
5160
|
stableId = parseFrontmatter(content).id;
|
|
5066
5161
|
const patch = {
|
|
5067
5162
|
status: "deferred",
|
|
@@ -5402,9 +5497,9 @@ function registerPending(server, tracker) {
|
|
|
5402
5497
|
}
|
|
5403
5498
|
|
|
5404
5499
|
// src/services/doctor.ts
|
|
5405
|
-
import { access as access4, readFile as
|
|
5500
|
+
import { access as access4, readFile as readFile19, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile5 } from "fs/promises";
|
|
5406
5501
|
import { constants as constants2 } from "fs";
|
|
5407
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5502
|
+
import { isAbsolute as isAbsolute2, join as join24, posix as posix5, resolve as resolve3 } from "path";
|
|
5408
5503
|
import {
|
|
5409
5504
|
createTranslator,
|
|
5410
5505
|
forensicReportSchema,
|
|
@@ -5618,7 +5713,7 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
|
|
|
5618
5713
|
}
|
|
5619
5714
|
|
|
5620
5715
|
// src/services/doctor-stable-id-collision.ts
|
|
5621
|
-
import { readFile as
|
|
5716
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
5622
5717
|
import { basename as basename2, join as join13 } from "path";
|
|
5623
5718
|
import {
|
|
5624
5719
|
buildStoreResolveInput as buildStoreResolveInput4,
|
|
@@ -5665,7 +5760,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
|
|
|
5665
5760
|
for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
|
|
5666
5761
|
let source;
|
|
5667
5762
|
try {
|
|
5668
|
-
source = await
|
|
5763
|
+
source = await readFile10(ref.file, "utf8");
|
|
5669
5764
|
} catch {
|
|
5670
5765
|
continue;
|
|
5671
5766
|
}
|
|
@@ -5986,7 +6081,7 @@ function createNarrowNoPathsCheck(t, inspection) {
|
|
|
5986
6081
|
}
|
|
5987
6082
|
|
|
5988
6083
|
// src/services/doctor-broad-index.ts
|
|
5989
|
-
import { readFile as
|
|
6084
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5990
6085
|
import { join as join15 } from "path";
|
|
5991
6086
|
var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
|
|
5992
6087
|
var BROAD_INDEX_BACKSTOP_MIN = 20;
|
|
@@ -5995,7 +6090,7 @@ var BROAD_INDEX_DRIFT_RATIO = 0.8;
|
|
|
5995
6090
|
async function readBroadIndexBackstop(projectRoot) {
|
|
5996
6091
|
const configPath = join15(projectRoot, ".fabric", "fabric-config.json");
|
|
5997
6092
|
try {
|
|
5998
|
-
const raw = await
|
|
6093
|
+
const raw = await readFile11(configPath, "utf8");
|
|
5999
6094
|
const parsed = JSON.parse(raw);
|
|
6000
6095
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
6001
6096
|
const v = parsed.broad_index_backstop;
|
|
@@ -6312,7 +6407,7 @@ function createBroadReviewRecheckCheck(t, inspection) {
|
|
|
6312
6407
|
}
|
|
6313
6408
|
|
|
6314
6409
|
// src/services/doctor-scope-lint.ts
|
|
6315
|
-
import { readFile as
|
|
6410
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
6316
6411
|
import { join as join16 } from "path";
|
|
6317
6412
|
import {
|
|
6318
6413
|
buildStoreResolveInput as buildStoreResolveInput5,
|
|
@@ -6380,7 +6475,7 @@ async function lintStoreScopes(projectRoot) {
|
|
|
6380
6475
|
}
|
|
6381
6476
|
let source;
|
|
6382
6477
|
try {
|
|
6383
|
-
source = await
|
|
6478
|
+
source = await readFile12(ref.file, "utf8");
|
|
6384
6479
|
} catch {
|
|
6385
6480
|
continue;
|
|
6386
6481
|
}
|
|
@@ -6463,9 +6558,9 @@ function createScopeLintCheck(t, violations) {
|
|
|
6463
6558
|
}
|
|
6464
6559
|
|
|
6465
6560
|
// src/services/doctor-unbound-project.ts
|
|
6466
|
-
import { loadProjectConfig as
|
|
6561
|
+
import { loadProjectConfig as loadProjectConfig4 } from "@fenglimg/fabric-shared";
|
|
6467
6562
|
function detectUnboundProject(projectRoot) {
|
|
6468
|
-
const config =
|
|
6563
|
+
const config = loadProjectConfig4(projectRoot);
|
|
6469
6564
|
const alias = config?.active_write_store;
|
|
6470
6565
|
if (typeof alias !== "string" || alias.length === 0) {
|
|
6471
6566
|
return null;
|
|
@@ -6513,7 +6608,7 @@ import {
|
|
|
6513
6608
|
readStoreCounters,
|
|
6514
6609
|
reconcileStoreCounters,
|
|
6515
6610
|
resolveGlobalRoot as resolveGlobalRoot6,
|
|
6516
|
-
STORE_KNOWLEDGE_TYPE_DIRS,
|
|
6611
|
+
STORE_KNOWLEDGE_TYPE_DIRS as STORE_KNOWLEDGE_TYPE_DIRS2,
|
|
6517
6612
|
STORE_LAYOUT as STORE_LAYOUT2,
|
|
6518
6613
|
storeRelativePathForMount as storeRelativePathForMount5
|
|
6519
6614
|
} from "@fenglimg/fabric-shared";
|
|
@@ -6557,7 +6652,7 @@ function readEntryId(file) {
|
|
|
6557
6652
|
}
|
|
6558
6653
|
function computeStoreDiskMax(storeDir) {
|
|
6559
6654
|
const max = defaultAgentsMetaCounters();
|
|
6560
|
-
for (const type of
|
|
6655
|
+
for (const type of STORE_KNOWLEDGE_TYPE_DIRS2) {
|
|
6561
6656
|
const dir = join17(storeDir, STORE_LAYOUT2.knowledgeDir, type);
|
|
6562
6657
|
if (!existsSync7(dir)) {
|
|
6563
6658
|
continue;
|
|
@@ -6752,6 +6847,195 @@ function createStoreOrphanCheck(t, orphans) {
|
|
|
6752
6847
|
};
|
|
6753
6848
|
}
|
|
6754
6849
|
|
|
6850
|
+
// src/services/doctor-project-registry-drift.ts
|
|
6851
|
+
import { readdir as readdir3, rmdir } from "fs/promises";
|
|
6852
|
+
import { join as join19 } from "path";
|
|
6853
|
+
import {
|
|
6854
|
+
addStoreProject,
|
|
6855
|
+
buildStoreResolveInput as buildStoreResolveInput7,
|
|
6856
|
+
createStoreResolver as createStoreResolver7,
|
|
6857
|
+
readStoreProjects as readStoreProjects2,
|
|
6858
|
+
resolveGlobalRoot as resolveGlobalRoot8,
|
|
6859
|
+
STORE_KNOWLEDGE_TYPE_DIRS as STORE_KNOWLEDGE_TYPE_DIRS3,
|
|
6860
|
+
STORE_LAYOUT as STORE_LAYOUT3,
|
|
6861
|
+
STORE_PROJECT_ID_PATTERN as STORE_PROJECT_ID_PATTERN2,
|
|
6862
|
+
storeRelativePathForMount as storeRelativePathForMount6
|
|
6863
|
+
} from "@fenglimg/fabric-shared";
|
|
6864
|
+
var PROJECTS_DIR = "projects";
|
|
6865
|
+
var EMPTY_INSPECTION = { findings: [] };
|
|
6866
|
+
function resolveDriftStores(projectRoot) {
|
|
6867
|
+
const input = buildStoreResolveInput7(projectRoot);
|
|
6868
|
+
if (input === null) {
|
|
6869
|
+
return [];
|
|
6870
|
+
}
|
|
6871
|
+
const readSet = createStoreResolver7().resolveReadSet(input);
|
|
6872
|
+
if (readSet.stores.length === 0) {
|
|
6873
|
+
return [];
|
|
6874
|
+
}
|
|
6875
|
+
const globalRoot = resolveGlobalRoot8();
|
|
6876
|
+
return readSet.stores.map((entry) => {
|
|
6877
|
+
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
6878
|
+
return {
|
|
6879
|
+
uuid: entry.store_uuid,
|
|
6880
|
+
alias: entry.alias,
|
|
6881
|
+
dir: join19(globalRoot, storeRelativePathForMount6(mounted ?? { store_uuid: entry.store_uuid }))
|
|
6882
|
+
};
|
|
6883
|
+
});
|
|
6884
|
+
}
|
|
6885
|
+
async function listDir2(dir) {
|
|
6886
|
+
try {
|
|
6887
|
+
return await readdir3(dir);
|
|
6888
|
+
} catch {
|
|
6889
|
+
return [];
|
|
6890
|
+
}
|
|
6891
|
+
}
|
|
6892
|
+
async function listProjectFolders(storeDir) {
|
|
6893
|
+
const projectsRoot = join19(storeDir, STORE_LAYOUT3.knowledgeDir, PROJECTS_DIR);
|
|
6894
|
+
const reserved = /* @__PURE__ */ new Set([PROJECTS_DIR, ...STORE_KNOWLEDGE_TYPE_DIRS3]);
|
|
6895
|
+
return (await listDir2(projectsRoot)).filter((id) => STORE_PROJECT_ID_PATTERN2.test(id) && !reserved.has(id)).sort();
|
|
6896
|
+
}
|
|
6897
|
+
async function folderHasEntries(storeDir, projectId) {
|
|
6898
|
+
const base = join19(storeDir, STORE_LAYOUT3.knowledgeDir, PROJECTS_DIR, projectId);
|
|
6899
|
+
for (const type of STORE_KNOWLEDGE_TYPE_DIRS3) {
|
|
6900
|
+
const names = await listDir2(join19(base, type));
|
|
6901
|
+
if (names.some((name) => name.endsWith(".md"))) {
|
|
6902
|
+
return true;
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
return false;
|
|
6906
|
+
}
|
|
6907
|
+
async function inspectProjectRegistryDrift(projectRoot) {
|
|
6908
|
+
const stores = resolveDriftStores(projectRoot);
|
|
6909
|
+
if (stores.length === 0) {
|
|
6910
|
+
return EMPTY_INSPECTION;
|
|
6911
|
+
}
|
|
6912
|
+
const findings = [];
|
|
6913
|
+
for (const store of stores) {
|
|
6914
|
+
const registered = new Set((await readStoreProjects2(store.dir)).map((p) => p.id));
|
|
6915
|
+
const folders = await listProjectFolders(store.dir);
|
|
6916
|
+
const folderSet = new Set(folders);
|
|
6917
|
+
const base = { store_alias: store.alias, store_uuid: store.uuid, store_dir: store.dir };
|
|
6918
|
+
for (const projectId of folders) {
|
|
6919
|
+
if (registered.has(projectId)) {
|
|
6920
|
+
if (!await folderHasEntries(store.dir, projectId)) {
|
|
6921
|
+
findings.push({ ...base, project_id: projectId, kind: "empty_folder" });
|
|
6922
|
+
}
|
|
6923
|
+
continue;
|
|
6924
|
+
}
|
|
6925
|
+
const kind = await folderHasEntries(store.dir, projectId) ? "unregistered_write" : "orphan_folder";
|
|
6926
|
+
findings.push({ ...base, project_id: projectId, kind });
|
|
6927
|
+
}
|
|
6928
|
+
void folderSet;
|
|
6929
|
+
}
|
|
6930
|
+
findings.sort(
|
|
6931
|
+
(a, b) => a.store_alias.localeCompare(b.store_alias) || a.project_id.localeCompare(b.project_id)
|
|
6932
|
+
);
|
|
6933
|
+
return { findings };
|
|
6934
|
+
}
|
|
6935
|
+
async function fixProjectRegistryDrift(projectRoot) {
|
|
6936
|
+
const registered = [];
|
|
6937
|
+
const pruned = [];
|
|
6938
|
+
const { findings } = await inspectProjectRegistryDrift(projectRoot);
|
|
6939
|
+
for (const finding of findings) {
|
|
6940
|
+
if (finding.kind === "empty_folder") {
|
|
6941
|
+
if (await folderHasEntries(finding.store_dir, finding.project_id)) {
|
|
6942
|
+
continue;
|
|
6943
|
+
}
|
|
6944
|
+
try {
|
|
6945
|
+
const base = join19(
|
|
6946
|
+
finding.store_dir,
|
|
6947
|
+
STORE_LAYOUT3.knowledgeDir,
|
|
6948
|
+
PROJECTS_DIR,
|
|
6949
|
+
finding.project_id
|
|
6950
|
+
);
|
|
6951
|
+
for (const type of STORE_KNOWLEDGE_TYPE_DIRS3) {
|
|
6952
|
+
await rmdir(join19(base, type)).catch(() => void 0);
|
|
6953
|
+
}
|
|
6954
|
+
await rmdir(base);
|
|
6955
|
+
pruned.push(finding);
|
|
6956
|
+
} catch {
|
|
6957
|
+
}
|
|
6958
|
+
continue;
|
|
6959
|
+
}
|
|
6960
|
+
try {
|
|
6961
|
+
await addStoreProject(finding.store_dir, {
|
|
6962
|
+
id: finding.project_id,
|
|
6963
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6964
|
+
});
|
|
6965
|
+
registered.push(finding);
|
|
6966
|
+
} catch {
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
return { registered, pruned };
|
|
6970
|
+
}
|
|
6971
|
+
function createProjectRegistryDriftCheck(t, inspection) {
|
|
6972
|
+
const { findings } = inspection;
|
|
6973
|
+
if (findings.length === 0) {
|
|
6974
|
+
return {
|
|
6975
|
+
name: t("doctor.check.project_registry_drift.name"),
|
|
6976
|
+
status: "ok",
|
|
6977
|
+
message: t("doctor.check.project_registry_drift.ok")
|
|
6978
|
+
};
|
|
6979
|
+
}
|
|
6980
|
+
const unregistered = findings.filter((f) => f.kind === "unregistered_write").length;
|
|
6981
|
+
const orphans = findings.filter((f) => f.kind === "orphan_folder").length;
|
|
6982
|
+
const empties = findings.filter((f) => f.kind === "empty_folder").length;
|
|
6983
|
+
const breakdown = [
|
|
6984
|
+
unregistered > 0 ? `${unregistered} unregistered-write` : null,
|
|
6985
|
+
orphans > 0 ? `${orphans} orphan-folder` : null,
|
|
6986
|
+
empties > 0 ? `${empties} empty-folder` : null
|
|
6987
|
+
].filter((part) => part !== null).join(", ");
|
|
6988
|
+
if (unregistered > 0) {
|
|
6989
|
+
const first2 = findings.find((f) => f.kind === "unregistered_write");
|
|
6990
|
+
return {
|
|
6991
|
+
name: t("doctor.check.project_registry_drift.name"),
|
|
6992
|
+
status: "error",
|
|
6993
|
+
kind: "manual_error",
|
|
6994
|
+
code: "project_registry_drift",
|
|
6995
|
+
fixable: true,
|
|
6996
|
+
message: t("doctor.check.project_registry_drift.message.unregistered", {
|
|
6997
|
+
total: String(findings.length),
|
|
6998
|
+
breakdown,
|
|
6999
|
+
projectId: first2.project_id,
|
|
7000
|
+
storeAlias: first2.store_alias
|
|
7001
|
+
}),
|
|
7002
|
+
actionHint: t("doctor.check.project_registry_drift.remediation")
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
7005
|
+
if (orphans > 0) {
|
|
7006
|
+
const first2 = findings.find((f) => f.kind === "orphan_folder");
|
|
7007
|
+
return {
|
|
7008
|
+
name: t("doctor.check.project_registry_drift.name"),
|
|
7009
|
+
status: "warn",
|
|
7010
|
+
kind: "warning",
|
|
7011
|
+
code: "project_registry_drift",
|
|
7012
|
+
fixable: true,
|
|
7013
|
+
message: t("doctor.check.project_registry_drift.message.orphan", {
|
|
7014
|
+
total: String(findings.length),
|
|
7015
|
+
breakdown,
|
|
7016
|
+
projectId: first2.project_id,
|
|
7017
|
+
storeAlias: first2.store_alias
|
|
7018
|
+
}),
|
|
7019
|
+
actionHint: t("doctor.check.project_registry_drift.remediation")
|
|
7020
|
+
};
|
|
7021
|
+
}
|
|
7022
|
+
const first = findings.find((f) => f.kind === "empty_folder");
|
|
7023
|
+
return {
|
|
7024
|
+
name: t("doctor.check.project_registry_drift.name"),
|
|
7025
|
+
status: "warn",
|
|
7026
|
+
kind: "info",
|
|
7027
|
+
code: "project_registry_drift",
|
|
7028
|
+
fixable: true,
|
|
7029
|
+
message: t("doctor.check.project_registry_drift.message.empty", {
|
|
7030
|
+
total: String(findings.length),
|
|
7031
|
+
breakdown,
|
|
7032
|
+
projectId: first.project_id,
|
|
7033
|
+
storeAlias: first.store_alias
|
|
7034
|
+
}),
|
|
7035
|
+
actionHint: t("doctor.check.project_registry_drift.remediation")
|
|
7036
|
+
};
|
|
7037
|
+
}
|
|
7038
|
+
|
|
6755
7039
|
// src/services/legacy-serve-lock-probe.ts
|
|
6756
7040
|
import {
|
|
6757
7041
|
isAlive,
|
|
@@ -6824,8 +7108,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
6824
7108
|
}
|
|
6825
7109
|
|
|
6826
7110
|
// src/services/doctor-skill-lints.ts
|
|
6827
|
-
import { readdir as
|
|
6828
|
-
import { join as
|
|
7111
|
+
import { readdir as readdir4, readFile as readFile13 } from "fs/promises";
|
|
7112
|
+
import { join as join20, posix as posix2 } from "path";
|
|
6829
7113
|
var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review"];
|
|
6830
7114
|
var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
|
|
6831
7115
|
var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
|
|
@@ -6847,7 +7131,7 @@ function issueCheck(name, status, kind, code, message, actionHint, audience) {
|
|
|
6847
7131
|
}
|
|
6848
7132
|
async function listMarkdownFiles(dir) {
|
|
6849
7133
|
try {
|
|
6850
|
-
return (await
|
|
7134
|
+
return (await readdir4(dir)).filter((name) => name.endsWith(".md"));
|
|
6851
7135
|
} catch {
|
|
6852
7136
|
return null;
|
|
6853
7137
|
}
|
|
@@ -6855,8 +7139,8 @@ async function listMarkdownFiles(dir) {
|
|
|
6855
7139
|
async function inspectSkillRefMirror(projectRoot) {
|
|
6856
7140
|
const driftedPaths = [];
|
|
6857
7141
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6858
|
-
const claudeRef =
|
|
6859
|
-
const codexRef =
|
|
7142
|
+
const claudeRef = join20(projectRoot, ".claude", "skills", slug, "ref");
|
|
7143
|
+
const codexRef = join20(projectRoot, ".codex", "skills", slug, "ref");
|
|
6860
7144
|
const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
|
|
6861
7145
|
if (claudeFiles === null || codexFiles === null) continue;
|
|
6862
7146
|
const claudeSet = new Set(claudeFiles);
|
|
@@ -6873,8 +7157,8 @@ async function inspectSkillRefMirror(projectRoot) {
|
|
|
6873
7157
|
let codexBody;
|
|
6874
7158
|
try {
|
|
6875
7159
|
[claudeBody, codexBody] = await Promise.all([
|
|
6876
|
-
|
|
6877
|
-
|
|
7160
|
+
readFile13(join20(claudeRef, fname), "utf8"),
|
|
7161
|
+
readFile13(join20(codexRef, fname), "utf8")
|
|
6878
7162
|
]);
|
|
6879
7163
|
} catch {
|
|
6880
7164
|
continue;
|
|
@@ -6893,10 +7177,10 @@ async function inspectSkillTokenBudget(projectRoot) {
|
|
|
6893
7177
|
const overSize = [];
|
|
6894
7178
|
let highestSeverity = "ok";
|
|
6895
7179
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6896
|
-
const skillMdPath =
|
|
7180
|
+
const skillMdPath = join20(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6897
7181
|
let body;
|
|
6898
7182
|
try {
|
|
6899
|
-
body = await
|
|
7183
|
+
body = await readFile13(skillMdPath, "utf8");
|
|
6900
7184
|
} catch {
|
|
6901
7185
|
continue;
|
|
6902
7186
|
}
|
|
@@ -6917,10 +7201,10 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6917
7201
|
const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
|
|
6918
7202
|
const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
|
|
6919
7203
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6920
|
-
const skillMdPath =
|
|
7204
|
+
const skillMdPath = join20(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6921
7205
|
let body;
|
|
6922
7206
|
try {
|
|
6923
|
-
body = await
|
|
7207
|
+
body = await readFile13(skillMdPath, "utf8");
|
|
6924
7208
|
} catch {
|
|
6925
7209
|
continue;
|
|
6926
7210
|
}
|
|
@@ -6951,19 +7235,19 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6951
7235
|
async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
6952
7236
|
const candidates = [];
|
|
6953
7237
|
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
6954
|
-
const rootAbs =
|
|
7238
|
+
const rootAbs = join20(projectRoot, rootRel);
|
|
6955
7239
|
let dirEntries;
|
|
6956
7240
|
try {
|
|
6957
|
-
dirEntries = await
|
|
7241
|
+
dirEntries = await readdir4(rootAbs, { withFileTypes: true });
|
|
6958
7242
|
} catch {
|
|
6959
7243
|
continue;
|
|
6960
7244
|
}
|
|
6961
7245
|
for (const dirEntry of dirEntries) {
|
|
6962
7246
|
if (!dirEntry.isDirectory()) continue;
|
|
6963
|
-
const skillFile =
|
|
7247
|
+
const skillFile = join20(rootAbs, dirEntry.name, "SKILL.md");
|
|
6964
7248
|
let raw;
|
|
6965
7249
|
try {
|
|
6966
|
-
raw = await
|
|
7250
|
+
raw = await readFile13(skillFile, "utf8");
|
|
6967
7251
|
} catch {
|
|
6968
7252
|
continue;
|
|
6969
7253
|
}
|
|
@@ -7086,8 +7370,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
|
|
|
7086
7370
|
}
|
|
7087
7371
|
|
|
7088
7372
|
// src/services/doctor-retired-references-lint.ts
|
|
7089
|
-
import { readdir as
|
|
7090
|
-
import { join as
|
|
7373
|
+
import { readdir as readdir5, readFile as readFile14, stat as stat3 } from "fs/promises";
|
|
7374
|
+
import { join as join21, posix as posix3, relative as relative2 } from "path";
|
|
7091
7375
|
var RETIRED_TOKENS = [
|
|
7092
7376
|
{ token: "fab_plan_context", replacement: "fab_recall", reason: "retrieval collapsed to one lean fab_recall (KT-DEC-0026)" },
|
|
7093
7377
|
{ token: "fab_get_knowledge_sections", replacement: "fab_recall", reason: "two-step fetch retired (KT-DEC-0026)" },
|
|
@@ -7110,7 +7394,7 @@ var RETIRED_TOKENS = [
|
|
|
7110
7394
|
];
|
|
7111
7395
|
var HOOK_DIRS = [".claude/hooks", ".codex/hooks"];
|
|
7112
7396
|
var SKILL_DIRS = [".claude/skills", ".codex/skills"];
|
|
7113
|
-
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md",
|
|
7397
|
+
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md", join21(".fabric", "AGENTS.md")];
|
|
7114
7398
|
function isCommentLine(line) {
|
|
7115
7399
|
const t = line.trim();
|
|
7116
7400
|
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
@@ -7118,13 +7402,13 @@ function isCommentLine(line) {
|
|
|
7118
7402
|
async function walkFiles(dir, exts) {
|
|
7119
7403
|
let entries;
|
|
7120
7404
|
try {
|
|
7121
|
-
entries = await
|
|
7405
|
+
entries = await readdir5(dir, { withFileTypes: true });
|
|
7122
7406
|
} catch {
|
|
7123
7407
|
return [];
|
|
7124
7408
|
}
|
|
7125
7409
|
const out = [];
|
|
7126
7410
|
for (const e of entries) {
|
|
7127
|
-
const full =
|
|
7411
|
+
const full = join21(dir, e.name);
|
|
7128
7412
|
if (e.isDirectory()) {
|
|
7129
7413
|
out.push(...await walkFiles(full, exts));
|
|
7130
7414
|
} else if (exts.some((ext) => e.name.endsWith(ext))) {
|
|
@@ -7150,29 +7434,29 @@ async function inspectRetiredReferences(projectRoot) {
|
|
|
7150
7434
|
let scannedFiles = 0;
|
|
7151
7435
|
const toRel = (p) => posix3.normalize(relative2(projectRoot, p).split("\\").join("/"));
|
|
7152
7436
|
for (const rel of BOOTSTRAP_FILES) {
|
|
7153
|
-
const abs =
|
|
7437
|
+
const abs = join21(projectRoot, rel);
|
|
7154
7438
|
try {
|
|
7155
7439
|
if ((await stat3(abs)).isFile()) {
|
|
7156
7440
|
scannedFiles += 1;
|
|
7157
|
-
scanText(toRel(abs), await
|
|
7441
|
+
scanText(toRel(abs), await readFile14(abs, "utf8"), false, hits);
|
|
7158
7442
|
}
|
|
7159
7443
|
} catch {
|
|
7160
7444
|
}
|
|
7161
7445
|
}
|
|
7162
7446
|
for (const dir of SKILL_DIRS) {
|
|
7163
|
-
for (const file of await walkFiles(
|
|
7447
|
+
for (const file of await walkFiles(join21(projectRoot, dir), [".md"])) {
|
|
7164
7448
|
scannedFiles += 1;
|
|
7165
7449
|
try {
|
|
7166
|
-
scanText(toRel(file), await
|
|
7450
|
+
scanText(toRel(file), await readFile14(file, "utf8"), false, hits);
|
|
7167
7451
|
} catch {
|
|
7168
7452
|
}
|
|
7169
7453
|
}
|
|
7170
7454
|
}
|
|
7171
7455
|
for (const dir of HOOK_DIRS) {
|
|
7172
|
-
for (const file of await walkFiles(
|
|
7456
|
+
for (const file of await walkFiles(join21(projectRoot, dir), [".cjs"])) {
|
|
7173
7457
|
scannedFiles += 1;
|
|
7174
7458
|
try {
|
|
7175
|
-
scanText(toRel(file), await
|
|
7459
|
+
scanText(toRel(file), await readFile14(file, "utf8"), true, hits);
|
|
7176
7460
|
} catch {
|
|
7177
7461
|
}
|
|
7178
7462
|
}
|
|
@@ -7207,8 +7491,8 @@ function createRetiredReferenceCheck(t, inspection) {
|
|
|
7207
7491
|
|
|
7208
7492
|
// src/services/doctor-hooks-lints.ts
|
|
7209
7493
|
import { constants } from "fs";
|
|
7210
|
-
import { access, readdir as
|
|
7211
|
-
import { join as
|
|
7494
|
+
import { access, readdir as readdir6, readFile as readFile15, stat as stat4 } from "fs/promises";
|
|
7495
|
+
import { join as join22, posix as posix4 } from "path";
|
|
7212
7496
|
import { Script } from "vm";
|
|
7213
7497
|
var HOOKS_RUNTIME_CLIENT_DIRS = [
|
|
7214
7498
|
{ client: "claude", dir: ".claude/hooks" },
|
|
@@ -7257,7 +7541,7 @@ function isHookWiredForEvent(hooks, event, hookFile) {
|
|
|
7257
7541
|
}
|
|
7258
7542
|
async function readDirectoryFileNames(dir) {
|
|
7259
7543
|
try {
|
|
7260
|
-
return await
|
|
7544
|
+
return await readdir6(dir);
|
|
7261
7545
|
} catch {
|
|
7262
7546
|
return null;
|
|
7263
7547
|
}
|
|
@@ -7270,14 +7554,14 @@ async function isFile(absPath) {
|
|
|
7270
7554
|
}
|
|
7271
7555
|
}
|
|
7272
7556
|
async function inspectHooksWired(projectRoot) {
|
|
7273
|
-
const claudeEntries = await readDirectoryFileNames(
|
|
7557
|
+
const claudeEntries = await readDirectoryFileNames(join22(projectRoot, ".claude"));
|
|
7274
7558
|
if (claudeEntries === null) {
|
|
7275
7559
|
return { status: "skipped", missingHooks: [] };
|
|
7276
7560
|
}
|
|
7277
|
-
const settingsPath =
|
|
7561
|
+
const settingsPath = join22(projectRoot, ".claude", "settings.json");
|
|
7278
7562
|
let parsed;
|
|
7279
7563
|
try {
|
|
7280
|
-
parsed = JSON.parse(await
|
|
7564
|
+
parsed = JSON.parse(await readFile15(settingsPath, "utf8"));
|
|
7281
7565
|
} catch {
|
|
7282
7566
|
return { status: "missing-settings", missingHooks: [] };
|
|
7283
7567
|
}
|
|
@@ -7300,8 +7584,8 @@ async function inspectHooksWired(projectRoot) {
|
|
|
7300
7584
|
}
|
|
7301
7585
|
async function inspectHookCacheWritability(projectRoot) {
|
|
7302
7586
|
const relPath = posix4.join(".fabric", ".cache");
|
|
7303
|
-
const fabricDir =
|
|
7304
|
-
const cacheDir =
|
|
7587
|
+
const fabricDir = join22(projectRoot, ".fabric");
|
|
7588
|
+
const cacheDir = join22(projectRoot, ".fabric", ".cache");
|
|
7305
7589
|
try {
|
|
7306
7590
|
try {
|
|
7307
7591
|
const cacheStats = await stat4(cacheDir);
|
|
@@ -7350,12 +7634,12 @@ async function inspectHookCacheWritability(projectRoot) {
|
|
|
7350
7634
|
async function inspectHooksContentDrift(projectRoot) {
|
|
7351
7635
|
const hookFilesByBasename = /* @__PURE__ */ new Map();
|
|
7352
7636
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
7353
|
-
const absDir =
|
|
7637
|
+
const absDir = join22(projectRoot, dir);
|
|
7354
7638
|
const entries = await readDirectoryFileNames(absDir);
|
|
7355
7639
|
if (entries === null) continue;
|
|
7356
7640
|
for (const name of entries) {
|
|
7357
7641
|
if (!name.endsWith(".cjs")) continue;
|
|
7358
|
-
const abs =
|
|
7642
|
+
const abs = join22(absDir, name);
|
|
7359
7643
|
if (!await isFile(abs)) continue;
|
|
7360
7644
|
const arr = hookFilesByBasename.get(name) ?? [];
|
|
7361
7645
|
arr.push({ client, abs });
|
|
@@ -7370,7 +7654,7 @@ async function inspectHooksContentDrift(projectRoot) {
|
|
|
7370
7654
|
const hashes = [];
|
|
7371
7655
|
for (const { client, abs } of copies) {
|
|
7372
7656
|
try {
|
|
7373
|
-
const body = await
|
|
7657
|
+
const body = await readFile15(abs, "utf8");
|
|
7374
7658
|
hashes.push({ client, sha: sha256(body) });
|
|
7375
7659
|
} catch {
|
|
7376
7660
|
}
|
|
@@ -7392,18 +7676,18 @@ async function inspectHooksRuntime(projectRoot) {
|
|
|
7392
7676
|
const issues = [];
|
|
7393
7677
|
let scanned = 0;
|
|
7394
7678
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
7395
|
-
const absDir =
|
|
7679
|
+
const absDir = join22(projectRoot, dir);
|
|
7396
7680
|
const entries = await readDirectoryFileNames(absDir);
|
|
7397
7681
|
if (entries === null) continue;
|
|
7398
7682
|
for (const name of entries) {
|
|
7399
7683
|
if (!name.endsWith(".cjs")) continue;
|
|
7400
|
-
const abs =
|
|
7684
|
+
const abs = join22(absDir, name);
|
|
7401
7685
|
const displayPath = `${dir}/${name}`;
|
|
7402
7686
|
if (!await isFile(abs)) continue;
|
|
7403
7687
|
scanned += 1;
|
|
7404
7688
|
let body;
|
|
7405
7689
|
try {
|
|
7406
|
-
body = await
|
|
7690
|
+
body = await readFile15(abs, "utf8");
|
|
7407
7691
|
} catch (err) {
|
|
7408
7692
|
issues.push({
|
|
7409
7693
|
path: displayPath,
|
|
@@ -7540,8 +7824,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
|
|
|
7540
7824
|
}
|
|
7541
7825
|
|
|
7542
7826
|
// src/services/doctor-bootstrap-lints.ts
|
|
7543
|
-
import { access as access2, readFile as
|
|
7544
|
-
import { join as
|
|
7827
|
+
import { access as access2, readFile as readFile16 } from "fs/promises";
|
|
7828
|
+
import { join as join23 } from "path";
|
|
7545
7829
|
import {
|
|
7546
7830
|
BOOTSTRAP_MARKER_BEGIN,
|
|
7547
7831
|
BOOTSTRAP_MARKER_END,
|
|
@@ -7573,17 +7857,17 @@ async function fileExists(path) {
|
|
|
7573
7857
|
}
|
|
7574
7858
|
async function inspectBootstrapAnchor(projectRoot) {
|
|
7575
7859
|
const [hasAgentsMd, hasClaudeMd] = await Promise.all([
|
|
7576
|
-
fileExists(
|
|
7577
|
-
fileExists(
|
|
7860
|
+
fileExists(join23(projectRoot, "AGENTS.md")),
|
|
7861
|
+
fileExists(join23(projectRoot, "CLAUDE.md"))
|
|
7578
7862
|
]);
|
|
7579
7863
|
return { hasAgentsMd, hasClaudeMd };
|
|
7580
7864
|
}
|
|
7581
7865
|
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
7582
|
-
const abs =
|
|
7866
|
+
const abs = join23(target, ".fabric", "AGENTS.md");
|
|
7583
7867
|
const canonical = resolveBootstrapCanonical();
|
|
7584
7868
|
let onDisk;
|
|
7585
7869
|
try {
|
|
7586
|
-
onDisk = await
|
|
7870
|
+
onDisk = await readFile16(abs, "utf8");
|
|
7587
7871
|
} catch {
|
|
7588
7872
|
return { status: "missing", canonical, onDisk: null };
|
|
7589
7873
|
}
|
|
@@ -7609,17 +7893,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
|
|
|
7609
7893
|
);
|
|
7610
7894
|
}
|
|
7611
7895
|
async function inspectL2ManagedBlockDrift(target) {
|
|
7612
|
-
const snapshotPath =
|
|
7896
|
+
const snapshotPath = join23(target, ".fabric", "AGENTS.md");
|
|
7613
7897
|
let snapshot;
|
|
7614
7898
|
try {
|
|
7615
|
-
snapshot = await
|
|
7899
|
+
snapshot = await readFile16(snapshotPath, "utf8");
|
|
7616
7900
|
} catch {
|
|
7617
7901
|
return { status: "ok", drifted: [] };
|
|
7618
7902
|
}
|
|
7619
|
-
const projectRulesPath =
|
|
7903
|
+
const projectRulesPath = join23(target, ".fabric", "project-rules.md");
|
|
7620
7904
|
let expectedBody = snapshot;
|
|
7621
7905
|
try {
|
|
7622
|
-
const projectRules = await
|
|
7906
|
+
const projectRules = await readFile16(projectRulesPath, "utf8");
|
|
7623
7907
|
expectedBody = `${snapshot}
|
|
7624
7908
|
---
|
|
7625
7909
|
${projectRules}`;
|
|
@@ -7628,12 +7912,12 @@ ${projectRules}`;
|
|
|
7628
7912
|
const drifted = [];
|
|
7629
7913
|
let anyManagedBlockFound = false;
|
|
7630
7914
|
const blockTargets = [
|
|
7631
|
-
|
|
7915
|
+
join23(target, "AGENTS.md")
|
|
7632
7916
|
];
|
|
7633
7917
|
for (const abs of blockTargets) {
|
|
7634
7918
|
let content;
|
|
7635
7919
|
try {
|
|
7636
|
-
content = await
|
|
7920
|
+
content = await readFile16(abs, "utf8");
|
|
7637
7921
|
} catch {
|
|
7638
7922
|
continue;
|
|
7639
7923
|
}
|
|
@@ -7656,9 +7940,9 @@ ${projectRules}`;
|
|
|
7656
7940
|
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
7657
7941
|
}
|
|
7658
7942
|
}
|
|
7659
|
-
const claudeMdPath =
|
|
7943
|
+
const claudeMdPath = join23(target, "CLAUDE.md");
|
|
7660
7944
|
try {
|
|
7661
|
-
const claudeContent = await
|
|
7945
|
+
const claudeContent = await readFile16(claudeMdPath, "utf8");
|
|
7662
7946
|
anyManagedBlockFound = true;
|
|
7663
7947
|
const lines = claudeContent.split(/\r?\n/u);
|
|
7664
7948
|
const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
|
|
@@ -7726,7 +8010,7 @@ import { appendFile as appendFile3 } from "fs/promises";
|
|
|
7726
8010
|
import { minimatch as minimatch2 } from "minimatch";
|
|
7727
8011
|
|
|
7728
8012
|
// src/services/cite-rollup.ts
|
|
7729
|
-
import { readFile as
|
|
8013
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
7730
8014
|
import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
7731
8015
|
var citeRollupQueue = createLedgerWriteQueue3();
|
|
7732
8016
|
async function appendCiteRollupRow(projectRoot, row) {
|
|
@@ -7738,7 +8022,7 @@ async function readCiteRollup(projectRoot) {
|
|
|
7738
8022
|
const path = getCiteRollupPath(projectRoot);
|
|
7739
8023
|
let raw;
|
|
7740
8024
|
try {
|
|
7741
|
-
raw = await
|
|
8025
|
+
raw = await readFile17(path, "utf8");
|
|
7742
8026
|
} catch (error) {
|
|
7743
8027
|
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
7744
8028
|
throw error;
|
|
@@ -8856,6 +9140,7 @@ async function runDoctorReport(target) {
|
|
|
8856
9140
|
const driftUnconsumed = await inspectDriftUnconsumed(projectRoot);
|
|
8857
9141
|
const storeCounterDrift = inspectStoreCounters(projectRoot);
|
|
8858
9142
|
const storeOrphans = inspectStoreOrphans();
|
|
9143
|
+
const projectRegistryDrift = await inspectProjectRegistryDrift(projectRoot);
|
|
8859
9144
|
const stableIdIntegrity = await inspectStoreStableIdIntegrity(projectRoot);
|
|
8860
9145
|
const relevancePaths = await inspectStoreRelevancePaths(projectRoot);
|
|
8861
9146
|
const broadIndexDrift = await inspectBroadIndexDrift(projectRoot);
|
|
@@ -8885,7 +9170,7 @@ async function runDoctorReport(target) {
|
|
|
8885
9170
|
const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
|
|
8886
9171
|
const targetFiles = Object.fromEntries(
|
|
8887
9172
|
await Promise.all(
|
|
8888
|
-
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(
|
|
9173
|
+
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join24(projectRoot, path))])
|
|
8889
9174
|
)
|
|
8890
9175
|
);
|
|
8891
9176
|
const checks = [
|
|
@@ -8939,6 +9224,12 @@ async function runDoctorReport(target) {
|
|
|
8939
9224
|
// (warning). `--fix` adopts it (rescue-before-delete — re-register, never
|
|
8940
9225
|
// an on-disk delete).
|
|
8941
9226
|
createStoreOrphanCheck(t, storeOrphans),
|
|
9227
|
+
// W2 (F-003): project-registry drift over projects.json ↔ projects/ folder
|
|
9228
|
+
// tree. unregistered-write (data unrouted) → manual_error; orphan-folder →
|
|
9229
|
+
// warning; empty-folder → info; ghost-registration emits nothing (DA-05).
|
|
9230
|
+
// `--fix` rescue-registers orphans/unregistered (addStoreProject — never a
|
|
9231
|
+
// non-empty delete) and prunes only genuinely-empty registered folders.
|
|
9232
|
+
createProjectRegistryDriftCheck(t, projectRegistryDrift),
|
|
8942
9233
|
// rc.5 TASK-010: read-side underseeded-corpus check (#22). Info kind —
|
|
8943
9234
|
// does not bump report status. Recommends running the fabric-import skill
|
|
8944
9235
|
// to backfill knowledge when the corpus is below the threshold floor.
|
|
@@ -9089,7 +9380,7 @@ async function runDoctorFix(target) {
|
|
|
9089
9380
|
const fixed = [];
|
|
9090
9381
|
const ledgerWarnings = [];
|
|
9091
9382
|
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
9092
|
-
const snapshotPath =
|
|
9383
|
+
const snapshotPath = join24(projectRoot, ".fabric", "AGENTS.md");
|
|
9093
9384
|
await ensureParentDirectory(snapshotPath);
|
|
9094
9385
|
await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
|
|
9095
9386
|
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
@@ -9113,6 +9404,18 @@ async function runDoctorFix(target) {
|
|
|
9113
9404
|
fixed.push(findIssue(before.warnings, "store_orphan"));
|
|
9114
9405
|
}
|
|
9115
9406
|
}
|
|
9407
|
+
const registryDriftFound = before.manual_errors.some((issue) => issue.code === "project_registry_drift") || before.warnings.some((issue) => issue.code === "project_registry_drift") || before.infos.some((issue) => issue.code === "project_registry_drift");
|
|
9408
|
+
if (registryDriftFound) {
|
|
9409
|
+
const result = await fixProjectRegistryDrift(projectRoot);
|
|
9410
|
+
if (result.registered.length > 0 || result.pruned.length > 0) {
|
|
9411
|
+
fixed.push(
|
|
9412
|
+
findIssue(
|
|
9413
|
+
[...before.manual_errors, ...before.warnings, ...before.infos],
|
|
9414
|
+
"project_registry_drift"
|
|
9415
|
+
)
|
|
9416
|
+
);
|
|
9417
|
+
}
|
|
9418
|
+
}
|
|
9116
9419
|
if (before.fixable_errors.some((issue) => issue.code === "event_ledger_partial_write")) {
|
|
9117
9420
|
const ledgerPath = getEventLedgerPath(projectRoot);
|
|
9118
9421
|
const truncResult = await truncateLedgerToLastNewline(ledgerPath);
|
|
@@ -9162,7 +9465,7 @@ async function runDoctorFix(target) {
|
|
|
9162
9465
|
if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
|
|
9163
9466
|
const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
|
|
9164
9467
|
if (lockInspection.present && !lockInspection.pidAlive) {
|
|
9165
|
-
const lockFilePath =
|
|
9468
|
+
const lockFilePath = join24(projectRoot, ".fabric", ".serve.lock");
|
|
9166
9469
|
try {
|
|
9167
9470
|
await unlink4(lockFilePath);
|
|
9168
9471
|
} catch (err) {
|
|
@@ -9279,7 +9582,7 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
|
|
|
9279
9582
|
}
|
|
9280
9583
|
async function applySessionHintsStaleCleanup(projectRoot, candidate) {
|
|
9281
9584
|
const detail = `deleted (${candidate.age_days}d old)`;
|
|
9282
|
-
const absPath =
|
|
9585
|
+
const absPath = join24(projectRoot, candidate.path);
|
|
9283
9586
|
try {
|
|
9284
9587
|
const { unlink: unlink5 } = await import("fs/promises");
|
|
9285
9588
|
await unlink5(absPath);
|
|
@@ -9304,9 +9607,9 @@ function truncateErrorMessage(error) {
|
|
|
9304
9607
|
return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
|
|
9305
9608
|
}
|
|
9306
9609
|
async function inspectForensic(projectRoot) {
|
|
9307
|
-
const path =
|
|
9610
|
+
const path = join24(projectRoot, ".fabric", "forensic.json");
|
|
9308
9611
|
try {
|
|
9309
|
-
const parsed = forensicReportSchema.parse(JSON.parse(await
|
|
9612
|
+
const parsed = forensicReportSchema.parse(JSON.parse(await readFile19(path, "utf8")));
|
|
9310
9613
|
return { present: true, valid: true, report: parsed };
|
|
9311
9614
|
} catch (error) {
|
|
9312
9615
|
if (isMissingFileError(error)) {
|
|
@@ -9336,7 +9639,7 @@ async function inspectEventLedger(projectRoot) {
|
|
|
9336
9639
|
try {
|
|
9337
9640
|
await access4(path, constants2.W_OK);
|
|
9338
9641
|
const { warnings } = await readEventLedger(projectRoot);
|
|
9339
|
-
const raw = await
|
|
9642
|
+
const raw = await readFile19(path, "utf8");
|
|
9340
9643
|
const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
|
|
9341
9644
|
const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
|
|
9342
9645
|
const schemaVersionSamples = [];
|
|
@@ -9881,7 +10184,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
|
|
|
9881
10184
|
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
9882
10185
|
const detected = [];
|
|
9883
10186
|
for (const name of candidates) {
|
|
9884
|
-
if (await pathExists(
|
|
10187
|
+
if (await pathExists(join24(projectRoot, name))) {
|
|
9885
10188
|
detected.push(name);
|
|
9886
10189
|
}
|
|
9887
10190
|
}
|
|
@@ -9966,7 +10269,7 @@ async function buildLastActiveIndex(projectRoot) {
|
|
|
9966
10269
|
return map;
|
|
9967
10270
|
}
|
|
9968
10271
|
async function inspectSessionHintsStale(projectRoot, now) {
|
|
9969
|
-
const cacheDir =
|
|
10272
|
+
const cacheDir = join24(projectRoot, ".fabric", ".cache");
|
|
9970
10273
|
let entries;
|
|
9971
10274
|
try {
|
|
9972
10275
|
entries = await readdirAsync(cacheDir, { withFileTypes: true });
|
|
@@ -9978,7 +10281,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
|
|
|
9978
10281
|
if (!entry.isFile()) continue;
|
|
9979
10282
|
if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
|
|
9980
10283
|
if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
|
|
9981
|
-
const absPath =
|
|
10284
|
+
const absPath = join24(cacheDir, entry.name);
|
|
9982
10285
|
let mtimeMs = 0;
|
|
9983
10286
|
try {
|
|
9984
10287
|
mtimeMs = (await statAsync(absPath)).mtimeMs;
|
|
@@ -10010,9 +10313,9 @@ function inspectStaleServeLock(projectRoot, now) {
|
|
|
10010
10313
|
};
|
|
10011
10314
|
}
|
|
10012
10315
|
async function readUnderseedThresholdFromConfig(projectRoot) {
|
|
10013
|
-
const configPath =
|
|
10316
|
+
const configPath = join24(projectRoot, ".fabric", "fabric-config.json");
|
|
10014
10317
|
try {
|
|
10015
|
-
const raw = await
|
|
10318
|
+
const raw = await readFile19(configPath, "utf8");
|
|
10016
10319
|
const parsed = JSON.parse(raw);
|
|
10017
10320
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10018
10321
|
const v = parsed.underseed_node_threshold;
|
|
@@ -10128,10 +10431,10 @@ async function inspectOnboardCoverage(projectRoot) {
|
|
|
10128
10431
|
return { filled, missing, opted_out: optedOut };
|
|
10129
10432
|
}
|
|
10130
10433
|
async function readOnboardOptedOut(projectRoot) {
|
|
10131
|
-
const path =
|
|
10434
|
+
const path = join24(projectRoot, ".fabric", "fabric-config.json");
|
|
10132
10435
|
let raw;
|
|
10133
10436
|
try {
|
|
10134
|
-
raw = await
|
|
10437
|
+
raw = await readFile19(path, "utf8");
|
|
10135
10438
|
} catch {
|
|
10136
10439
|
return [];
|
|
10137
10440
|
}
|
|
@@ -10233,22 +10536,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
|
|
|
10233
10536
|
}
|
|
10234
10537
|
}
|
|
10235
10538
|
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
10236
|
-
const snapshotPath =
|
|
10539
|
+
const snapshotPath = join24(projectRoot, ".fabric", "AGENTS.md");
|
|
10237
10540
|
if (!await pathExists(snapshotPath)) {
|
|
10238
10541
|
return;
|
|
10239
10542
|
}
|
|
10240
10543
|
let snapshot;
|
|
10241
10544
|
try {
|
|
10242
|
-
snapshot = await
|
|
10545
|
+
snapshot = await readFile19(snapshotPath, "utf8");
|
|
10243
10546
|
} catch {
|
|
10244
10547
|
return;
|
|
10245
10548
|
}
|
|
10246
|
-
const projectRulesPath =
|
|
10549
|
+
const projectRulesPath = join24(projectRoot, ".fabric", "project-rules.md");
|
|
10247
10550
|
const hasProjectRules = await pathExists(projectRulesPath);
|
|
10248
10551
|
let expectedBody = snapshot;
|
|
10249
10552
|
if (hasProjectRules) {
|
|
10250
10553
|
try {
|
|
10251
|
-
const projectRules = await
|
|
10554
|
+
const projectRules = await readFile19(projectRulesPath, "utf8");
|
|
10252
10555
|
expectedBody = `${snapshot}
|
|
10253
10556
|
---
|
|
10254
10557
|
${projectRules}`;
|
|
@@ -10259,7 +10562,7 @@ ${projectRules}`;
|
|
|
10259
10562
|
${expectedBody}
|
|
10260
10563
|
${BOOTSTRAP_MARKER_END2}`;
|
|
10261
10564
|
const blockTargets = [
|
|
10262
|
-
|
|
10565
|
+
join24(projectRoot, "AGENTS.md")
|
|
10263
10566
|
];
|
|
10264
10567
|
for (const abs of blockTargets) {
|
|
10265
10568
|
if (!await pathExists(abs)) {
|
|
@@ -10267,7 +10570,7 @@ ${BOOTSTRAP_MARKER_END2}`;
|
|
|
10267
10570
|
}
|
|
10268
10571
|
let existing;
|
|
10269
10572
|
try {
|
|
10270
|
-
existing = await
|
|
10573
|
+
existing = await readFile19(abs, "utf8");
|
|
10271
10574
|
} catch {
|
|
10272
10575
|
continue;
|
|
10273
10576
|
}
|
|
@@ -10292,11 +10595,11 @@ ${managedBlock}
|
|
|
10292
10595
|
}
|
|
10293
10596
|
await atomicWriteText4(abs, next);
|
|
10294
10597
|
}
|
|
10295
|
-
const claudeMdPath =
|
|
10598
|
+
const claudeMdPath = join24(projectRoot, "CLAUDE.md");
|
|
10296
10599
|
if (await pathExists(claudeMdPath)) {
|
|
10297
10600
|
let claudeContent;
|
|
10298
10601
|
try {
|
|
10299
|
-
claudeContent = await
|
|
10602
|
+
claudeContent = await readFile19(claudeMdPath, "utf8");
|
|
10300
10603
|
} catch {
|
|
10301
10604
|
return;
|
|
10302
10605
|
}
|
|
@@ -10323,7 +10626,7 @@ ${managedBlock}
|
|
|
10323
10626
|
async function ensureEventLedger(projectRoot) {
|
|
10324
10627
|
const path = getEventLedgerPath(projectRoot);
|
|
10325
10628
|
await ensureParentDirectory(path);
|
|
10326
|
-
await
|
|
10629
|
+
await writeFile5(path, "", { encoding: "utf8", flag: "a" });
|
|
10327
10630
|
}
|
|
10328
10631
|
function createFixMessage(fixed, report) {
|
|
10329
10632
|
const fixedText = fixed.length === 0 ? "No deterministic doctor fixes were needed." : `Applied ${fixed.length} deterministic doctor fix${fixed.length === 1 ? "" : "es"}.`;
|
|
@@ -10362,7 +10665,7 @@ async function collectEntryPoints(root) {
|
|
|
10362
10665
|
continue;
|
|
10363
10666
|
}
|
|
10364
10667
|
for (const entry of await readdirAsync(current, { withFileTypes: true })) {
|
|
10365
|
-
const absolutePath =
|
|
10668
|
+
const absolutePath = join24(current, entry.name);
|
|
10366
10669
|
const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
|
|
10367
10670
|
if (relativePath.length === 0) {
|
|
10368
10671
|
continue;
|
|
@@ -10438,7 +10741,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
|
|
|
10438
10741
|
scanned += 1;
|
|
10439
10742
|
let source;
|
|
10440
10743
|
try {
|
|
10441
|
-
source = await
|
|
10744
|
+
source = await readFile19(absPath, "utf8");
|
|
10442
10745
|
} catch {
|
|
10443
10746
|
continue;
|
|
10444
10747
|
}
|
|
@@ -10576,16 +10879,16 @@ async function runDoctorConflictLint(projectRoot, opts = {}) {
|
|
|
10576
10879
|
}
|
|
10577
10880
|
|
|
10578
10881
|
// src/services/why-not-surfaced.ts
|
|
10579
|
-
import { readFile as
|
|
10580
|
-
import { basename as basename3, join as
|
|
10882
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
10883
|
+
import { basename as basename3, join as join25 } from "path";
|
|
10581
10884
|
import {
|
|
10582
|
-
buildStoreResolveInput as
|
|
10583
|
-
createStoreResolver as
|
|
10584
|
-
loadProjectConfig as
|
|
10885
|
+
buildStoreResolveInput as buildStoreResolveInput8,
|
|
10886
|
+
createStoreResolver as createStoreResolver8,
|
|
10887
|
+
loadProjectConfig as loadProjectConfig5,
|
|
10585
10888
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores4,
|
|
10586
|
-
resolveGlobalRoot as
|
|
10889
|
+
resolveGlobalRoot as resolveGlobalRoot9,
|
|
10587
10890
|
scopeRoot as scopeRoot3,
|
|
10588
|
-
storeRelativePathForMount as
|
|
10891
|
+
storeRelativePathForMount as storeRelativePathForMount7
|
|
10589
10892
|
} from "@fenglimg/fabric-shared";
|
|
10590
10893
|
var SEMANTIC_SCOPE_LINE3 = /^semantic_scope:\s*"?([^"\n]+?)"?\s*$/mu;
|
|
10591
10894
|
var RELEVANCE_SCOPE_LINE = /^relevance_scope:\s*"?(broad|narrow)"?\s*$/mu;
|
|
@@ -10609,15 +10912,15 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10609
10912
|
activeProject: null,
|
|
10610
10913
|
relevanceScope: null
|
|
10611
10914
|
};
|
|
10612
|
-
const input =
|
|
10915
|
+
const input = buildStoreResolveInput8(projectRoot);
|
|
10613
10916
|
if (input === null) {
|
|
10614
10917
|
return base;
|
|
10615
10918
|
}
|
|
10616
|
-
const globalRoot =
|
|
10919
|
+
const globalRoot = resolveGlobalRoot9();
|
|
10617
10920
|
const allStores = input.mountedStores.map((s) => ({
|
|
10618
10921
|
store_uuid: s.store_uuid,
|
|
10619
10922
|
alias: s.alias,
|
|
10620
|
-
dir:
|
|
10923
|
+
dir: join25(globalRoot, storeRelativePathForMount7(s))
|
|
10621
10924
|
}));
|
|
10622
10925
|
const refs = await readKnowledgeAcrossStores4(allStores);
|
|
10623
10926
|
const candidate = refs.find((ref) => fileMatchesId(ref.file, localId));
|
|
@@ -10626,7 +10929,7 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10626
10929
|
}
|
|
10627
10930
|
let source;
|
|
10628
10931
|
try {
|
|
10629
|
-
source = await
|
|
10932
|
+
source = await readFile20(candidate.file, "utf8");
|
|
10630
10933
|
} catch {
|
|
10631
10934
|
return base;
|
|
10632
10935
|
}
|
|
@@ -10635,9 +10938,9 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10635
10938
|
}
|
|
10636
10939
|
const semanticScope = SEMANTIC_SCOPE_LINE3.exec(source)?.[1] ?? null;
|
|
10637
10940
|
const relevanceScope = RELEVANCE_SCOPE_LINE.exec(source)?.[1] ?? "broad";
|
|
10638
|
-
const activeProject =
|
|
10941
|
+
const activeProject = loadProjectConfig5(projectRoot)?.active_project ?? null;
|
|
10639
10942
|
const boundUuids = new Set(
|
|
10640
|
-
|
|
10943
|
+
createStoreResolver8().resolveReadSet(input).stores.map((s) => s.store_uuid)
|
|
10641
10944
|
);
|
|
10642
10945
|
const storeBound = boundUuids.has(candidate.store_uuid);
|
|
10643
10946
|
const found = {
|
|
@@ -10732,12 +11035,12 @@ async function inspectRelatedGraph(projectRoot) {
|
|
|
10732
11035
|
|
|
10733
11036
|
// src/services/store-precheck.ts
|
|
10734
11037
|
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
10735
|
-
import { join as
|
|
11038
|
+
import { join as join26 } from "path";
|
|
10736
11039
|
import {
|
|
10737
|
-
buildStoreResolveInput as
|
|
10738
|
-
createStoreResolver as
|
|
10739
|
-
resolveGlobalRoot as
|
|
10740
|
-
storeRelativePathForMount as
|
|
11040
|
+
buildStoreResolveInput as buildStoreResolveInput9,
|
|
11041
|
+
createStoreResolver as createStoreResolver9,
|
|
11042
|
+
resolveGlobalRoot as resolveGlobalRoot10,
|
|
11043
|
+
storeRelativePathForMount as storeRelativePathForMount8
|
|
10741
11044
|
} from "@fenglimg/fabric-shared";
|
|
10742
11045
|
var CACHE_TTL_MS = 6e4;
|
|
10743
11046
|
var cache = /* @__PURE__ */ new Map();
|
|
@@ -10753,8 +11056,8 @@ function evaluateStoreDir(storeDir, identity) {
|
|
|
10753
11056
|
reason: `directory not found at ${storeDir}`
|
|
10754
11057
|
};
|
|
10755
11058
|
}
|
|
10756
|
-
const storeJsonPath =
|
|
10757
|
-
const gitDirPath =
|
|
11059
|
+
const storeJsonPath = join26(storeDir, "store.json");
|
|
11060
|
+
const gitDirPath = join26(storeDir, ".git");
|
|
10758
11061
|
const hasStoreJson = existsSync9(storeJsonPath) && isValidStoreJson(storeJsonPath);
|
|
10759
11062
|
const hasGitDir = existsSync9(gitDirPath);
|
|
10760
11063
|
if (!hasStoreJson && !hasGitDir) {
|
|
@@ -10775,23 +11078,23 @@ function isValidStoreJson(path) {
|
|
|
10775
11078
|
return false;
|
|
10776
11079
|
}
|
|
10777
11080
|
}
|
|
10778
|
-
async function precheckStoreReachability(projectRoot, globalRoot =
|
|
11081
|
+
async function precheckStoreReachability(projectRoot, globalRoot = resolveGlobalRoot10(), now = Date.now()) {
|
|
10779
11082
|
const cached = cache.get(projectRoot);
|
|
10780
11083
|
if (cached !== void 0 && cached.expiresAt > now) {
|
|
10781
11084
|
return cached.result;
|
|
10782
11085
|
}
|
|
10783
|
-
const input =
|
|
11086
|
+
const input = buildStoreResolveInput9(projectRoot, globalRoot);
|
|
10784
11087
|
if (input === null) {
|
|
10785
11088
|
const result2 = { stores: [], allReachable: true };
|
|
10786
11089
|
cache.set(projectRoot, { result: result2, expiresAt: now + CACHE_TTL_MS });
|
|
10787
11090
|
return result2;
|
|
10788
11091
|
}
|
|
10789
|
-
const readSet =
|
|
11092
|
+
const readSet = createStoreResolver9().resolveReadSet(input);
|
|
10790
11093
|
const stores = readSet.stores.map((entry) => {
|
|
10791
11094
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
10792
|
-
const storeDir =
|
|
11095
|
+
const storeDir = join26(
|
|
10793
11096
|
globalRoot,
|
|
10794
|
-
|
|
11097
|
+
storeRelativePathForMount8(mounted ?? { store_uuid: entry.store_uuid })
|
|
10795
11098
|
);
|
|
10796
11099
|
return evaluateStoreDir(storeDir, { uuid: entry.store_uuid, alias: entry.alias });
|
|
10797
11100
|
});
|
|
@@ -10899,7 +11202,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
|
|
|
10899
11202
|
|
|
10900
11203
|
// src/services/read-ledger.ts
|
|
10901
11204
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10902
|
-
import { access as access5, copyFile, readFile as
|
|
11205
|
+
import { access as access5, copyFile, readFile as readFile21, rm } from "fs/promises";
|
|
10903
11206
|
import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
10904
11207
|
async function resolveLedgerPaths(projectRoot) {
|
|
10905
11208
|
const primaryPath = getLedgerPath(projectRoot);
|
|
@@ -10927,7 +11230,7 @@ async function readLegacyLedger(projectRoot) {
|
|
|
10927
11230
|
const { readPath } = await resolveLedgerPaths(projectRoot);
|
|
10928
11231
|
let raw;
|
|
10929
11232
|
try {
|
|
10930
|
-
raw = await
|
|
11233
|
+
raw = await readFile21(readPath, "utf8");
|
|
10931
11234
|
} catch (error) {
|
|
10932
11235
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
10933
11236
|
return [];
|
|
@@ -11182,8 +11485,8 @@ function formatError(error) {
|
|
|
11182
11485
|
}
|
|
11183
11486
|
function formatPreexistingRootMessage(projectRoot) {
|
|
11184
11487
|
const preexisting = [];
|
|
11185
|
-
if (existsSync10(
|
|
11186
|
-
if (existsSync10(
|
|
11488
|
+
if (existsSync10(join27(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
|
|
11489
|
+
if (existsSync10(join27(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
|
|
11187
11490
|
if (preexisting.length === 0) return null;
|
|
11188
11491
|
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.`;
|
|
11189
11492
|
}
|
|
@@ -11210,7 +11513,7 @@ function createFabricServer(tracker) {
|
|
|
11210
11513
|
const server = new McpServer(
|
|
11211
11514
|
{
|
|
11212
11515
|
name: "fabric-knowledge-server",
|
|
11213
|
-
version: "2.3.0-rc.
|
|
11516
|
+
version: "2.3.0-rc.6"
|
|
11214
11517
|
},
|
|
11215
11518
|
{
|
|
11216
11519
|
instructions: FABRIC_SERVER_INSTRUCTIONS
|
|
@@ -11230,10 +11533,10 @@ function createFabricServer(tracker) {
|
|
|
11230
11533
|
},
|
|
11231
11534
|
async (_uri) => {
|
|
11232
11535
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
11233
|
-
const path =
|
|
11536
|
+
const path = join27(projectRoot, ".fabric", "bootstrap", "README.md");
|
|
11234
11537
|
let text = "";
|
|
11235
11538
|
if (existsSync10(path)) {
|
|
11236
|
-
text = await
|
|
11539
|
+
text = await readFile22(path, "utf8");
|
|
11237
11540
|
}
|
|
11238
11541
|
return {
|
|
11239
11542
|
contents: [
|