@fenglimg/fabric-server 2.3.0-rc.2 → 2.3.0-rc.4
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 +200 -2
- package/dist/index.js +586 -161
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { existsSync as
|
|
3
|
-
import { readFile as
|
|
4
|
-
import { join as
|
|
2
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
4
|
+
import { join as join26, resolve as resolve4 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -820,9 +820,10 @@ function readOrphanDemoteThresholdDays(projectRoot) {
|
|
|
820
820
|
function readFusion(projectRoot) {
|
|
821
821
|
try {
|
|
822
822
|
const raw = readFabricConfig(projectRoot).fusion;
|
|
823
|
-
|
|
823
|
+
if (raw === "rrf" || raw === "additive") return raw;
|
|
824
|
+
return "auto";
|
|
824
825
|
} catch {
|
|
825
|
-
return "
|
|
826
|
+
return "auto";
|
|
826
827
|
}
|
|
827
828
|
}
|
|
828
829
|
function readConflictLintThreshold(projectRoot) {
|
|
@@ -892,7 +893,7 @@ async function awaitFirstReconcileGate(timeoutMs = 5e3) {
|
|
|
892
893
|
|
|
893
894
|
// src/services/extract-knowledge.ts
|
|
894
895
|
import { existsSync as existsSync3 } from "fs";
|
|
895
|
-
import { readFile as
|
|
896
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
896
897
|
import { join as join7 } from "path";
|
|
897
898
|
import {
|
|
898
899
|
PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE
|
|
@@ -900,6 +901,8 @@ import {
|
|
|
900
901
|
import { hasSecrets, isPersonalScope, redactPii, resolveGlobalLocale } from "@fenglimg/fabric-shared";
|
|
901
902
|
|
|
902
903
|
// src/services/cross-store-write.ts
|
|
904
|
+
import { createHash as createHash2 } from "crypto";
|
|
905
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
903
906
|
import { join as join5 } from "path";
|
|
904
907
|
import {
|
|
905
908
|
STORE_LAYOUT,
|
|
@@ -915,6 +918,7 @@ import {
|
|
|
915
918
|
PersonalScopeLeakError,
|
|
916
919
|
StoreWriteTargetUnresolvedError
|
|
917
920
|
} from "@fenglimg/fabric-shared/errors";
|
|
921
|
+
import { withFileLock as withFileLock2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
918
922
|
function writeTargetUnresolved(scope, layer) {
|
|
919
923
|
const actionHint = layer === "personal" ? "run `fabric install --global` to mint your personal store, then retry" : `mount + bind a shared store, then set an explicit route: \`fabric store switch-write <alias> --scope ${scope}\``;
|
|
920
924
|
return new StoreWriteTargetUnresolvedError(
|
|
@@ -977,7 +981,7 @@ function resolveWriteScopeMeta(layer, projectRoot, semanticScope) {
|
|
|
977
981
|
}
|
|
978
982
|
|
|
979
983
|
// src/services/cross-store-recall.ts
|
|
980
|
-
import { readFile as
|
|
984
|
+
import { readFile as readFile4, stat } from "fs/promises";
|
|
981
985
|
import { join as join6 } from "path";
|
|
982
986
|
import {
|
|
983
987
|
buildStoreResolveInput as buildStoreResolveInput2,
|
|
@@ -1349,7 +1353,7 @@ async function walkReadSetStoresUncached(snapshot) {
|
|
|
1349
1353
|
const entries = await Promise.all(snapshot.refs.map(async (ref) => {
|
|
1350
1354
|
let source;
|
|
1351
1355
|
try {
|
|
1352
|
-
source = await
|
|
1356
|
+
source = await readFile4(ref.file, "utf8");
|
|
1353
1357
|
} catch {
|
|
1354
1358
|
return null;
|
|
1355
1359
|
}
|
|
@@ -1621,6 +1625,144 @@ function serializeBm25Model(model) {
|
|
|
1621
1625
|
function rehydrateBm25Model(serialized) {
|
|
1622
1626
|
return modelFromStats(serialized);
|
|
1623
1627
|
}
|
|
1628
|
+
var SYNONYM_PAIRS = {
|
|
1629
|
+
// Code / engineering actions
|
|
1630
|
+
refactor: ["restructure", "rewrite", "redesign", "reorganize", "clean", "rework"],
|
|
1631
|
+
optimize: ["improve", "speed-up", "tune", "accelerate", "perf", "performance"],
|
|
1632
|
+
debug: ["fix", "troubleshoot", "diagnose", "investigate", "resolve", "correct"],
|
|
1633
|
+
migrate: ["port", "move", "transfer", "upgrade", "transition", "convert"],
|
|
1634
|
+
"set up": ["init", "initialize", "configure", "bootstrap", "install", "onboard"],
|
|
1635
|
+
implement: ["add", "build", "create", "develop", "write", "introduce", "introduce"],
|
|
1636
|
+
// Architecture / design
|
|
1637
|
+
architecture: ["design", "structure", "layout", "organization", "pattern", "system"],
|
|
1638
|
+
"data model": ["schema", "entity", "type", "structure", "contract", "shape"],
|
|
1639
|
+
// Quality / correctness
|
|
1640
|
+
test: ["verify", "validate", "assert", "check", "spec", "coverage"],
|
|
1641
|
+
lint: ["check", "validate", "audit", "inspect", "analyze"],
|
|
1642
|
+
// Communication / impact
|
|
1643
|
+
documentation: ["docs", "readme", "guide", "spec", "explanation", "reference"],
|
|
1644
|
+
decision: ["adr", "rationale", "why", "motivation", "reason", "trade-off"],
|
|
1645
|
+
// Change management
|
|
1646
|
+
release: ["deploy", "ship", "publish", "cut", "version", "tag"],
|
|
1647
|
+
rollback: ["revert", "undo", "back-out", "backout", "restore"],
|
|
1648
|
+
// Containers / infra
|
|
1649
|
+
container: ["docker", "image", "oci", "cri-o"],
|
|
1650
|
+
deploy: ["release", "rollout", "ship", "publish", "promote"],
|
|
1651
|
+
// Project / process
|
|
1652
|
+
on_boarding: ["getting-started", "quickstart", "newcomer", "new-hire", "first-time"],
|
|
1653
|
+
best_practice: ["convention", "guideline", "standard", "rule", "recommendation"],
|
|
1654
|
+
// Tech stacks
|
|
1655
|
+
api: ["endpoint", "route", "service", "interface", "rpc", "rest"],
|
|
1656
|
+
typescript: ["ts", "types", "type-safe"],
|
|
1657
|
+
react: ["jsx", "tsx", "component", "ui"],
|
|
1658
|
+
node: ["nodejs", "runtime", "backend", "server"],
|
|
1659
|
+
database: ["db", "sql", "nosql", "storage", "persistence"]
|
|
1660
|
+
};
|
|
1661
|
+
var STEMMING_PATTERNS = [
|
|
1662
|
+
{ suffix: "e", alternatives: ["es", "ed", "ing", "ation"] },
|
|
1663
|
+
{ suffix: "y", alternatives: ["ies", "ied", "ying"] }
|
|
1664
|
+
// Catch-all for non-verb / already-stemmed query terms: no-op by default.
|
|
1665
|
+
];
|
|
1666
|
+
var DEFAULT_IDF_WEIGHT = 1;
|
|
1667
|
+
var COMMON_TERMS = /* @__PURE__ */ new Set([
|
|
1668
|
+
"a",
|
|
1669
|
+
"an",
|
|
1670
|
+
"the",
|
|
1671
|
+
"and",
|
|
1672
|
+
"or",
|
|
1673
|
+
"but",
|
|
1674
|
+
"in",
|
|
1675
|
+
"on",
|
|
1676
|
+
"at",
|
|
1677
|
+
"to",
|
|
1678
|
+
"for",
|
|
1679
|
+
"of",
|
|
1680
|
+
"with",
|
|
1681
|
+
"by",
|
|
1682
|
+
"from",
|
|
1683
|
+
"as",
|
|
1684
|
+
"is",
|
|
1685
|
+
"it",
|
|
1686
|
+
"at",
|
|
1687
|
+
"be",
|
|
1688
|
+
"do",
|
|
1689
|
+
"has",
|
|
1690
|
+
"have",
|
|
1691
|
+
"was",
|
|
1692
|
+
"are",
|
|
1693
|
+
"been",
|
|
1694
|
+
"this",
|
|
1695
|
+
"that",
|
|
1696
|
+
"these",
|
|
1697
|
+
"those",
|
|
1698
|
+
"will",
|
|
1699
|
+
"can",
|
|
1700
|
+
"may",
|
|
1701
|
+
"would",
|
|
1702
|
+
"could",
|
|
1703
|
+
"should",
|
|
1704
|
+
"does",
|
|
1705
|
+
"not",
|
|
1706
|
+
"no",
|
|
1707
|
+
"if",
|
|
1708
|
+
"so",
|
|
1709
|
+
"up",
|
|
1710
|
+
"out",
|
|
1711
|
+
"all",
|
|
1712
|
+
"each",
|
|
1713
|
+
"every",
|
|
1714
|
+
"both",
|
|
1715
|
+
"some",
|
|
1716
|
+
"any",
|
|
1717
|
+
"such",
|
|
1718
|
+
"only",
|
|
1719
|
+
"own",
|
|
1720
|
+
"same",
|
|
1721
|
+
"too",
|
|
1722
|
+
"very",
|
|
1723
|
+
"just",
|
|
1724
|
+
"about",
|
|
1725
|
+
"over",
|
|
1726
|
+
"than",
|
|
1727
|
+
"then",
|
|
1728
|
+
"also"
|
|
1729
|
+
]);
|
|
1730
|
+
function idfWeight(term) {
|
|
1731
|
+
return COMMON_TERMS.has(term) ? 0.3 : DEFAULT_IDF_WEIGHT;
|
|
1732
|
+
}
|
|
1733
|
+
function expandQueryTerms(text) {
|
|
1734
|
+
const baseTerms = tokenize(text);
|
|
1735
|
+
const weighted = /* @__PURE__ */ new Map();
|
|
1736
|
+
for (const term of baseTerms) {
|
|
1737
|
+
const lower = term.toLowerCase();
|
|
1738
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
1739
|
+
const syns = SYNONYM_PAIRS[lower];
|
|
1740
|
+
if (syns !== void 0) {
|
|
1741
|
+
for (const s of syns) aliases.add(s);
|
|
1742
|
+
}
|
|
1743
|
+
for (const [key, values] of Object.entries(SYNONYM_PAIRS)) {
|
|
1744
|
+
if (values.includes(lower)) {
|
|
1745
|
+
aliases.add(key);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
for (const { suffix, alternatives } of STEMMING_PATTERNS) {
|
|
1749
|
+
if (lower.endsWith(suffix) && lower.length > suffix.length) {
|
|
1750
|
+
const stem = lower.slice(0, -suffix.length);
|
|
1751
|
+
for (const alt of alternatives) {
|
|
1752
|
+
aliases.add(stem + alt);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
const originalWeight = idfWeight(lower);
|
|
1757
|
+
weighted.set(term, originalWeight);
|
|
1758
|
+
for (const alias of aliases) {
|
|
1759
|
+
if (!weighted.has(alias)) {
|
|
1760
|
+
weighted.set(alias, originalWeight * 0.5);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return weighted;
|
|
1765
|
+
}
|
|
1624
1766
|
function buildQueryTerms(text) {
|
|
1625
1767
|
return tokenize(text);
|
|
1626
1768
|
}
|
|
@@ -2020,7 +2162,7 @@ async function extractKnowledge(projectRoot, input) {
|
|
|
2020
2162
|
const writeScopeMeta = resolveWriteScopeMeta(layer, projectRoot, semanticScope);
|
|
2021
2163
|
await ensureParentDirectory(absolutePath);
|
|
2022
2164
|
if (existsSync3(absolutePath)) {
|
|
2023
|
-
const existing = await
|
|
2165
|
+
const existing = await readFile5(absolutePath, "utf8");
|
|
2024
2166
|
const existingKey = readFrontmatterKey(existing, "x-fabric-idempotency-key");
|
|
2025
2167
|
if (existingKey === effectiveIdempotencyKey) {
|
|
2026
2168
|
const fresh2 = renderFreshEntry({
|
|
@@ -2153,7 +2295,7 @@ async function resolveDisambiguatedSlugPath(args) {
|
|
|
2153
2295
|
idempotencyKey: candidateKey
|
|
2154
2296
|
};
|
|
2155
2297
|
}
|
|
2156
|
-
const existing = await
|
|
2298
|
+
const existing = await readFile5(candidatePath, "utf8");
|
|
2157
2299
|
const existingKey = readFrontmatterKey(existing, "x-fabric-idempotency-key");
|
|
2158
2300
|
if (existingKey === candidateKey) {
|
|
2159
2301
|
return {
|
|
@@ -2751,12 +2893,24 @@ var LEDGER_DUAL_WRITE_METRIC_NAMES = {
|
|
|
2751
2893
|
};
|
|
2752
2894
|
|
|
2753
2895
|
// src/services/plan-context.ts
|
|
2754
|
-
import { mkdir as mkdir4, readFile as
|
|
2755
|
-
import { join as
|
|
2896
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
2897
|
+
import { join as join10 } from "path";
|
|
2756
2898
|
|
|
2757
2899
|
// src/services/vector-retrieval.ts
|
|
2900
|
+
import { mkdirSync } from "fs";
|
|
2901
|
+
import { createRequire } from "module";
|
|
2902
|
+
import { join as join9 } from "path";
|
|
2903
|
+
import { resolveGlobalRoot as resolveGlobalRoot3 } from "@fenglimg/fabric-shared";
|
|
2758
2904
|
var embedderLoad;
|
|
2759
2905
|
var OPTIONAL_EMBED_PACKAGE = "fastembed";
|
|
2906
|
+
function isEmbedderResolvable() {
|
|
2907
|
+
try {
|
|
2908
|
+
createRequire(import.meta.url).resolve(OPTIONAL_EMBED_PACKAGE);
|
|
2909
|
+
return true;
|
|
2910
|
+
} catch {
|
|
2911
|
+
return false;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2760
2914
|
var embedderModuleLoader = (name) => import(name);
|
|
2761
2915
|
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
2916
|
var defaultMissingEmbedderHint = () => {
|
|
@@ -2771,10 +2925,13 @@ function hintMissingEmbedderOnce() {
|
|
|
2771
2925
|
missingEmbedderHinted = true;
|
|
2772
2926
|
emitMissingEmbedderHint();
|
|
2773
2927
|
}
|
|
2928
|
+
function defaultEmbedCacheDir() {
|
|
2929
|
+
return join9(resolveGlobalRoot3(), "cache", "embed");
|
|
2930
|
+
}
|
|
2774
2931
|
function buildEmbedInitOptions(modelName) {
|
|
2775
2932
|
return {
|
|
2776
2933
|
maxLength: 512,
|
|
2777
|
-
cacheDir: process.env.FABRIC_EMBED_CACHE_DIR,
|
|
2934
|
+
cacheDir: process.env.FABRIC_EMBED_CACHE_DIR ?? defaultEmbedCacheDir(),
|
|
2778
2935
|
...typeof modelName === "string" && modelName.length > 0 ? { model: modelName } : {}
|
|
2779
2936
|
};
|
|
2780
2937
|
}
|
|
@@ -2788,7 +2945,9 @@ async function loadEmbedder(modelName) {
|
|
|
2788
2945
|
hintMissingEmbedderOnce();
|
|
2789
2946
|
return null;
|
|
2790
2947
|
}
|
|
2791
|
-
const
|
|
2948
|
+
const initOpts = buildEmbedInitOptions(modelName);
|
|
2949
|
+
mkdirSync(initOpts.cacheDir, { recursive: true });
|
|
2950
|
+
const model = await mod.FlagEmbedding.init(initOpts);
|
|
2792
2951
|
return {
|
|
2793
2952
|
async embed(texts) {
|
|
2794
2953
|
const out = [];
|
|
@@ -3157,11 +3316,11 @@ var bm25BuildCount = 0;
|
|
|
3157
3316
|
var BM25_CACHE_DIR = ".fabric/cache/bm25";
|
|
3158
3317
|
function bm25CachePath(projectRoot, revision) {
|
|
3159
3318
|
const safe = revision.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
3160
|
-
return
|
|
3319
|
+
return join10(projectRoot, BM25_CACHE_DIR, `${safe}.json`);
|
|
3161
3320
|
}
|
|
3162
3321
|
async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
3163
3322
|
try {
|
|
3164
|
-
const raw = await
|
|
3323
|
+
const raw = await readFile6(bm25CachePath(projectRoot, revision), "utf8");
|
|
3165
3324
|
const parsed = JSON.parse(raw);
|
|
3166
3325
|
if (parsed.version !== 1) return null;
|
|
3167
3326
|
return rehydrateBm25Model(parsed);
|
|
@@ -3172,7 +3331,7 @@ async function loadBm25ModelFromDisk(projectRoot, revision) {
|
|
|
3172
3331
|
async function saveBm25ModelToDisk(projectRoot, revision, model) {
|
|
3173
3332
|
try {
|
|
3174
3333
|
const path = bm25CachePath(projectRoot, revision);
|
|
3175
|
-
await mkdir4(
|
|
3334
|
+
await mkdir4(join10(projectRoot, BM25_CACHE_DIR), { recursive: true });
|
|
3176
3335
|
await writeFile2(path, JSON.stringify(serializeBm25Model(model)), "utf8");
|
|
3177
3336
|
} catch {
|
|
3178
3337
|
}
|
|
@@ -3271,7 +3430,13 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
|
|
|
3271
3430
|
scoringContext.vectorWeight = embedConfig.weight;
|
|
3272
3431
|
}
|
|
3273
3432
|
}
|
|
3274
|
-
|
|
3433
|
+
const configuredFusion = readFusion(projectRoot);
|
|
3434
|
+
const vectorActive = scoringContext.vectorScores !== void 0 && scoringContext.vectorScores.size > 0;
|
|
3435
|
+
scoringContext.fusion = configuredFusion === "auto" ? vectorActive ? "rrf" : "additive" : configuredFusion;
|
|
3436
|
+
let queryTermWeights;
|
|
3437
|
+
if (scoringContext.queryTerms.length > 0) {
|
|
3438
|
+
queryTermWeights = expandQueryTerms(opts.queryText);
|
|
3439
|
+
}
|
|
3275
3440
|
if (scoringContext.fusion === "rrf" && scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
|
|
3276
3441
|
const rankIds = rawItems.map((item) => item.stable_id).sort((a, b) => compareStableIds(a, b));
|
|
3277
3442
|
if (scoringContext.bm25 !== void 0) {
|
|
@@ -3283,6 +3448,49 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
|
|
|
3283
3448
|
}
|
|
3284
3449
|
return scoringContext;
|
|
3285
3450
|
}
|
|
3451
|
+
var PROXIMITY_WINDOW = 6;
|
|
3452
|
+
var PROXIMITY_BOOST_CAP = 0.15;
|
|
3453
|
+
function proximityBoost(item, context, contentScore2) {
|
|
3454
|
+
if (contentScore2 <= 0) return 0;
|
|
3455
|
+
const text = context.docTexts?.get(item.stable_id);
|
|
3456
|
+
if (text === void 0 || text.length === 0) return 0;
|
|
3457
|
+
const tokens = text.toLowerCase().split(/[^a-z0-9_$#]+/u).filter(Boolean);
|
|
3458
|
+
const queryTerms = context.queryTerms;
|
|
3459
|
+
if (queryTerms.length < 2) return 0;
|
|
3460
|
+
const positions = /* @__PURE__ */ new Map();
|
|
3461
|
+
for (const qt of queryTerms) {
|
|
3462
|
+
const qtLower = qt.toLowerCase();
|
|
3463
|
+
const pos = [];
|
|
3464
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3465
|
+
if (tokens[i] === qtLower) {
|
|
3466
|
+
pos.push(i);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
if (pos.length > 0) {
|
|
3470
|
+
positions.set(qtLower, pos);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
if (positions.size < 2) return 0;
|
|
3474
|
+
const termList = [...positions.entries()];
|
|
3475
|
+
let minDist = Infinity;
|
|
3476
|
+
for (let i = 0; i < termList.length; i++) {
|
|
3477
|
+
const [, posI] = termList[i];
|
|
3478
|
+
for (let j = i + 1; j < termList.length; j++) {
|
|
3479
|
+
const [, posJ] = termList[j];
|
|
3480
|
+
for (const pi of posI) {
|
|
3481
|
+
for (const pj of posJ) {
|
|
3482
|
+
const dist = Math.abs(pi - pj);
|
|
3483
|
+
if (dist < minDist) minDist = dist;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
if (!Number.isFinite(minDist)) return 0;
|
|
3489
|
+
if (minDist >= PROXIMITY_WINDOW) return 0;
|
|
3490
|
+
const ratio = 1 - minDist / PROXIMITY_WINDOW;
|
|
3491
|
+
const boost = contentScore2 * PROXIMITY_BOOST_CAP * ratio;
|
|
3492
|
+
return boost;
|
|
3493
|
+
}
|
|
3286
3494
|
function sortDescriptionItems(rawItems, scoringContext) {
|
|
3287
3495
|
if (scoringContext === void 0) {
|
|
3288
3496
|
return [...rawItems].sort((left, right) => compareStableIds(left.stable_id, right.stable_id)).map((item) => ({ item, score: 0 }));
|
|
@@ -3425,7 +3633,8 @@ function structuralScaleFor(context) {
|
|
|
3425
3633
|
function scoreDescriptionItem(item, context) {
|
|
3426
3634
|
const content = contentScore(item, context);
|
|
3427
3635
|
const structural = salienceScore(item) + recencyBoost(item, context) + localityBoost(item, context);
|
|
3428
|
-
|
|
3636
|
+
const proximity = proximityBoost(item, context, content);
|
|
3637
|
+
return content + structuralScaleFor(context) * structural + proximity;
|
|
3429
3638
|
}
|
|
3430
3639
|
function scoreBreakdownForItem(item, context) {
|
|
3431
3640
|
const hasQuery = context.queryTerms.length > 0;
|
|
@@ -3907,9 +4116,9 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
|
|
|
3907
4116
|
// src/services/review.ts
|
|
3908
4117
|
import { execFileSync } from "child_process";
|
|
3909
4118
|
import { existsSync as existsSync5 } from "fs";
|
|
3910
|
-
import { readFile as
|
|
4119
|
+
import { readFile as readFile8, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
3911
4120
|
import { homedir } from "os";
|
|
3912
|
-
import { basename, isAbsolute, join as
|
|
4121
|
+
import { basename, isAbsolute, join as join12, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
3913
4122
|
|
|
3914
4123
|
// src/services/promotion-gate.ts
|
|
3915
4124
|
function toLocalId(id) {
|
|
@@ -3949,8 +4158,8 @@ import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@
|
|
|
3949
4158
|
|
|
3950
4159
|
// src/services/pending-dedupe.ts
|
|
3951
4160
|
import { existsSync as existsSync4 } from "fs";
|
|
3952
|
-
import { readdir, readFile as
|
|
3953
|
-
import { join as
|
|
4161
|
+
import { readdir, readFile as readFile7, unlink } from "fs/promises";
|
|
4162
|
+
import { join as join11 } from "path";
|
|
3954
4163
|
var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
|
|
3955
4164
|
var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
|
|
3956
4165
|
var SOURCE_SESSIONS_LINE = /^source_sessions:\s*\[(.*)\]\s*$/mu;
|
|
@@ -4023,7 +4232,7 @@ async function mergePendingTwins(projectRoot) {
|
|
|
4023
4232
|
continue;
|
|
4024
4233
|
}
|
|
4025
4234
|
for (const type of PENDING_TYPES) {
|
|
4026
|
-
const dir =
|
|
4235
|
+
const dir = join11(pendingBase2, type);
|
|
4027
4236
|
if (!existsSync4(dir)) continue;
|
|
4028
4237
|
let names;
|
|
4029
4238
|
try {
|
|
@@ -4044,10 +4253,10 @@ async function mergePendingTwins(projectRoot) {
|
|
|
4044
4253
|
if (groupNames.length < 2) continue;
|
|
4045
4254
|
const parsed = [];
|
|
4046
4255
|
for (const name of groupNames) {
|
|
4047
|
-
const abs =
|
|
4256
|
+
const abs = join11(dir, name);
|
|
4048
4257
|
let content;
|
|
4049
4258
|
try {
|
|
4050
|
-
content = await
|
|
4259
|
+
content = await readFile7(abs, "utf8");
|
|
4051
4260
|
} catch {
|
|
4052
4261
|
continue;
|
|
4053
4262
|
}
|
|
@@ -4227,7 +4436,7 @@ async function listPending(projectRoot, filters) {
|
|
|
4227
4436
|
}
|
|
4228
4437
|
for (const source of sources) {
|
|
4229
4438
|
for (const type of typesToScan) {
|
|
4230
|
-
const dir =
|
|
4439
|
+
const dir = join12(source.root, type);
|
|
4231
4440
|
if (!existsSync5(dir)) {
|
|
4232
4441
|
continue;
|
|
4233
4442
|
}
|
|
@@ -4239,10 +4448,10 @@ async function listPending(projectRoot, filters) {
|
|
|
4239
4448
|
}
|
|
4240
4449
|
for (const name of entries) {
|
|
4241
4450
|
if (!name.endsWith(".md")) continue;
|
|
4242
|
-
const absolutePath =
|
|
4451
|
+
const absolutePath = join12(dir, name);
|
|
4243
4452
|
let content;
|
|
4244
4453
|
try {
|
|
4245
|
-
content = await
|
|
4454
|
+
content = await readFile8(absolutePath, "utf8");
|
|
4246
4455
|
} catch {
|
|
4247
4456
|
continue;
|
|
4248
4457
|
}
|
|
@@ -4352,7 +4561,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4352
4561
|
let targetAbs;
|
|
4353
4562
|
let writtenTarget = false;
|
|
4354
4563
|
try {
|
|
4355
|
-
const content = await
|
|
4564
|
+
const content = await readFile8(sourceAbs, "utf8");
|
|
4356
4565
|
const fm = parseFrontmatter(content);
|
|
4357
4566
|
const pluralType = fm.type;
|
|
4358
4567
|
if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
|
|
@@ -4366,7 +4575,7 @@ async function approveOne(projectRoot, pendingPath) {
|
|
|
4366
4575
|
);
|
|
4367
4576
|
allocatedId = stableId;
|
|
4368
4577
|
const newFilename = `${stableId}--${slug}.md`;
|
|
4369
|
-
targetAbs =
|
|
4578
|
+
targetAbs = join12(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
|
|
4370
4579
|
await ensureParentDirectory(targetAbs);
|
|
4371
4580
|
const rewritten = rewriteFrontmatterMerge(
|
|
4372
4581
|
rewriteFrontmatterForPromote(content, stableId),
|
|
@@ -4424,7 +4633,7 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
|
|
|
4424
4633
|
try {
|
|
4425
4634
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4426
4635
|
if (existsSync5(sandboxed.abs)) {
|
|
4427
|
-
const content = await
|
|
4636
|
+
const content = await readFile8(sandboxed.abs, "utf8");
|
|
4428
4637
|
const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
|
|
4429
4638
|
const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
|
|
4430
4639
|
if (rejectedAbs !== null) {
|
|
@@ -4451,7 +4660,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
|
|
|
4451
4660
|
if (target === null) {
|
|
4452
4661
|
throw new Error(`modify target not found: ${pendingPath}`);
|
|
4453
4662
|
}
|
|
4454
|
-
const content = await
|
|
4663
|
+
const content = await readFile8(target.absPath, "utf8");
|
|
4455
4664
|
const fm = parseFrontmatter(content);
|
|
4456
4665
|
const currentLayer = fm.layer ?? "team";
|
|
4457
4666
|
if (fm.maturity === "verified" && changes.maturity === "proven" && fm.id !== void 0) {
|
|
@@ -4553,7 +4762,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
4553
4762
|
pluralType,
|
|
4554
4763
|
resolveWriteTargetStoreDir(toLayer, projectRoot)
|
|
4555
4764
|
);
|
|
4556
|
-
const toAbs =
|
|
4765
|
+
const toAbs = join12(
|
|
4557
4766
|
resolveStoreCanonicalBase(toLayer, projectRoot),
|
|
4558
4767
|
pluralType,
|
|
4559
4768
|
`${newStableId}--${slug}.md`
|
|
@@ -4653,7 +4862,7 @@ function getSearchDirectoryCache(cacheKey) {
|
|
|
4653
4862
|
return created;
|
|
4654
4863
|
}
|
|
4655
4864
|
async function listIndexedSearchEntries(source, type) {
|
|
4656
|
-
const dir =
|
|
4865
|
+
const dir = join12(source.root, type);
|
|
4657
4866
|
let entries;
|
|
4658
4867
|
try {
|
|
4659
4868
|
entries = await readdir2(dir);
|
|
@@ -4666,7 +4875,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4666
4875
|
const indexed = [];
|
|
4667
4876
|
for (const name of entries) {
|
|
4668
4877
|
if (!name.endsWith(".md")) continue;
|
|
4669
|
-
const absolutePath =
|
|
4878
|
+
const absolutePath = join12(dir, name);
|
|
4670
4879
|
let fingerprint;
|
|
4671
4880
|
try {
|
|
4672
4881
|
const st = await stat2(absolutePath);
|
|
@@ -4683,7 +4892,7 @@ async function listIndexedSearchEntries(source, type) {
|
|
|
4683
4892
|
}
|
|
4684
4893
|
let content;
|
|
4685
4894
|
try {
|
|
4686
|
-
content = await
|
|
4895
|
+
content = await readFile8(absolutePath, "utf8");
|
|
4687
4896
|
searchEntryIndexContentReads += 1;
|
|
4688
4897
|
} catch {
|
|
4689
4898
|
directoryCache.files.delete(absolutePath);
|
|
@@ -4852,7 +5061,7 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
|
4852
5061
|
try {
|
|
4853
5062
|
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
4854
5063
|
if (existsSync5(sandboxed.abs)) {
|
|
4855
|
-
const content = await
|
|
5064
|
+
const content = await readFile8(sandboxed.abs, "utf8");
|
|
4856
5065
|
stableId = parseFrontmatter(content).id;
|
|
4857
5066
|
const patch = {
|
|
4858
5067
|
status: "deferred",
|
|
@@ -5193,9 +5402,9 @@ function registerPending(server, tracker) {
|
|
|
5193
5402
|
}
|
|
5194
5403
|
|
|
5195
5404
|
// src/services/doctor.ts
|
|
5196
|
-
import { access as access4, readFile as
|
|
5405
|
+
import { access as access4, readFile as readFile18, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
5197
5406
|
import { constants as constants2 } from "fs";
|
|
5198
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5407
|
+
import { isAbsolute as isAbsolute2, join as join23, posix as posix5, resolve as resolve3 } from "path";
|
|
5199
5408
|
import {
|
|
5200
5409
|
createTranslator,
|
|
5201
5410
|
forensicReportSchema,
|
|
@@ -5409,13 +5618,13 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
|
|
|
5409
5618
|
}
|
|
5410
5619
|
|
|
5411
5620
|
// src/services/doctor-stable-id-collision.ts
|
|
5412
|
-
import { readFile as
|
|
5413
|
-
import { basename as basename2, join as
|
|
5621
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5622
|
+
import { basename as basename2, join as join13 } from "path";
|
|
5414
5623
|
import {
|
|
5415
5624
|
buildStoreResolveInput as buildStoreResolveInput4,
|
|
5416
5625
|
createStoreResolver as createStoreResolver4,
|
|
5417
5626
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores2,
|
|
5418
|
-
resolveGlobalRoot as
|
|
5627
|
+
resolveGlobalRoot as resolveGlobalRoot4,
|
|
5419
5628
|
storeRelativePathForMount as storeRelativePathForMount3
|
|
5420
5629
|
} from "@fenglimg/fabric-shared";
|
|
5421
5630
|
var ID_LINE = /^id:\s*"?([^"\n]+?)"?\s*$/mu;
|
|
@@ -5431,13 +5640,13 @@ function resolveIntegrityStores(projectRoot) {
|
|
|
5431
5640
|
const personalUuids = new Set(
|
|
5432
5641
|
input.mountedStores.filter((s) => s.personal).map((s) => s.store_uuid)
|
|
5433
5642
|
);
|
|
5434
|
-
const globalRoot =
|
|
5643
|
+
const globalRoot = resolveGlobalRoot4();
|
|
5435
5644
|
const dirs = readSet.stores.map((entry) => {
|
|
5436
5645
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
5437
5646
|
return {
|
|
5438
5647
|
store_uuid: entry.store_uuid,
|
|
5439
5648
|
alias: entry.alias,
|
|
5440
|
-
dir:
|
|
5649
|
+
dir: join13(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
|
|
5441
5650
|
};
|
|
5442
5651
|
});
|
|
5443
5652
|
return { dirs, personalUuids };
|
|
@@ -5456,7 +5665,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
|
|
|
5456
5665
|
for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
|
|
5457
5666
|
let source;
|
|
5458
5667
|
try {
|
|
5459
|
-
source = await
|
|
5668
|
+
source = await readFile9(ref.file, "utf8");
|
|
5460
5669
|
} catch {
|
|
5461
5670
|
continue;
|
|
5462
5671
|
}
|
|
@@ -5541,7 +5750,7 @@ function createLayerMismatchCheck(t, inspection) {
|
|
|
5541
5750
|
// src/services/doctor-relevance-paths.ts
|
|
5542
5751
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5543
5752
|
import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
|
|
5544
|
-
import { join as
|
|
5753
|
+
import { join as join14, sep as sep3 } from "path";
|
|
5545
5754
|
import { minimatch } from "minimatch";
|
|
5546
5755
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
5547
5756
|
var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
|
|
@@ -5584,7 +5793,7 @@ function collectWorkspacePaths(projectRoot) {
|
|
|
5584
5793
|
continue;
|
|
5585
5794
|
}
|
|
5586
5795
|
for (const entry of entries) {
|
|
5587
|
-
const abs =
|
|
5796
|
+
const abs = join14(current, entry.name);
|
|
5588
5797
|
const rel = toPosix(abs.slice(projectRoot.length + 1));
|
|
5589
5798
|
if (rel.length === 0) continue;
|
|
5590
5799
|
if (entry.isDirectory()) {
|
|
@@ -5777,16 +5986,16 @@ function createNarrowNoPathsCheck(t, inspection) {
|
|
|
5777
5986
|
}
|
|
5778
5987
|
|
|
5779
5988
|
// src/services/doctor-broad-index.ts
|
|
5780
|
-
import { readFile as
|
|
5781
|
-
import { join as
|
|
5989
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
5990
|
+
import { join as join15 } from "path";
|
|
5782
5991
|
var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
|
|
5783
5992
|
var BROAD_INDEX_BACKSTOP_MIN = 20;
|
|
5784
5993
|
var BROAD_INDEX_BACKSTOP_MAX = 500;
|
|
5785
5994
|
var BROAD_INDEX_DRIFT_RATIO = 0.8;
|
|
5786
5995
|
async function readBroadIndexBackstop(projectRoot) {
|
|
5787
|
-
const configPath =
|
|
5996
|
+
const configPath = join15(projectRoot, ".fabric", "fabric-config.json");
|
|
5788
5997
|
try {
|
|
5789
|
-
const raw = await
|
|
5998
|
+
const raw = await readFile10(configPath, "utf8");
|
|
5790
5999
|
const parsed = JSON.parse(raw);
|
|
5791
6000
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
5792
6001
|
const v = parsed.broad_index_backstop;
|
|
@@ -6103,15 +6312,15 @@ function createBroadReviewRecheckCheck(t, inspection) {
|
|
|
6103
6312
|
}
|
|
6104
6313
|
|
|
6105
6314
|
// src/services/doctor-scope-lint.ts
|
|
6106
|
-
import { readFile as
|
|
6107
|
-
import { join as
|
|
6315
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
6316
|
+
import { join as join16 } from "path";
|
|
6108
6317
|
import {
|
|
6109
6318
|
buildStoreResolveInput as buildStoreResolveInput5,
|
|
6110
6319
|
createStoreResolver as createStoreResolver5,
|
|
6111
6320
|
isPersonalScope as isPersonalScope3,
|
|
6112
6321
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores3,
|
|
6113
6322
|
readStoreProjects,
|
|
6114
|
-
resolveGlobalRoot as
|
|
6323
|
+
resolveGlobalRoot as resolveGlobalRoot5,
|
|
6115
6324
|
scopeRoot as scopeRoot2,
|
|
6116
6325
|
storeRelativePathForMount as storeRelativePathForMount4
|
|
6117
6326
|
} from "@fenglimg/fabric-shared";
|
|
@@ -6136,10 +6345,10 @@ async function resolveLintStores(projectRoot) {
|
|
|
6136
6345
|
const personalUuids = new Set(
|
|
6137
6346
|
input.mountedStores.filter((s) => s.personal).map((s) => s.store_uuid)
|
|
6138
6347
|
);
|
|
6139
|
-
const globalRoot =
|
|
6348
|
+
const globalRoot = resolveGlobalRoot5();
|
|
6140
6349
|
return Promise.all(readSet.stores.map(async (entry) => {
|
|
6141
6350
|
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
6142
|
-
const dir =
|
|
6351
|
+
const dir = join16(
|
|
6143
6352
|
globalRoot,
|
|
6144
6353
|
storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
|
|
6145
6354
|
);
|
|
@@ -6171,7 +6380,7 @@ async function lintStoreScopes(projectRoot) {
|
|
|
6171
6380
|
}
|
|
6172
6381
|
let source;
|
|
6173
6382
|
try {
|
|
6174
|
-
source = await
|
|
6383
|
+
source = await readFile11(ref.file, "utf8");
|
|
6175
6384
|
} catch {
|
|
6176
6385
|
continue;
|
|
6177
6386
|
}
|
|
@@ -6294,7 +6503,7 @@ function createUnboundProjectCheck(t, violation) {
|
|
|
6294
6503
|
|
|
6295
6504
|
// src/services/doctor-store-counters.ts
|
|
6296
6505
|
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
6297
|
-
import { join as
|
|
6506
|
+
import { join as join17 } from "path";
|
|
6298
6507
|
import {
|
|
6299
6508
|
buildStoreResolveInput as buildStoreResolveInput6,
|
|
6300
6509
|
createStoreResolver as createStoreResolver6,
|
|
@@ -6303,7 +6512,7 @@ import {
|
|
|
6303
6512
|
parseKnowledgeId as parseKnowledgeId2,
|
|
6304
6513
|
readStoreCounters,
|
|
6305
6514
|
reconcileStoreCounters,
|
|
6306
|
-
resolveGlobalRoot as
|
|
6515
|
+
resolveGlobalRoot as resolveGlobalRoot6,
|
|
6307
6516
|
STORE_KNOWLEDGE_TYPE_DIRS,
|
|
6308
6517
|
STORE_LAYOUT as STORE_LAYOUT2,
|
|
6309
6518
|
storeRelativePathForMount as storeRelativePathForMount5
|
|
@@ -6317,11 +6526,11 @@ function resolveCounterStores(projectRoot) {
|
|
|
6317
6526
|
if (readSet.stores.length === 0) {
|
|
6318
6527
|
return [];
|
|
6319
6528
|
}
|
|
6320
|
-
const globalRoot =
|
|
6529
|
+
const globalRoot = resolveGlobalRoot6();
|
|
6321
6530
|
return readSet.stores.map((entry) => ({
|
|
6322
6531
|
uuid: entry.store_uuid,
|
|
6323
6532
|
alias: entry.alias,
|
|
6324
|
-
dir:
|
|
6533
|
+
dir: join17(
|
|
6325
6534
|
globalRoot,
|
|
6326
6535
|
storeRelativePathForMount5(
|
|
6327
6536
|
input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
|
|
@@ -6349,7 +6558,7 @@ function readEntryId(file) {
|
|
|
6349
6558
|
function computeStoreDiskMax(storeDir) {
|
|
6350
6559
|
const max = defaultAgentsMetaCounters();
|
|
6351
6560
|
for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
6352
|
-
const dir =
|
|
6561
|
+
const dir = join17(storeDir, STORE_LAYOUT2.knowledgeDir, type);
|
|
6353
6562
|
if (!existsSync7(dir)) {
|
|
6354
6563
|
continue;
|
|
6355
6564
|
}
|
|
@@ -6363,7 +6572,7 @@ function computeStoreDiskMax(storeDir) {
|
|
|
6363
6572
|
if (!name.endsWith(".md")) {
|
|
6364
6573
|
continue;
|
|
6365
6574
|
}
|
|
6366
|
-
const parsed = parseKnowledgeId2(readEntryId(
|
|
6575
|
+
const parsed = parseKnowledgeId2(readEntryId(join17(dir, name)) ?? "");
|
|
6367
6576
|
if (parsed === null) {
|
|
6368
6577
|
continue;
|
|
6369
6578
|
}
|
|
@@ -6446,14 +6655,14 @@ function createStoreCounterCheck(t, drifts) {
|
|
|
6446
6655
|
|
|
6447
6656
|
// src/services/doctor-store-orphan.ts
|
|
6448
6657
|
import { readdirSync as readdirSync3 } from "fs";
|
|
6449
|
-
import { join as
|
|
6658
|
+
import { join as join18 } from "path";
|
|
6450
6659
|
import {
|
|
6451
6660
|
STORES_ROOT_DIR,
|
|
6452
6661
|
addMountedStore,
|
|
6453
6662
|
disambiguateAlias,
|
|
6454
6663
|
loadGlobalConfig,
|
|
6455
6664
|
readStoreIdentity,
|
|
6456
|
-
resolveGlobalRoot as
|
|
6665
|
+
resolveGlobalRoot as resolveGlobalRoot7,
|
|
6457
6666
|
saveGlobalConfig
|
|
6458
6667
|
} from "@fenglimg/fabric-shared";
|
|
6459
6668
|
var STORE_BY_ALIAS_DIR = "by-alias";
|
|
@@ -6464,21 +6673,21 @@ function listDir(dir) {
|
|
|
6464
6673
|
return [];
|
|
6465
6674
|
}
|
|
6466
6675
|
}
|
|
6467
|
-
function inspectStoreOrphans(globalRoot =
|
|
6676
|
+
function inspectStoreOrphans(globalRoot = resolveGlobalRoot7()) {
|
|
6468
6677
|
const config = loadGlobalConfig(globalRoot);
|
|
6469
6678
|
if (config === null) {
|
|
6470
6679
|
return [];
|
|
6471
6680
|
}
|
|
6472
6681
|
const registered = new Set(config.stores.map((s) => s.store_uuid));
|
|
6473
|
-
const storesRoot =
|
|
6682
|
+
const storesRoot = join18(globalRoot, STORES_ROOT_DIR);
|
|
6474
6683
|
const orphans = [];
|
|
6475
6684
|
for (const group of listDir(storesRoot)) {
|
|
6476
6685
|
if (group === STORE_BY_ALIAS_DIR) {
|
|
6477
6686
|
continue;
|
|
6478
6687
|
}
|
|
6479
|
-
const groupDir =
|
|
6688
|
+
const groupDir = join18(storesRoot, group);
|
|
6480
6689
|
for (const mount of listDir(groupDir)) {
|
|
6481
|
-
const dir =
|
|
6690
|
+
const dir = join18(groupDir, mount);
|
|
6482
6691
|
const identity = readStoreIdentity(dir);
|
|
6483
6692
|
if (identity === null || registered.has(identity.store_uuid)) {
|
|
6484
6693
|
continue;
|
|
@@ -6488,7 +6697,7 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
|
|
|
6488
6697
|
}
|
|
6489
6698
|
return orphans;
|
|
6490
6699
|
}
|
|
6491
|
-
function fixStoreOrphans(globalRoot =
|
|
6700
|
+
function fixStoreOrphans(globalRoot = resolveGlobalRoot7()) {
|
|
6492
6701
|
let config = loadGlobalConfig(globalRoot);
|
|
6493
6702
|
if (config === null) {
|
|
6494
6703
|
return [];
|
|
@@ -6615,8 +6824,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
|
|
|
6615
6824
|
}
|
|
6616
6825
|
|
|
6617
6826
|
// src/services/doctor-skill-lints.ts
|
|
6618
|
-
import { readdir as readdir3, readFile as
|
|
6619
|
-
import { join as
|
|
6827
|
+
import { readdir as readdir3, readFile as readFile12 } from "fs/promises";
|
|
6828
|
+
import { join as join19, posix as posix2 } from "path";
|
|
6620
6829
|
var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review"];
|
|
6621
6830
|
var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
|
|
6622
6831
|
var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
|
|
@@ -6646,8 +6855,8 @@ async function listMarkdownFiles(dir) {
|
|
|
6646
6855
|
async function inspectSkillRefMirror(projectRoot) {
|
|
6647
6856
|
const driftedPaths = [];
|
|
6648
6857
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6649
|
-
const claudeRef =
|
|
6650
|
-
const codexRef =
|
|
6858
|
+
const claudeRef = join19(projectRoot, ".claude", "skills", slug, "ref");
|
|
6859
|
+
const codexRef = join19(projectRoot, ".codex", "skills", slug, "ref");
|
|
6651
6860
|
const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
|
|
6652
6861
|
if (claudeFiles === null || codexFiles === null) continue;
|
|
6653
6862
|
const claudeSet = new Set(claudeFiles);
|
|
@@ -6664,8 +6873,8 @@ async function inspectSkillRefMirror(projectRoot) {
|
|
|
6664
6873
|
let codexBody;
|
|
6665
6874
|
try {
|
|
6666
6875
|
[claudeBody, codexBody] = await Promise.all([
|
|
6667
|
-
|
|
6668
|
-
|
|
6876
|
+
readFile12(join19(claudeRef, fname), "utf8"),
|
|
6877
|
+
readFile12(join19(codexRef, fname), "utf8")
|
|
6669
6878
|
]);
|
|
6670
6879
|
} catch {
|
|
6671
6880
|
continue;
|
|
@@ -6684,10 +6893,10 @@ async function inspectSkillTokenBudget(projectRoot) {
|
|
|
6684
6893
|
const overSize = [];
|
|
6685
6894
|
let highestSeverity = "ok";
|
|
6686
6895
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6687
|
-
const skillMdPath =
|
|
6896
|
+
const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6688
6897
|
let body;
|
|
6689
6898
|
try {
|
|
6690
|
-
body = await
|
|
6899
|
+
body = await readFile12(skillMdPath, "utf8");
|
|
6691
6900
|
} catch {
|
|
6692
6901
|
continue;
|
|
6693
6902
|
}
|
|
@@ -6708,10 +6917,10 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6708
6917
|
const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
|
|
6709
6918
|
const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
|
|
6710
6919
|
for (const slug of FABRIC_SKILL_SLUGS) {
|
|
6711
|
-
const skillMdPath =
|
|
6920
|
+
const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
|
|
6712
6921
|
let body;
|
|
6713
6922
|
try {
|
|
6714
|
-
body = await
|
|
6923
|
+
body = await readFile12(skillMdPath, "utf8");
|
|
6715
6924
|
} catch {
|
|
6716
6925
|
continue;
|
|
6717
6926
|
}
|
|
@@ -6742,7 +6951,7 @@ async function inspectSkillDescription(projectRoot) {
|
|
|
6742
6951
|
async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
6743
6952
|
const candidates = [];
|
|
6744
6953
|
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
6745
|
-
const rootAbs =
|
|
6954
|
+
const rootAbs = join19(projectRoot, rootRel);
|
|
6746
6955
|
let dirEntries;
|
|
6747
6956
|
try {
|
|
6748
6957
|
dirEntries = await readdir3(rootAbs, { withFileTypes: true });
|
|
@@ -6751,10 +6960,10 @@ async function inspectSkillMdYamlInvalid(projectRoot) {
|
|
|
6751
6960
|
}
|
|
6752
6961
|
for (const dirEntry of dirEntries) {
|
|
6753
6962
|
if (!dirEntry.isDirectory()) continue;
|
|
6754
|
-
const skillFile =
|
|
6963
|
+
const skillFile = join19(rootAbs, dirEntry.name, "SKILL.md");
|
|
6755
6964
|
let raw;
|
|
6756
6965
|
try {
|
|
6757
|
-
raw = await
|
|
6966
|
+
raw = await readFile12(skillFile, "utf8");
|
|
6758
6967
|
} catch {
|
|
6759
6968
|
continue;
|
|
6760
6969
|
}
|
|
@@ -6877,8 +7086,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
|
|
|
6877
7086
|
}
|
|
6878
7087
|
|
|
6879
7088
|
// src/services/doctor-retired-references-lint.ts
|
|
6880
|
-
import { readdir as readdir4, readFile as
|
|
6881
|
-
import { join as
|
|
7089
|
+
import { readdir as readdir4, readFile as readFile13, stat as stat3 } from "fs/promises";
|
|
7090
|
+
import { join as join20, posix as posix3, relative as relative2 } from "path";
|
|
6882
7091
|
var RETIRED_TOKENS = [
|
|
6883
7092
|
{ token: "fab_plan_context", replacement: "fab_recall", reason: "retrieval collapsed to one lean fab_recall (KT-DEC-0026)" },
|
|
6884
7093
|
{ token: "fab_get_knowledge_sections", replacement: "fab_recall", reason: "two-step fetch retired (KT-DEC-0026)" },
|
|
@@ -6901,7 +7110,7 @@ var RETIRED_TOKENS = [
|
|
|
6901
7110
|
];
|
|
6902
7111
|
var HOOK_DIRS = [".claude/hooks", ".codex/hooks"];
|
|
6903
7112
|
var SKILL_DIRS = [".claude/skills", ".codex/skills"];
|
|
6904
|
-
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md",
|
|
7113
|
+
var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md", join20(".fabric", "AGENTS.md")];
|
|
6905
7114
|
function isCommentLine(line) {
|
|
6906
7115
|
const t = line.trim();
|
|
6907
7116
|
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
@@ -6915,7 +7124,7 @@ async function walkFiles(dir, exts) {
|
|
|
6915
7124
|
}
|
|
6916
7125
|
const out = [];
|
|
6917
7126
|
for (const e of entries) {
|
|
6918
|
-
const full =
|
|
7127
|
+
const full = join20(dir, e.name);
|
|
6919
7128
|
if (e.isDirectory()) {
|
|
6920
7129
|
out.push(...await walkFiles(full, exts));
|
|
6921
7130
|
} else if (exts.some((ext) => e.name.endsWith(ext))) {
|
|
@@ -6941,29 +7150,29 @@ async function inspectRetiredReferences(projectRoot) {
|
|
|
6941
7150
|
let scannedFiles = 0;
|
|
6942
7151
|
const toRel = (p) => posix3.normalize(relative2(projectRoot, p).split("\\").join("/"));
|
|
6943
7152
|
for (const rel of BOOTSTRAP_FILES) {
|
|
6944
|
-
const abs =
|
|
7153
|
+
const abs = join20(projectRoot, rel);
|
|
6945
7154
|
try {
|
|
6946
7155
|
if ((await stat3(abs)).isFile()) {
|
|
6947
7156
|
scannedFiles += 1;
|
|
6948
|
-
scanText(toRel(abs), await
|
|
7157
|
+
scanText(toRel(abs), await readFile13(abs, "utf8"), false, hits);
|
|
6949
7158
|
}
|
|
6950
7159
|
} catch {
|
|
6951
7160
|
}
|
|
6952
7161
|
}
|
|
6953
7162
|
for (const dir of SKILL_DIRS) {
|
|
6954
|
-
for (const file of await walkFiles(
|
|
7163
|
+
for (const file of await walkFiles(join20(projectRoot, dir), [".md"])) {
|
|
6955
7164
|
scannedFiles += 1;
|
|
6956
7165
|
try {
|
|
6957
|
-
scanText(toRel(file), await
|
|
7166
|
+
scanText(toRel(file), await readFile13(file, "utf8"), false, hits);
|
|
6958
7167
|
} catch {
|
|
6959
7168
|
}
|
|
6960
7169
|
}
|
|
6961
7170
|
}
|
|
6962
7171
|
for (const dir of HOOK_DIRS) {
|
|
6963
|
-
for (const file of await walkFiles(
|
|
7172
|
+
for (const file of await walkFiles(join20(projectRoot, dir), [".cjs"])) {
|
|
6964
7173
|
scannedFiles += 1;
|
|
6965
7174
|
try {
|
|
6966
|
-
scanText(toRel(file), await
|
|
7175
|
+
scanText(toRel(file), await readFile13(file, "utf8"), true, hits);
|
|
6967
7176
|
} catch {
|
|
6968
7177
|
}
|
|
6969
7178
|
}
|
|
@@ -6998,8 +7207,8 @@ function createRetiredReferenceCheck(t, inspection) {
|
|
|
6998
7207
|
|
|
6999
7208
|
// src/services/doctor-hooks-lints.ts
|
|
7000
7209
|
import { constants } from "fs";
|
|
7001
|
-
import { access, readdir as readdir5, readFile as
|
|
7002
|
-
import { join as
|
|
7210
|
+
import { access, readdir as readdir5, readFile as readFile14, stat as stat4 } from "fs/promises";
|
|
7211
|
+
import { join as join21, posix as posix4 } from "path";
|
|
7003
7212
|
import { Script } from "vm";
|
|
7004
7213
|
var HOOKS_RUNTIME_CLIENT_DIRS = [
|
|
7005
7214
|
{ client: "claude", dir: ".claude/hooks" },
|
|
@@ -7061,14 +7270,14 @@ async function isFile(absPath) {
|
|
|
7061
7270
|
}
|
|
7062
7271
|
}
|
|
7063
7272
|
async function inspectHooksWired(projectRoot) {
|
|
7064
|
-
const claudeEntries = await readDirectoryFileNames(
|
|
7273
|
+
const claudeEntries = await readDirectoryFileNames(join21(projectRoot, ".claude"));
|
|
7065
7274
|
if (claudeEntries === null) {
|
|
7066
7275
|
return { status: "skipped", missingHooks: [] };
|
|
7067
7276
|
}
|
|
7068
|
-
const settingsPath =
|
|
7277
|
+
const settingsPath = join21(projectRoot, ".claude", "settings.json");
|
|
7069
7278
|
let parsed;
|
|
7070
7279
|
try {
|
|
7071
|
-
parsed = JSON.parse(await
|
|
7280
|
+
parsed = JSON.parse(await readFile14(settingsPath, "utf8"));
|
|
7072
7281
|
} catch {
|
|
7073
7282
|
return { status: "missing-settings", missingHooks: [] };
|
|
7074
7283
|
}
|
|
@@ -7091,8 +7300,8 @@ async function inspectHooksWired(projectRoot) {
|
|
|
7091
7300
|
}
|
|
7092
7301
|
async function inspectHookCacheWritability(projectRoot) {
|
|
7093
7302
|
const relPath = posix4.join(".fabric", ".cache");
|
|
7094
|
-
const fabricDir =
|
|
7095
|
-
const cacheDir =
|
|
7303
|
+
const fabricDir = join21(projectRoot, ".fabric");
|
|
7304
|
+
const cacheDir = join21(projectRoot, ".fabric", ".cache");
|
|
7096
7305
|
try {
|
|
7097
7306
|
try {
|
|
7098
7307
|
const cacheStats = await stat4(cacheDir);
|
|
@@ -7141,12 +7350,12 @@ async function inspectHookCacheWritability(projectRoot) {
|
|
|
7141
7350
|
async function inspectHooksContentDrift(projectRoot) {
|
|
7142
7351
|
const hookFilesByBasename = /* @__PURE__ */ new Map();
|
|
7143
7352
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
7144
|
-
const absDir =
|
|
7353
|
+
const absDir = join21(projectRoot, dir);
|
|
7145
7354
|
const entries = await readDirectoryFileNames(absDir);
|
|
7146
7355
|
if (entries === null) continue;
|
|
7147
7356
|
for (const name of entries) {
|
|
7148
7357
|
if (!name.endsWith(".cjs")) continue;
|
|
7149
|
-
const abs =
|
|
7358
|
+
const abs = join21(absDir, name);
|
|
7150
7359
|
if (!await isFile(abs)) continue;
|
|
7151
7360
|
const arr = hookFilesByBasename.get(name) ?? [];
|
|
7152
7361
|
arr.push({ client, abs });
|
|
@@ -7161,7 +7370,7 @@ async function inspectHooksContentDrift(projectRoot) {
|
|
|
7161
7370
|
const hashes = [];
|
|
7162
7371
|
for (const { client, abs } of copies) {
|
|
7163
7372
|
try {
|
|
7164
|
-
const body = await
|
|
7373
|
+
const body = await readFile14(abs, "utf8");
|
|
7165
7374
|
hashes.push({ client, sha: sha256(body) });
|
|
7166
7375
|
} catch {
|
|
7167
7376
|
}
|
|
@@ -7183,18 +7392,18 @@ async function inspectHooksRuntime(projectRoot) {
|
|
|
7183
7392
|
const issues = [];
|
|
7184
7393
|
let scanned = 0;
|
|
7185
7394
|
for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
|
|
7186
|
-
const absDir =
|
|
7395
|
+
const absDir = join21(projectRoot, dir);
|
|
7187
7396
|
const entries = await readDirectoryFileNames(absDir);
|
|
7188
7397
|
if (entries === null) continue;
|
|
7189
7398
|
for (const name of entries) {
|
|
7190
7399
|
if (!name.endsWith(".cjs")) continue;
|
|
7191
|
-
const abs =
|
|
7400
|
+
const abs = join21(absDir, name);
|
|
7192
7401
|
const displayPath = `${dir}/${name}`;
|
|
7193
7402
|
if (!await isFile(abs)) continue;
|
|
7194
7403
|
scanned += 1;
|
|
7195
7404
|
let body;
|
|
7196
7405
|
try {
|
|
7197
|
-
body = await
|
|
7406
|
+
body = await readFile14(abs, "utf8");
|
|
7198
7407
|
} catch (err) {
|
|
7199
7408
|
issues.push({
|
|
7200
7409
|
path: displayPath,
|
|
@@ -7331,8 +7540,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
|
|
|
7331
7540
|
}
|
|
7332
7541
|
|
|
7333
7542
|
// src/services/doctor-bootstrap-lints.ts
|
|
7334
|
-
import { access as access2, readFile as
|
|
7335
|
-
import { join as
|
|
7543
|
+
import { access as access2, readFile as readFile15 } from "fs/promises";
|
|
7544
|
+
import { join as join22 } from "path";
|
|
7336
7545
|
import {
|
|
7337
7546
|
BOOTSTRAP_MARKER_BEGIN,
|
|
7338
7547
|
BOOTSTRAP_MARKER_END,
|
|
@@ -7364,17 +7573,17 @@ async function fileExists(path) {
|
|
|
7364
7573
|
}
|
|
7365
7574
|
async function inspectBootstrapAnchor(projectRoot) {
|
|
7366
7575
|
const [hasAgentsMd, hasClaudeMd] = await Promise.all([
|
|
7367
|
-
fileExists(
|
|
7368
|
-
fileExists(
|
|
7576
|
+
fileExists(join22(projectRoot, "AGENTS.md")),
|
|
7577
|
+
fileExists(join22(projectRoot, "CLAUDE.md"))
|
|
7369
7578
|
]);
|
|
7370
7579
|
return { hasAgentsMd, hasClaudeMd };
|
|
7371
7580
|
}
|
|
7372
7581
|
async function inspectL1BootstrapSnapshotDrift(target) {
|
|
7373
|
-
const abs =
|
|
7582
|
+
const abs = join22(target, ".fabric", "AGENTS.md");
|
|
7374
7583
|
const canonical = resolveBootstrapCanonical();
|
|
7375
7584
|
let onDisk;
|
|
7376
7585
|
try {
|
|
7377
|
-
onDisk = await
|
|
7586
|
+
onDisk = await readFile15(abs, "utf8");
|
|
7378
7587
|
} catch {
|
|
7379
7588
|
return { status: "missing", canonical, onDisk: null };
|
|
7380
7589
|
}
|
|
@@ -7400,17 +7609,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
|
|
|
7400
7609
|
);
|
|
7401
7610
|
}
|
|
7402
7611
|
async function inspectL2ManagedBlockDrift(target) {
|
|
7403
|
-
const snapshotPath =
|
|
7612
|
+
const snapshotPath = join22(target, ".fabric", "AGENTS.md");
|
|
7404
7613
|
let snapshot;
|
|
7405
7614
|
try {
|
|
7406
|
-
snapshot = await
|
|
7615
|
+
snapshot = await readFile15(snapshotPath, "utf8");
|
|
7407
7616
|
} catch {
|
|
7408
7617
|
return { status: "ok", drifted: [] };
|
|
7409
7618
|
}
|
|
7410
|
-
const projectRulesPath =
|
|
7619
|
+
const projectRulesPath = join22(target, ".fabric", "project-rules.md");
|
|
7411
7620
|
let expectedBody = snapshot;
|
|
7412
7621
|
try {
|
|
7413
|
-
const projectRules = await
|
|
7622
|
+
const projectRules = await readFile15(projectRulesPath, "utf8");
|
|
7414
7623
|
expectedBody = `${snapshot}
|
|
7415
7624
|
---
|
|
7416
7625
|
${projectRules}`;
|
|
@@ -7419,12 +7628,12 @@ ${projectRules}`;
|
|
|
7419
7628
|
const drifted = [];
|
|
7420
7629
|
let anyManagedBlockFound = false;
|
|
7421
7630
|
const blockTargets = [
|
|
7422
|
-
|
|
7631
|
+
join22(target, "AGENTS.md")
|
|
7423
7632
|
];
|
|
7424
7633
|
for (const abs of blockTargets) {
|
|
7425
7634
|
let content;
|
|
7426
7635
|
try {
|
|
7427
|
-
content = await
|
|
7636
|
+
content = await readFile15(abs, "utf8");
|
|
7428
7637
|
} catch {
|
|
7429
7638
|
continue;
|
|
7430
7639
|
}
|
|
@@ -7447,9 +7656,9 @@ ${projectRules}`;
|
|
|
7447
7656
|
drifted.push({ path: abs, expected: expectedBody, actual: body });
|
|
7448
7657
|
}
|
|
7449
7658
|
}
|
|
7450
|
-
const claudeMdPath =
|
|
7659
|
+
const claudeMdPath = join22(target, "CLAUDE.md");
|
|
7451
7660
|
try {
|
|
7452
|
-
const claudeContent = await
|
|
7661
|
+
const claudeContent = await readFile15(claudeMdPath, "utf8");
|
|
7453
7662
|
anyManagedBlockFound = true;
|
|
7454
7663
|
const lines = claudeContent.split(/\r?\n/u);
|
|
7455
7664
|
const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
|
|
@@ -7517,7 +7726,7 @@ import { appendFile as appendFile3 } from "fs/promises";
|
|
|
7517
7726
|
import { minimatch as minimatch2 } from "minimatch";
|
|
7518
7727
|
|
|
7519
7728
|
// src/services/cite-rollup.ts
|
|
7520
|
-
import { readFile as
|
|
7729
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
7521
7730
|
import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
7522
7731
|
var citeRollupQueue = createLedgerWriteQueue3();
|
|
7523
7732
|
async function appendCiteRollupRow(projectRoot, row) {
|
|
@@ -7529,7 +7738,7 @@ async function readCiteRollup(projectRoot) {
|
|
|
7529
7738
|
const path = getCiteRollupPath(projectRoot);
|
|
7530
7739
|
let raw;
|
|
7531
7740
|
try {
|
|
7532
|
-
raw = await
|
|
7741
|
+
raw = await readFile16(path, "utf8");
|
|
7533
7742
|
} catch (error) {
|
|
7534
7743
|
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
7535
7744
|
throw error;
|
|
@@ -8120,6 +8329,18 @@ async function runDoctorCiteCoverage(projectRoot, options) {
|
|
|
8120
8329
|
}
|
|
8121
8330
|
}
|
|
8122
8331
|
}
|
|
8332
|
+
const editSessionIds = /* @__PURE__ */ new Set();
|
|
8333
|
+
for (const edit of editEvents) {
|
|
8334
|
+
if (typeof edit.session_id === "string" && edit.session_id.length > 0) {
|
|
8335
|
+
editSessionIds.add(edit.session_id);
|
|
8336
|
+
}
|
|
8337
|
+
}
|
|
8338
|
+
let recallsInWindow = 0;
|
|
8339
|
+
for (const list of plannedBySession.values()) recallsInWindow += list.length;
|
|
8340
|
+
let recallSessionsCorrelated = 0;
|
|
8341
|
+
for (const sid of plannedBySession.keys()) {
|
|
8342
|
+
if (editSessionIds.has(sid)) recallSessionsCorrelated += 1;
|
|
8343
|
+
}
|
|
8123
8344
|
const noneTotal = Object.values(noneHistogram).reduce((a, b) => a + b, 0);
|
|
8124
8345
|
const compliantCites = qualifyingCites + noneTotal;
|
|
8125
8346
|
const noncompliantCites = expectedButMissed;
|
|
@@ -8187,6 +8408,11 @@ async function runDoctorCiteCoverage(projectRoot, options) {
|
|
|
8187
8408
|
uncorrelatable_edits: uncorrelatableEdits,
|
|
8188
8409
|
recall_backed_edits: recallBackedEdits,
|
|
8189
8410
|
recall_coverage_rate: recallCoverageRate,
|
|
8411
|
+
recall_diagnostics: {
|
|
8412
|
+
recalls_in_window: recallsInWindow,
|
|
8413
|
+
recall_sessions: plannedBySession.size,
|
|
8414
|
+
recall_sessions_correlated: recallSessionsCorrelated
|
|
8415
|
+
},
|
|
8190
8416
|
exposed_and_mutated: {
|
|
8191
8417
|
count: exposedAndMutated.count,
|
|
8192
8418
|
...exposedAndMutated.ids.length > 0 ? { ids: exposedAndMutated.ids } : {}
|
|
@@ -8659,7 +8885,7 @@ async function runDoctorReport(target) {
|
|
|
8659
8885
|
const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
|
|
8660
8886
|
const targetFiles = Object.fromEntries(
|
|
8661
8887
|
await Promise.all(
|
|
8662
|
-
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(
|
|
8888
|
+
TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join23(projectRoot, path))])
|
|
8663
8889
|
)
|
|
8664
8890
|
);
|
|
8665
8891
|
const checks = [
|
|
@@ -8863,7 +9089,7 @@ async function runDoctorFix(target) {
|
|
|
8863
9089
|
const fixed = [];
|
|
8864
9090
|
const ledgerWarnings = [];
|
|
8865
9091
|
if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
|
|
8866
|
-
const snapshotPath =
|
|
9092
|
+
const snapshotPath = join23(projectRoot, ".fabric", "AGENTS.md");
|
|
8867
9093
|
await ensureParentDirectory(snapshotPath);
|
|
8868
9094
|
await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
|
|
8869
9095
|
fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
|
|
@@ -8936,7 +9162,7 @@ async function runDoctorFix(target) {
|
|
|
8936
9162
|
if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
|
|
8937
9163
|
const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
|
|
8938
9164
|
if (lockInspection.present && !lockInspection.pidAlive) {
|
|
8939
|
-
const lockFilePath =
|
|
9165
|
+
const lockFilePath = join23(projectRoot, ".fabric", ".serve.lock");
|
|
8940
9166
|
try {
|
|
8941
9167
|
await unlink4(lockFilePath);
|
|
8942
9168
|
} catch (err) {
|
|
@@ -9053,7 +9279,7 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
|
|
|
9053
9279
|
}
|
|
9054
9280
|
async function applySessionHintsStaleCleanup(projectRoot, candidate) {
|
|
9055
9281
|
const detail = `deleted (${candidate.age_days}d old)`;
|
|
9056
|
-
const absPath =
|
|
9282
|
+
const absPath = join23(projectRoot, candidate.path);
|
|
9057
9283
|
try {
|
|
9058
9284
|
const { unlink: unlink5 } = await import("fs/promises");
|
|
9059
9285
|
await unlink5(absPath);
|
|
@@ -9078,9 +9304,9 @@ function truncateErrorMessage(error) {
|
|
|
9078
9304
|
return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
|
|
9079
9305
|
}
|
|
9080
9306
|
async function inspectForensic(projectRoot) {
|
|
9081
|
-
const path =
|
|
9307
|
+
const path = join23(projectRoot, ".fabric", "forensic.json");
|
|
9082
9308
|
try {
|
|
9083
|
-
const parsed = forensicReportSchema.parse(JSON.parse(await
|
|
9309
|
+
const parsed = forensicReportSchema.parse(JSON.parse(await readFile18(path, "utf8")));
|
|
9084
9310
|
return { present: true, valid: true, report: parsed };
|
|
9085
9311
|
} catch (error) {
|
|
9086
9312
|
if (isMissingFileError(error)) {
|
|
@@ -9110,7 +9336,7 @@ async function inspectEventLedger(projectRoot) {
|
|
|
9110
9336
|
try {
|
|
9111
9337
|
await access4(path, constants2.W_OK);
|
|
9112
9338
|
const { warnings } = await readEventLedger(projectRoot);
|
|
9113
|
-
const raw = await
|
|
9339
|
+
const raw = await readFile18(path, "utf8");
|
|
9114
9340
|
const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
|
|
9115
9341
|
const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
|
|
9116
9342
|
const schemaVersionSamples = [];
|
|
@@ -9655,7 +9881,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
|
|
|
9655
9881
|
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
9656
9882
|
const detected = [];
|
|
9657
9883
|
for (const name of candidates) {
|
|
9658
|
-
if (await pathExists(
|
|
9884
|
+
if (await pathExists(join23(projectRoot, name))) {
|
|
9659
9885
|
detected.push(name);
|
|
9660
9886
|
}
|
|
9661
9887
|
}
|
|
@@ -9740,7 +9966,7 @@ async function buildLastActiveIndex(projectRoot) {
|
|
|
9740
9966
|
return map;
|
|
9741
9967
|
}
|
|
9742
9968
|
async function inspectSessionHintsStale(projectRoot, now) {
|
|
9743
|
-
const cacheDir =
|
|
9969
|
+
const cacheDir = join23(projectRoot, ".fabric", ".cache");
|
|
9744
9970
|
let entries;
|
|
9745
9971
|
try {
|
|
9746
9972
|
entries = await readdirAsync(cacheDir, { withFileTypes: true });
|
|
@@ -9752,7 +9978,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
|
|
|
9752
9978
|
if (!entry.isFile()) continue;
|
|
9753
9979
|
if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
|
|
9754
9980
|
if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
|
|
9755
|
-
const absPath =
|
|
9981
|
+
const absPath = join23(cacheDir, entry.name);
|
|
9756
9982
|
let mtimeMs = 0;
|
|
9757
9983
|
try {
|
|
9758
9984
|
mtimeMs = (await statAsync(absPath)).mtimeMs;
|
|
@@ -9784,9 +10010,9 @@ function inspectStaleServeLock(projectRoot, now) {
|
|
|
9784
10010
|
};
|
|
9785
10011
|
}
|
|
9786
10012
|
async function readUnderseedThresholdFromConfig(projectRoot) {
|
|
9787
|
-
const configPath =
|
|
10013
|
+
const configPath = join23(projectRoot, ".fabric", "fabric-config.json");
|
|
9788
10014
|
try {
|
|
9789
|
-
const raw = await
|
|
10015
|
+
const raw = await readFile18(configPath, "utf8");
|
|
9790
10016
|
const parsed = JSON.parse(raw);
|
|
9791
10017
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
9792
10018
|
const v = parsed.underseed_node_threshold;
|
|
@@ -9902,10 +10128,10 @@ async function inspectOnboardCoverage(projectRoot) {
|
|
|
9902
10128
|
return { filled, missing, opted_out: optedOut };
|
|
9903
10129
|
}
|
|
9904
10130
|
async function readOnboardOptedOut(projectRoot) {
|
|
9905
|
-
const path =
|
|
10131
|
+
const path = join23(projectRoot, ".fabric", "fabric-config.json");
|
|
9906
10132
|
let raw;
|
|
9907
10133
|
try {
|
|
9908
|
-
raw = await
|
|
10134
|
+
raw = await readFile18(path, "utf8");
|
|
9909
10135
|
} catch {
|
|
9910
10136
|
return [];
|
|
9911
10137
|
}
|
|
@@ -10007,22 +10233,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
|
|
|
10007
10233
|
}
|
|
10008
10234
|
}
|
|
10009
10235
|
async function rewriteThreeEndManagedBlocks(projectRoot) {
|
|
10010
|
-
const snapshotPath =
|
|
10236
|
+
const snapshotPath = join23(projectRoot, ".fabric", "AGENTS.md");
|
|
10011
10237
|
if (!await pathExists(snapshotPath)) {
|
|
10012
10238
|
return;
|
|
10013
10239
|
}
|
|
10014
10240
|
let snapshot;
|
|
10015
10241
|
try {
|
|
10016
|
-
snapshot = await
|
|
10242
|
+
snapshot = await readFile18(snapshotPath, "utf8");
|
|
10017
10243
|
} catch {
|
|
10018
10244
|
return;
|
|
10019
10245
|
}
|
|
10020
|
-
const projectRulesPath =
|
|
10246
|
+
const projectRulesPath = join23(projectRoot, ".fabric", "project-rules.md");
|
|
10021
10247
|
const hasProjectRules = await pathExists(projectRulesPath);
|
|
10022
10248
|
let expectedBody = snapshot;
|
|
10023
10249
|
if (hasProjectRules) {
|
|
10024
10250
|
try {
|
|
10025
|
-
const projectRules = await
|
|
10251
|
+
const projectRules = await readFile18(projectRulesPath, "utf8");
|
|
10026
10252
|
expectedBody = `${snapshot}
|
|
10027
10253
|
---
|
|
10028
10254
|
${projectRules}`;
|
|
@@ -10033,7 +10259,7 @@ ${projectRules}`;
|
|
|
10033
10259
|
${expectedBody}
|
|
10034
10260
|
${BOOTSTRAP_MARKER_END2}`;
|
|
10035
10261
|
const blockTargets = [
|
|
10036
|
-
|
|
10262
|
+
join23(projectRoot, "AGENTS.md")
|
|
10037
10263
|
];
|
|
10038
10264
|
for (const abs of blockTargets) {
|
|
10039
10265
|
if (!await pathExists(abs)) {
|
|
@@ -10041,7 +10267,7 @@ ${BOOTSTRAP_MARKER_END2}`;
|
|
|
10041
10267
|
}
|
|
10042
10268
|
let existing;
|
|
10043
10269
|
try {
|
|
10044
|
-
existing = await
|
|
10270
|
+
existing = await readFile18(abs, "utf8");
|
|
10045
10271
|
} catch {
|
|
10046
10272
|
continue;
|
|
10047
10273
|
}
|
|
@@ -10066,11 +10292,11 @@ ${managedBlock}
|
|
|
10066
10292
|
}
|
|
10067
10293
|
await atomicWriteText4(abs, next);
|
|
10068
10294
|
}
|
|
10069
|
-
const claudeMdPath =
|
|
10295
|
+
const claudeMdPath = join23(projectRoot, "CLAUDE.md");
|
|
10070
10296
|
if (await pathExists(claudeMdPath)) {
|
|
10071
10297
|
let claudeContent;
|
|
10072
10298
|
try {
|
|
10073
|
-
claudeContent = await
|
|
10299
|
+
claudeContent = await readFile18(claudeMdPath, "utf8");
|
|
10074
10300
|
} catch {
|
|
10075
10301
|
return;
|
|
10076
10302
|
}
|
|
@@ -10136,7 +10362,7 @@ async function collectEntryPoints(root) {
|
|
|
10136
10362
|
continue;
|
|
10137
10363
|
}
|
|
10138
10364
|
for (const entry of await readdirAsync(current, { withFileTypes: true })) {
|
|
10139
|
-
const absolutePath =
|
|
10365
|
+
const absolutePath = join23(current, entry.name);
|
|
10140
10366
|
const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
|
|
10141
10367
|
if (relativePath.length === 0) {
|
|
10142
10368
|
continue;
|
|
@@ -10212,7 +10438,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
|
|
|
10212
10438
|
scanned += 1;
|
|
10213
10439
|
let source;
|
|
10214
10440
|
try {
|
|
10215
|
-
source = await
|
|
10441
|
+
source = await readFile18(absPath, "utf8");
|
|
10216
10442
|
} catch {
|
|
10217
10443
|
continue;
|
|
10218
10444
|
}
|
|
@@ -10350,14 +10576,14 @@ async function runDoctorConflictLint(projectRoot, opts = {}) {
|
|
|
10350
10576
|
}
|
|
10351
10577
|
|
|
10352
10578
|
// src/services/why-not-surfaced.ts
|
|
10353
|
-
import { readFile as
|
|
10354
|
-
import { basename as basename3, join as
|
|
10579
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
10580
|
+
import { basename as basename3, join as join24 } from "path";
|
|
10355
10581
|
import {
|
|
10356
10582
|
buildStoreResolveInput as buildStoreResolveInput7,
|
|
10357
10583
|
createStoreResolver as createStoreResolver7,
|
|
10358
10584
|
loadProjectConfig as loadProjectConfig4,
|
|
10359
10585
|
readKnowledgeAcrossStores as readKnowledgeAcrossStores4,
|
|
10360
|
-
resolveGlobalRoot as
|
|
10586
|
+
resolveGlobalRoot as resolveGlobalRoot8,
|
|
10361
10587
|
scopeRoot as scopeRoot3,
|
|
10362
10588
|
storeRelativePathForMount as storeRelativePathForMount6
|
|
10363
10589
|
} from "@fenglimg/fabric-shared";
|
|
@@ -10387,11 +10613,11 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10387
10613
|
if (input === null) {
|
|
10388
10614
|
return base;
|
|
10389
10615
|
}
|
|
10390
|
-
const globalRoot =
|
|
10616
|
+
const globalRoot = resolveGlobalRoot8();
|
|
10391
10617
|
const allStores = input.mountedStores.map((s) => ({
|
|
10392
10618
|
store_uuid: s.store_uuid,
|
|
10393
10619
|
alias: s.alias,
|
|
10394
|
-
dir:
|
|
10620
|
+
dir: join24(globalRoot, storeRelativePathForMount6(s))
|
|
10395
10621
|
}));
|
|
10396
10622
|
const refs = await readKnowledgeAcrossStores4(allStores);
|
|
10397
10623
|
const candidate = refs.find((ref) => fileMatchesId(ref.file, localId));
|
|
@@ -10400,7 +10626,7 @@ async function explainWhyNotSurfaced(projectRoot, query) {
|
|
|
10400
10626
|
}
|
|
10401
10627
|
let source;
|
|
10402
10628
|
try {
|
|
10403
|
-
source = await
|
|
10629
|
+
source = await readFile19(candidate.file, "utf8");
|
|
10404
10630
|
} catch {
|
|
10405
10631
|
return base;
|
|
10406
10632
|
}
|
|
@@ -10453,6 +10679,191 @@ function buildColdEvalBatch(candidates) {
|
|
|
10453
10679
|
return { rubric: COLD_EVAL_RUBRIC, candidates: judgeable };
|
|
10454
10680
|
}
|
|
10455
10681
|
|
|
10682
|
+
// src/services/doctor-related-graph.ts
|
|
10683
|
+
function buildRelatedGraph(nodes) {
|
|
10684
|
+
const brokenLinks = [];
|
|
10685
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
10686
|
+
const allIds = /* @__PURE__ */ new Set();
|
|
10687
|
+
const edgeSources = [];
|
|
10688
|
+
for (const node of nodes) {
|
|
10689
|
+
const bareId = extractBareStableId(node.qualifiedId);
|
|
10690
|
+
allIds.add(node.qualifiedId);
|
|
10691
|
+
if (bareId !== null) {
|
|
10692
|
+
allIds.add(bareId);
|
|
10693
|
+
}
|
|
10694
|
+
const related = node.related;
|
|
10695
|
+
if (related !== void 0 && related.length > 0) {
|
|
10696
|
+
edgeSources.push({ source: node.qualifiedId, related });
|
|
10697
|
+
for (const rel of related) {
|
|
10698
|
+
inDegree.set(rel, (inDegree.get(rel) ?? 0) + 1);
|
|
10699
|
+
}
|
|
10700
|
+
}
|
|
10701
|
+
}
|
|
10702
|
+
for (const { source, related } of edgeSources) {
|
|
10703
|
+
for (const rel of related) {
|
|
10704
|
+
if (!allIds.has(rel)) {
|
|
10705
|
+
brokenLinks.push({ source, target: rel });
|
|
10706
|
+
}
|
|
10707
|
+
}
|
|
10708
|
+
}
|
|
10709
|
+
const hubEntries = [...inDegree.entries()].map(([stableId, degree]) => ({ stableId, inDegree: degree })).sort((a, b) => b.inDegree - a.inDegree || a.stableId.localeCompare(b.stableId));
|
|
10710
|
+
return {
|
|
10711
|
+
brokenLinks,
|
|
10712
|
+
hubEntries,
|
|
10713
|
+
totalEntries: nodes.length
|
|
10714
|
+
};
|
|
10715
|
+
}
|
|
10716
|
+
function extractBareStableId(qualifiedId) {
|
|
10717
|
+
const colonIdx = qualifiedId.indexOf(":");
|
|
10718
|
+
if (colonIdx > 0 && colonIdx < qualifiedId.length - 1) {
|
|
10719
|
+
return qualifiedId.slice(colonIdx + 1);
|
|
10720
|
+
}
|
|
10721
|
+
return null;
|
|
10722
|
+
}
|
|
10723
|
+
async function inspectRelatedGraph(projectRoot) {
|
|
10724
|
+
const entries = await collectStoreCanonicalEntries(projectRoot);
|
|
10725
|
+
return buildRelatedGraph(
|
|
10726
|
+
entries.map((entry) => ({
|
|
10727
|
+
qualifiedId: entry.qualifiedId,
|
|
10728
|
+
related: entry.description.related
|
|
10729
|
+
}))
|
|
10730
|
+
);
|
|
10731
|
+
}
|
|
10732
|
+
|
|
10733
|
+
// src/services/store-precheck.ts
|
|
10734
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
10735
|
+
import { join as join25 } from "path";
|
|
10736
|
+
import {
|
|
10737
|
+
buildStoreResolveInput as buildStoreResolveInput8,
|
|
10738
|
+
createStoreResolver as createStoreResolver8,
|
|
10739
|
+
resolveGlobalRoot as resolveGlobalRoot9,
|
|
10740
|
+
storeRelativePathForMount as storeRelativePathForMount7
|
|
10741
|
+
} from "@fenglimg/fabric-shared";
|
|
10742
|
+
var CACHE_TTL_MS = 6e4;
|
|
10743
|
+
var cache = /* @__PURE__ */ new Map();
|
|
10744
|
+
function clearPrecheckCache() {
|
|
10745
|
+
cache.clear();
|
|
10746
|
+
}
|
|
10747
|
+
function evaluateStoreDir(storeDir, identity) {
|
|
10748
|
+
if (!existsSync9(storeDir)) {
|
|
10749
|
+
return {
|
|
10750
|
+
uuid: identity.uuid,
|
|
10751
|
+
alias: identity.alias,
|
|
10752
|
+
reachable: false,
|
|
10753
|
+
reason: `directory not found at ${storeDir}`
|
|
10754
|
+
};
|
|
10755
|
+
}
|
|
10756
|
+
const storeJsonPath = join25(storeDir, "store.json");
|
|
10757
|
+
const gitDirPath = join25(storeDir, ".git");
|
|
10758
|
+
const hasStoreJson = existsSync9(storeJsonPath) && isValidStoreJson(storeJsonPath);
|
|
10759
|
+
const hasGitDir = existsSync9(gitDirPath);
|
|
10760
|
+
if (!hasStoreJson && !hasGitDir) {
|
|
10761
|
+
return {
|
|
10762
|
+
uuid: identity.uuid,
|
|
10763
|
+
alias: identity.alias,
|
|
10764
|
+
reachable: false,
|
|
10765
|
+
reason: `no store.json or .git found in ${storeDir}`
|
|
10766
|
+
};
|
|
10767
|
+
}
|
|
10768
|
+
return { uuid: identity.uuid, alias: identity.alias, reachable: true };
|
|
10769
|
+
}
|
|
10770
|
+
function isValidStoreJson(path) {
|
|
10771
|
+
try {
|
|
10772
|
+
JSON.parse(readFileSync4(path, "utf8"));
|
|
10773
|
+
return true;
|
|
10774
|
+
} catch {
|
|
10775
|
+
return false;
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
async function precheckStoreReachability(projectRoot, globalRoot = resolveGlobalRoot9(), now = Date.now()) {
|
|
10779
|
+
const cached = cache.get(projectRoot);
|
|
10780
|
+
if (cached !== void 0 && cached.expiresAt > now) {
|
|
10781
|
+
return cached.result;
|
|
10782
|
+
}
|
|
10783
|
+
const input = buildStoreResolveInput8(projectRoot, globalRoot);
|
|
10784
|
+
if (input === null) {
|
|
10785
|
+
const result2 = { stores: [], allReachable: true };
|
|
10786
|
+
cache.set(projectRoot, { result: result2, expiresAt: now + CACHE_TTL_MS });
|
|
10787
|
+
return result2;
|
|
10788
|
+
}
|
|
10789
|
+
const readSet = createStoreResolver8().resolveReadSet(input);
|
|
10790
|
+
const stores = readSet.stores.map((entry) => {
|
|
10791
|
+
const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
|
|
10792
|
+
const storeDir = join25(
|
|
10793
|
+
globalRoot,
|
|
10794
|
+
storeRelativePathForMount7(mounted ?? { store_uuid: entry.store_uuid })
|
|
10795
|
+
);
|
|
10796
|
+
return evaluateStoreDir(storeDir, { uuid: entry.store_uuid, alias: entry.alias });
|
|
10797
|
+
});
|
|
10798
|
+
const result = {
|
|
10799
|
+
stores,
|
|
10800
|
+
allReachable: stores.every((s) => s.reachable)
|
|
10801
|
+
};
|
|
10802
|
+
cache.set(projectRoot, { result, expiresAt: now + CACHE_TTL_MS });
|
|
10803
|
+
return result;
|
|
10804
|
+
}
|
|
10805
|
+
|
|
10806
|
+
// src/services/doctor-consumption-lint.ts
|
|
10807
|
+
var DEFAULT_WINDOW_DAYS = 30;
|
|
10808
|
+
var DEFAULT_TOP_N = 10;
|
|
10809
|
+
var DEFAULT_MIN_TOTAL_ENTRIES = 20;
|
|
10810
|
+
var DEFAULT_MIN_CONSUMED_WINDOWS = 30;
|
|
10811
|
+
var DEFAULT_MIN_CONSUMED_EVENTS = 50;
|
|
10812
|
+
var CONSUMED_PREFIX = "knowledge_consumed:";
|
|
10813
|
+
var MS_PER_DAY5 = 24 * 60 * 60 * 1e3;
|
|
10814
|
+
function resolveConfig(config) {
|
|
10815
|
+
return {
|
|
10816
|
+
windowDays: config?.windowDays ?? DEFAULT_WINDOW_DAYS,
|
|
10817
|
+
topN: config?.topN ?? DEFAULT_TOP_N,
|
|
10818
|
+
minTotalEntries: config?.minTotalEntries ?? DEFAULT_MIN_TOTAL_ENTRIES,
|
|
10819
|
+
minConsumedWindows: config?.minConsumedWindows ?? DEFAULT_MIN_CONSUMED_WINDOWS,
|
|
10820
|
+
minConsumedEvents: config?.minConsumedEvents ?? DEFAULT_MIN_CONSUMED_EVENTS
|
|
10821
|
+
};
|
|
10822
|
+
}
|
|
10823
|
+
function aggregateConsumption(rows, allQualifiedIds, now, config) {
|
|
10824
|
+
const cfg = resolveConfig(config);
|
|
10825
|
+
const cutoffMs = now - cfg.windowDays * MS_PER_DAY5;
|
|
10826
|
+
const consumed = /* @__PURE__ */ new Map();
|
|
10827
|
+
let consumedWindows = 0;
|
|
10828
|
+
let totalConsumedEvents = 0;
|
|
10829
|
+
for (const row of rows) {
|
|
10830
|
+
const rowTs = Date.parse(row.timestamp);
|
|
10831
|
+
if (!Number.isFinite(rowTs) || rowTs < cutoffMs) continue;
|
|
10832
|
+
if (row.counters === null || typeof row.counters !== "object") continue;
|
|
10833
|
+
let windowHadConsumption = false;
|
|
10834
|
+
for (const [counterName, rawCount] of Object.entries(row.counters)) {
|
|
10835
|
+
if (!counterName.startsWith(CONSUMED_PREFIX)) continue;
|
|
10836
|
+
const count = typeof rawCount === "number" ? rawCount : 0;
|
|
10837
|
+
if (count <= 0) continue;
|
|
10838
|
+
const id = counterName.slice(CONSUMED_PREFIX.length);
|
|
10839
|
+
if (id.length === 0) continue;
|
|
10840
|
+
consumed.set(id, (consumed.get(id) ?? 0) + count);
|
|
10841
|
+
totalConsumedEvents += count;
|
|
10842
|
+
windowHadConsumption = true;
|
|
10843
|
+
}
|
|
10844
|
+
if (windowHadConsumption) consumedWindows += 1;
|
|
10845
|
+
}
|
|
10846
|
+
const topConsumed = [...consumed.entries()].map(([stableId, count]) => ({ stableId, count })).sort((a, b) => b.count - a.count || a.stableId.localeCompare(b.stableId)).slice(0, cfg.topN);
|
|
10847
|
+
const dataMature = consumedWindows >= cfg.minConsumedWindows && totalConsumedEvents >= cfg.minConsumedEvents && allQualifiedIds.length >= cfg.minTotalEntries;
|
|
10848
|
+
const zeroConsumed = dataMature ? allQualifiedIds.filter((id) => !consumed.has(id)).sort((a, b) => a.localeCompare(b)) : [];
|
|
10849
|
+
return {
|
|
10850
|
+
topConsumed,
|
|
10851
|
+
zeroConsumed,
|
|
10852
|
+
totalEntries: allQualifiedIds.length,
|
|
10853
|
+
consumedEntries: consumed.size,
|
|
10854
|
+
consumedWindows,
|
|
10855
|
+
totalConsumedEvents,
|
|
10856
|
+
dataMature,
|
|
10857
|
+
windowDays: cfg.windowDays
|
|
10858
|
+
};
|
|
10859
|
+
}
|
|
10860
|
+
async function inspectConsumption(projectRoot, config, now = Date.now()) {
|
|
10861
|
+
const rows = await readMetrics(projectRoot);
|
|
10862
|
+
const entries = await collectStoreCanonicalEntries(projectRoot);
|
|
10863
|
+
const allQualifiedIds = entries.map((entry) => entry.qualifiedId);
|
|
10864
|
+
return aggregateConsumption(rows, allQualifiedIds, now, config);
|
|
10865
|
+
}
|
|
10866
|
+
|
|
10456
10867
|
// src/services/rotation-tick.ts
|
|
10457
10868
|
var DEFAULT_TICK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
10458
10869
|
var tickTimers = /* @__PURE__ */ new Map();
|
|
@@ -10488,7 +10899,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
|
|
|
10488
10899
|
|
|
10489
10900
|
// src/services/read-ledger.ts
|
|
10490
10901
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10491
|
-
import { access as access5, copyFile, readFile as
|
|
10902
|
+
import { access as access5, copyFile, readFile as readFile20, rm } from "fs/promises";
|
|
10492
10903
|
import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
10493
10904
|
async function resolveLedgerPaths(projectRoot) {
|
|
10494
10905
|
const primaryPath = getLedgerPath(projectRoot);
|
|
@@ -10516,7 +10927,7 @@ async function readLegacyLedger(projectRoot) {
|
|
|
10516
10927
|
const { readPath } = await resolveLedgerPaths(projectRoot);
|
|
10517
10928
|
let raw;
|
|
10518
10929
|
try {
|
|
10519
|
-
raw = await
|
|
10930
|
+
raw = await readFile20(readPath, "utf8");
|
|
10520
10931
|
} catch (error) {
|
|
10521
10932
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
10522
10933
|
return [];
|
|
@@ -10771,8 +11182,8 @@ function formatError(error) {
|
|
|
10771
11182
|
}
|
|
10772
11183
|
function formatPreexistingRootMessage(projectRoot) {
|
|
10773
11184
|
const preexisting = [];
|
|
10774
|
-
if (
|
|
10775
|
-
if (
|
|
11185
|
+
if (existsSync10(join26(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
|
|
11186
|
+
if (existsSync10(join26(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
|
|
10776
11187
|
if (preexisting.length === 0) return null;
|
|
10777
11188
|
return `[startup] info: detected ${preexisting.join(", ")} at project root. Note: Fabric serves knowledge from mounted stores via MCP \u2014 root markdown files are not auto-loaded into the AI context.`;
|
|
10778
11189
|
}
|
|
@@ -10799,7 +11210,7 @@ function createFabricServer(tracker) {
|
|
|
10799
11210
|
const server = new McpServer(
|
|
10800
11211
|
{
|
|
10801
11212
|
name: "fabric-knowledge-server",
|
|
10802
|
-
version: "2.3.0-rc.
|
|
11213
|
+
version: "2.3.0-rc.4"
|
|
10803
11214
|
},
|
|
10804
11215
|
{
|
|
10805
11216
|
instructions: FABRIC_SERVER_INSTRUCTIONS
|
|
@@ -10819,10 +11230,10 @@ function createFabricServer(tracker) {
|
|
|
10819
11230
|
},
|
|
10820
11231
|
async (_uri) => {
|
|
10821
11232
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
10822
|
-
const path =
|
|
11233
|
+
const path = join26(projectRoot, ".fabric", "bootstrap", "README.md");
|
|
10823
11234
|
let text = "";
|
|
10824
|
-
if (
|
|
10825
|
-
text = await
|
|
11235
|
+
if (existsSync10(path)) {
|
|
11236
|
+
text = await readFile21(path, "utf8");
|
|
10826
11237
|
}
|
|
10827
11238
|
return {
|
|
10828
11239
|
contents: [
|
|
@@ -10920,19 +11331,26 @@ export {
|
|
|
10920
11331
|
LEGACY_LEDGER_PATH,
|
|
10921
11332
|
METRICS_LEDGER_PATH,
|
|
10922
11333
|
METRIC_COUNTER_NAMES,
|
|
11334
|
+
OPTIONAL_EMBED_PACKAGE,
|
|
10923
11335
|
RETIRED_TOKENS,
|
|
11336
|
+
aggregateConsumption,
|
|
10924
11337
|
appendEventLedgerEvent,
|
|
10925
11338
|
buildAlwaysActiveBodies,
|
|
10926
11339
|
buildColdEvalBatch,
|
|
10927
11340
|
buildKnowledgeCensus,
|
|
11341
|
+
buildRelatedGraph,
|
|
10928
11342
|
bumpCounter,
|
|
11343
|
+
clearPrecheckCache,
|
|
11344
|
+
collectStoreCanonicalEntries,
|
|
10929
11345
|
contextCache,
|
|
10930
11346
|
createFabricServer,
|
|
10931
11347
|
createInFlightTracker,
|
|
10932
11348
|
createShutdownHandler,
|
|
11349
|
+
defaultEmbedCacheDir,
|
|
10933
11350
|
detectUnboundProject,
|
|
10934
11351
|
drainCounters,
|
|
10935
11352
|
enrichDescriptions,
|
|
11353
|
+
evaluateStoreDir,
|
|
10936
11354
|
explainWhyNotSurfaced,
|
|
10937
11355
|
extractKnowledge,
|
|
10938
11356
|
findConflictCandidates,
|
|
@@ -10943,12 +11361,19 @@ export {
|
|
|
10943
11361
|
getLedgerPath,
|
|
10944
11362
|
getLegacyLedgerPath,
|
|
10945
11363
|
getMetricsLedgerPath,
|
|
11364
|
+
inspectConsumption,
|
|
11365
|
+
inspectRelatedGraph,
|
|
10946
11366
|
inspectRetiredReferences,
|
|
11367
|
+
isEmbedderResolvable,
|
|
10947
11368
|
lintConflicts,
|
|
10948
11369
|
loadConflictEntries,
|
|
11370
|
+
loadEmbedder,
|
|
10949
11371
|
pairSimilarity,
|
|
10950
11372
|
planContext,
|
|
11373
|
+
precheckStoreReachability,
|
|
11374
|
+
readEmbedConfig,
|
|
10951
11375
|
readEventLedger,
|
|
11376
|
+
readFusion,
|
|
10952
11377
|
readLedger,
|
|
10953
11378
|
readMetrics,
|
|
10954
11379
|
readSelectionToken,
|