@fenglimg/fabric-server 1.8.0-rc.3 → 2.0.0-rc.8
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/chunk-AR2HV5JT.js +4061 -0
- package/dist/{http-PXFWUKCA.js → http-AR26GYEV.js} +19 -169
- package/dist/index.d.ts +174 -35
- package/dist/index.js +1622 -64
- package/package.json +4 -5
- package/dist/chunk-ZZGARZL5.js +0 -3019
- package/dist/static/assets/index-BSbndc76.js +0 -10
- package/dist/static/assets/index-FoBU5Kta.css +0 -1
- package/dist/static/index.html +0 -16
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AGENTS_MD_RESOURCE_URI,
|
|
3
2
|
EVENT_LEDGER_PATH,
|
|
4
3
|
LEDGER_PATH,
|
|
5
4
|
LEGACY_LEDGER_PATH,
|
|
6
5
|
appendEventLedgerEvent,
|
|
7
6
|
contextCache,
|
|
8
7
|
getEventLedgerPath,
|
|
8
|
+
getKnowledge,
|
|
9
9
|
getLedgerPath,
|
|
10
10
|
getLegacyLedgerPath,
|
|
11
|
-
|
|
12
|
-
invalidateRuleSyncCooldown,
|
|
11
|
+
invalidateKnowledgeSyncCooldown,
|
|
13
12
|
isNodeError,
|
|
14
13
|
readAgentsMeta,
|
|
15
14
|
readEventLedger,
|
|
16
15
|
runDoctorReport,
|
|
17
16
|
sha256
|
|
18
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-AR2HV5JT.js";
|
|
19
18
|
|
|
20
19
|
// src/http.ts
|
|
21
20
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -87,7 +86,6 @@ import { join } from "path";
|
|
|
87
86
|
import {
|
|
88
87
|
agentsMetaSchema,
|
|
89
88
|
fabricEventSchema,
|
|
90
|
-
forensicReportSchema,
|
|
91
89
|
ledgerEntrySchema as ledgerEntrySchema2
|
|
92
90
|
} from "@fenglimg/fabric-shared";
|
|
93
91
|
import { eventLedgerEventSchema } from "@fenglimg/fabric-shared";
|
|
@@ -132,34 +130,6 @@ async function readLegacyLedger(projectRoot) {
|
|
|
132
130
|
}
|
|
133
131
|
return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line, index) => parseLedgerLine(line, index)).filter((entry) => entry !== null);
|
|
134
132
|
}
|
|
135
|
-
async function appendLedgerEntry(projectRoot, entry) {
|
|
136
|
-
const nextEntry = createStoredLedgerEntry(entry);
|
|
137
|
-
for (const affectedPath of nextEntry.affected_paths) {
|
|
138
|
-
await appendEventLedgerEvent(projectRoot, {
|
|
139
|
-
event_type: "edit_intent_checked",
|
|
140
|
-
ts: nextEntry.ts,
|
|
141
|
-
path: affectedPath,
|
|
142
|
-
compliant: true,
|
|
143
|
-
intent: nextEntry.intent,
|
|
144
|
-
ledger_entry_id: nextEntry.id,
|
|
145
|
-
ledger_source: nextEntry.source,
|
|
146
|
-
commit_sha: nextEntry.source === "ai" ? nextEntry.commit_sha : void 0,
|
|
147
|
-
parent_sha: nextEntry.source === "human" ? nextEntry.parent_sha : void 0,
|
|
148
|
-
parent_ledger_entry_id: nextEntry.source === "human" ? nextEntry.parent_ledger_entry_id : void 0,
|
|
149
|
-
diff_stat: nextEntry.source === "human" ? nextEntry.diff_stat : void 0,
|
|
150
|
-
annotation: nextEntry.source === "human" ? nextEntry.annotation : void 0,
|
|
151
|
-
matched_rule_context_ts: null,
|
|
152
|
-
window_ms: 0
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return nextEntry;
|
|
156
|
-
}
|
|
157
|
-
function createStoredLedgerEntry(entry) {
|
|
158
|
-
return ledgerEntrySchema.parse({
|
|
159
|
-
...entry,
|
|
160
|
-
id: entry.id ?? `ledger:${randomUUID()}`
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
133
|
function parseLedgerLine(line, index) {
|
|
164
134
|
try {
|
|
165
135
|
const parsed = JSON.parse(line);
|
|
@@ -254,10 +224,8 @@ async function pathExists(path) {
|
|
|
254
224
|
|
|
255
225
|
// src/api/events.ts
|
|
256
226
|
var AGENTS_META_PATH = ".fabric/agents.meta.json";
|
|
257
|
-
var FORENSIC_PATH = ".fabric/forensic.json";
|
|
258
227
|
var WATCHED_PATHS = [
|
|
259
228
|
AGENTS_META_PATH,
|
|
260
|
-
FORENSIC_PATH,
|
|
261
229
|
EVENT_LEDGER_PATH,
|
|
262
230
|
LEDGER_PATH,
|
|
263
231
|
LEGACY_LEDGER_PATH
|
|
@@ -438,10 +406,6 @@ async function readEventsForFile(state, projectRoot, relativePath) {
|
|
|
438
406
|
const event = await readMetaUpdatedEvent(projectRoot);
|
|
439
407
|
return event === null ? [] : [event];
|
|
440
408
|
}
|
|
441
|
-
if (relativePath === FORENSIC_PATH) {
|
|
442
|
-
const event = await readDriftDetectedEvent(projectRoot);
|
|
443
|
-
return event === null ? [] : [event];
|
|
444
|
-
}
|
|
445
409
|
if (relativePath === EVENT_LEDGER_PATH) {
|
|
446
410
|
return await readEventLedgerAppendedEvents(state, projectRoot);
|
|
447
411
|
}
|
|
@@ -462,18 +426,6 @@ async function readMetaUpdatedEvent(projectRoot) {
|
|
|
462
426
|
payload: parsed
|
|
463
427
|
};
|
|
464
428
|
}
|
|
465
|
-
async function readDriftDetectedEvent(projectRoot) {
|
|
466
|
-
const filePath = join(projectRoot, FORENSIC_PATH);
|
|
467
|
-
const raw = await readUtf8File(filePath);
|
|
468
|
-
if (raw === null) {
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
const parsed = forensicReportSchema.parse(JSON.parse(raw));
|
|
472
|
-
return {
|
|
473
|
-
type: "drift:detected",
|
|
474
|
-
payload: parsed
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
429
|
async function readLedgerAppendedEvents(state, projectRoot) {
|
|
478
430
|
const ledgerState = await resolveLedgerWatchState(projectRoot);
|
|
479
431
|
const ledgerPath = ledgerState.path;
|
|
@@ -830,60 +782,6 @@ function registerHistoryApi(app, projectRoot) {
|
|
|
830
782
|
});
|
|
831
783
|
}
|
|
832
784
|
|
|
833
|
-
// src/api/intent.ts
|
|
834
|
-
import { annotateIntentRequestSchema } from "@fenglimg/fabric-shared";
|
|
835
|
-
|
|
836
|
-
// src/services/annotate-intent.ts
|
|
837
|
-
async function annotateIntent(projectRoot, input) {
|
|
838
|
-
const entries = await readLedger(projectRoot);
|
|
839
|
-
const parentEntry = entries.find((entry2) => entry2.id === input.ledger_entry_id);
|
|
840
|
-
if (parentEntry === void 0) {
|
|
841
|
-
throw new LedgerEntryNotFoundError(`Cannot find ledger entry: ${input.ledger_entry_id}`);
|
|
842
|
-
}
|
|
843
|
-
const lastEntry = entries[entries.length - 1];
|
|
844
|
-
if (lastEntry?.source === "human" && lastEntry.parent_ledger_entry_id === input.ledger_entry_id && lastEntry.annotation === input.annotation) {
|
|
845
|
-
return {
|
|
846
|
-
created: false,
|
|
847
|
-
entry: lastEntry
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
const entry = await appendLedgerEntry(projectRoot, createAnnotationEntry(parentEntry, input));
|
|
851
|
-
return {
|
|
852
|
-
created: true,
|
|
853
|
-
entry
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
function createAnnotationEntry(parentEntry, input) {
|
|
857
|
-
return {
|
|
858
|
-
ts: Date.now(),
|
|
859
|
-
source: "human",
|
|
860
|
-
parent_sha: input.ledger_entry_id,
|
|
861
|
-
parent_ledger_entry_id: input.ledger_entry_id,
|
|
862
|
-
intent: input.annotation,
|
|
863
|
-
annotation: input.annotation,
|
|
864
|
-
affected_paths: parentEntry.affected_paths,
|
|
865
|
-
diff_stat: "annotation"
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// src/api/intent.ts
|
|
870
|
-
function registerIntentApi(app, projectRoot) {
|
|
871
|
-
app.post("/api/intent/annotate", async (req, res) => {
|
|
872
|
-
const validation = annotateIntentRequestSchema.safeParse(req.body);
|
|
873
|
-
if (!validation.success) {
|
|
874
|
-
sendValidationError(res, "Invalid intent annotation payload", validation.error.flatten());
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
try {
|
|
878
|
-
await readAgentsMeta(projectRoot);
|
|
879
|
-
const result = await annotateIntent(projectRoot, validation.data);
|
|
880
|
-
res.status(result.created ? 201 : 200).json(result);
|
|
881
|
-
} catch (error) {
|
|
882
|
-
sendUnknownError(res, error);
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
|
|
887
785
|
// src/api/ledger.ts
|
|
888
786
|
import { ledgerQuerySchema } from "@fenglimg/fabric-shared";
|
|
889
787
|
function registerLedgerApi(app, projectRoot) {
|
|
@@ -905,8 +803,8 @@ function registerLedgerApi(app, projectRoot) {
|
|
|
905
803
|
});
|
|
906
804
|
}
|
|
907
805
|
|
|
908
|
-
// src/api/
|
|
909
|
-
function
|
|
806
|
+
// src/api/knowledge.ts
|
|
807
|
+
function registerKnowledgeApi(app, projectRoot) {
|
|
910
808
|
app.get("/api/rules", async (_req, res) => {
|
|
911
809
|
try {
|
|
912
810
|
res.json(await readAgentsMeta(projectRoot));
|
|
@@ -916,8 +814,8 @@ function registerRulesApi(app, projectRoot) {
|
|
|
916
814
|
});
|
|
917
815
|
}
|
|
918
816
|
|
|
919
|
-
// src/api/
|
|
920
|
-
function
|
|
817
|
+
// src/api/knowledge-context.ts
|
|
818
|
+
function registerKnowledgeContextApi(app, projectRoot) {
|
|
921
819
|
app.get("/api/rules/context", async (req, res) => {
|
|
922
820
|
const path = typeof req.query.path === "string" ? req.query.path.trim() : "";
|
|
923
821
|
if (path.length === 0) {
|
|
@@ -929,7 +827,7 @@ function registerRulesContextApi(app, projectRoot) {
|
|
|
929
827
|
return;
|
|
930
828
|
}
|
|
931
829
|
try {
|
|
932
|
-
const result = await
|
|
830
|
+
const result = await getKnowledge(projectRoot, { path });
|
|
933
831
|
res.json(result.rules);
|
|
934
832
|
} catch (error) {
|
|
935
833
|
sendUnknownError(res, error);
|
|
@@ -967,7 +865,7 @@ async function createScanReport(targetInput = process.cwd()) {
|
|
|
967
865
|
const framework = detectFramework(target);
|
|
968
866
|
const readmeQuality = getReadmeQuality(target);
|
|
969
867
|
const hasContributing = existsSync(join2(target, "CONTRIBUTING.md"));
|
|
970
|
-
const hasExistingFabric = existsSync(join2(target, ".fabric"
|
|
868
|
+
const hasExistingFabric = existsSync(join2(target, ".fabric"));
|
|
971
869
|
const walkResult = walkFiles(target, DEFAULT_IGNORES);
|
|
972
870
|
return {
|
|
973
871
|
target,
|
|
@@ -1044,13 +942,13 @@ function toPosixPath(path) {
|
|
|
1044
942
|
function buildRecommendations(input) {
|
|
1045
943
|
const recommendations = [];
|
|
1046
944
|
if (!input.hasExistingFabric) {
|
|
1047
|
-
recommendations.push("L0: Run fab init to scaffold .fabric/
|
|
945
|
+
recommendations.push("L0: Run `fab init` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
|
|
1048
946
|
}
|
|
1049
947
|
if (input.readmeQuality === "stub") {
|
|
1050
|
-
recommendations.push("L0: Expand README.md before promoting project facts into Fabric
|
|
948
|
+
recommendations.push("L0: Expand README.md before promoting project facts into Fabric knowledge entries.");
|
|
1051
949
|
}
|
|
1052
950
|
if (!input.hasContributing) {
|
|
1053
|
-
recommendations.push("L0: Add CONTRIBUTING.md or
|
|
951
|
+
recommendations.push("L0: Add CONTRIBUTING.md or capture contribution-flow guidance under .fabric/knowledge/processes/.");
|
|
1054
952
|
}
|
|
1055
953
|
if (input.framework.kind === "unknown") {
|
|
1056
954
|
recommendations.push("L1: Add tech-stack TODOs manually because no framework marker was detected.");
|
|
@@ -1060,42 +958,6 @@ function buildRecommendations(input) {
|
|
|
1060
958
|
return recommendations;
|
|
1061
959
|
}
|
|
1062
960
|
|
|
1063
|
-
// src/api/static.ts
|
|
1064
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1065
|
-
import { dirname, resolve as resolve2 } from "path";
|
|
1066
|
-
import { fileURLToPath } from "url";
|
|
1067
|
-
import express from "express";
|
|
1068
|
-
var DEFAULT_STATIC_DIR = resolve2(dirname(fileURLToPath(import.meta.url)), "static");
|
|
1069
|
-
function registerDashboardStatic(app, options = {}) {
|
|
1070
|
-
if (options.dev ?? process.env.NODE_ENV === "development") {
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
const staticDir = resolve2(options.dashboardDistPath ?? DEFAULT_STATIC_DIR);
|
|
1074
|
-
const indexPath = resolve2(staticDir, "index.html");
|
|
1075
|
-
if (!existsSync2(indexPath)) {
|
|
1076
|
-
warnMissingDashboard(staticDir);
|
|
1077
|
-
app.get("/", (_req, res) => {
|
|
1078
|
-
res.status(404).json({
|
|
1079
|
-
error: {
|
|
1080
|
-
code: "DASHBOARD_DIST_MISSING",
|
|
1081
|
-
message: `Fabric dashboard dist was not found at ${staticDir}. Run pnpm --filter @fenglimg/fabric-dashboard build.`
|
|
1082
|
-
}
|
|
1083
|
-
});
|
|
1084
|
-
});
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
app.use("/", express.static(staticDir, { index: "index.html", fallthrough: true }));
|
|
1088
|
-
app.get(/^\/(?!api(?:\/|$)|mcp(?:\/|$)|events(?:\/|$)).*/, (_req, res) => {
|
|
1089
|
-
res.sendFile(indexPath);
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
function warnMissingDashboard(staticDir) {
|
|
1093
|
-
process.stderr.write(
|
|
1094
|
-
`[fabric-server] dashboard dist missing at ${staticDir}; '/' will return 404 until dashboard assets are built.
|
|
1095
|
-
`
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
961
|
// src/middleware/bearer-auth.ts
|
|
1100
962
|
import { createHash, timingSafeEqual } from "crypto";
|
|
1101
963
|
function createBearerAuthMiddleware(token) {
|
|
@@ -1221,23 +1083,13 @@ function handleCacheWatcherEvent(relativePath, projectRoot, sessions, timers) {
|
|
|
1221
1083
|
);
|
|
1222
1084
|
return;
|
|
1223
1085
|
}
|
|
1224
|
-
if (normalized
|
|
1225
|
-
contextCache.invalidate("file_watch", projectRoot);
|
|
1226
|
-
clearTimeout(timers.getAgentsMdTimer());
|
|
1227
|
-
timers.setAgentsMdTimer(
|
|
1228
|
-
setTimeout(() => {
|
|
1229
|
-
notifyAllSessions(sessions, "resource_updated", AGENTS_MD_RESOURCE_URI);
|
|
1230
|
-
}, NOTIFY_DEBOUNCE_MS)
|
|
1231
|
-
);
|
|
1232
|
-
return;
|
|
1233
|
-
}
|
|
1234
|
-
if (normalized.startsWith(".fabric/rules/") && normalized.endsWith(".md")) {
|
|
1086
|
+
if (normalized.startsWith(".fabric/knowledge/") && normalized.endsWith(".md")) {
|
|
1235
1087
|
contextCache.invalidate("file_watch", projectRoot);
|
|
1236
|
-
|
|
1088
|
+
invalidateKnowledgeSyncCooldown(projectRoot);
|
|
1237
1089
|
}
|
|
1238
1090
|
}
|
|
1239
1091
|
function createFabricHttpApp(options) {
|
|
1240
|
-
const { projectRoot, host = DEFAULT_HOST, authToken
|
|
1092
|
+
const { projectRoot, host = DEFAULT_HOST, authToken } = options;
|
|
1241
1093
|
const app = createMcpExpressApp({ host });
|
|
1242
1094
|
const eventStore = new JsonlEventStore(projectRoot);
|
|
1243
1095
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -1245,8 +1097,8 @@ function createFabricHttpApp(options) {
|
|
|
1245
1097
|
const cacheWatcher = chokidar2.watch(
|
|
1246
1098
|
[
|
|
1247
1099
|
".fabric/agents.meta.json",
|
|
1248
|
-
".fabric/
|
|
1249
|
-
".fabric/
|
|
1100
|
+
".fabric/knowledge/**/*.md",
|
|
1101
|
+
".fabric/knowledge/pending/**/*.md"
|
|
1250
1102
|
],
|
|
1251
1103
|
{
|
|
1252
1104
|
cwd: projectRoot,
|
|
@@ -1291,13 +1143,12 @@ function createFabricHttpApp(options) {
|
|
|
1291
1143
|
app.use("/events", bearerAuth);
|
|
1292
1144
|
app.use("/mcp", bearerAuth);
|
|
1293
1145
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1146
|
+
registerKnowledgeApi(app, projectRoot);
|
|
1147
|
+
registerKnowledgeContextApi(app, projectRoot);
|
|
1296
1148
|
registerLedgerApi(app, projectRoot);
|
|
1297
1149
|
registerHistoryApi(app, projectRoot);
|
|
1298
1150
|
registerScanApi(app, projectRoot);
|
|
1299
1151
|
registerDoctorApi(app, projectRoot);
|
|
1300
|
-
registerIntentApi(app, projectRoot);
|
|
1301
1152
|
app.get("/events", createEventsHandler({ projectRoot }));
|
|
1302
1153
|
app.all("/mcp", async (req, res) => {
|
|
1303
1154
|
const sessionId = readHeader(req.headers["mcp-session-id"]);
|
|
@@ -1317,7 +1168,6 @@ function createFabricHttpApp(options) {
|
|
|
1317
1168
|
const session = await createSession(eventStore, sessions);
|
|
1318
1169
|
await session.transport.handleRequest(req, res, req.body);
|
|
1319
1170
|
});
|
|
1320
|
-
registerDashboardStatic(app, { dashboardDistPath, dev });
|
|
1321
1171
|
return app;
|
|
1322
1172
|
}
|
|
1323
1173
|
function notifyAllSessions(sessions, kind, uri) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Server } from 'node:http';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import { AgentsMeta,
|
|
3
|
+
import { AgentsMeta, KnowledgeTestIndex, AgentsLayer, AgentsTopologyType, Layer, KnowledgeType, StableId, AgentsMetaCounters, EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem } from '@fenglimg/fabric-shared';
|
|
4
|
+
import { FabExtractKnowledgeInput, FabExtractKnowledgeOutput, FabReviewInput, FabReviewOutput } from '@fenglimg/fabric-shared/schemas/api-contracts';
|
|
4
5
|
import { IOFabricError } from '@fenglimg/fabric-shared/errors';
|
|
5
6
|
|
|
6
7
|
interface InFlightTracker {
|
|
@@ -76,33 +77,116 @@ type DoctorFixReport = {
|
|
|
76
77
|
message: string;
|
|
77
78
|
report: DoctorReport;
|
|
78
79
|
};
|
|
80
|
+
type DoctorApplyLintMutationKind = "knowledge_orphan_demote_required" | "knowledge_stale_archive_required" | "knowledge_index_drift" | "knowledge_pending_auto_archive" | "knowledge_session_hints_stale_cleanup";
|
|
81
|
+
type DoctorApplyLintMutation = {
|
|
82
|
+
kind: DoctorApplyLintMutationKind;
|
|
83
|
+
path: string;
|
|
84
|
+
detail: string;
|
|
85
|
+
applied: boolean;
|
|
86
|
+
error?: string;
|
|
87
|
+
};
|
|
88
|
+
type DoctorApplyLintReport = {
|
|
89
|
+
changed: boolean;
|
|
90
|
+
mutations: DoctorApplyLintMutation[];
|
|
91
|
+
manual_errors: DoctorIssue[];
|
|
92
|
+
aborted: boolean;
|
|
93
|
+
abort_reason?: string;
|
|
94
|
+
message: string;
|
|
95
|
+
report: DoctorReport;
|
|
96
|
+
};
|
|
79
97
|
declare function runDoctorReport(target: string): Promise<DoctorReport>;
|
|
80
98
|
declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
|
|
99
|
+
declare function runDoctorApplyLint(target: string): Promise<DoctorApplyLintReport>;
|
|
81
100
|
|
|
82
|
-
type
|
|
83
|
-
type
|
|
101
|
+
type KnowledgeMetaBuildSource = "doctor_fix" | "sync_meta";
|
|
102
|
+
type KnowledgeMetaBuildResult = {
|
|
84
103
|
meta: AgentsMeta;
|
|
85
|
-
|
|
104
|
+
knowledgeTestIndex: KnowledgeTestIndex;
|
|
86
105
|
changed: boolean;
|
|
87
106
|
};
|
|
88
|
-
type
|
|
89
|
-
source:
|
|
107
|
+
type WriteKnowledgeMetaOptions = {
|
|
108
|
+
source: KnowledgeMetaBuildSource;
|
|
90
109
|
};
|
|
91
|
-
declare function
|
|
92
|
-
declare function
|
|
93
|
-
declare function
|
|
94
|
-
declare function
|
|
95
|
-
declare function
|
|
96
|
-
declare function
|
|
97
|
-
declare function
|
|
110
|
+
declare function buildKnowledgeMeta(projectRootInput: string): Promise<KnowledgeMetaBuildResult>;
|
|
111
|
+
declare function writeKnowledgeMeta(projectRootInput: string, options: WriteKnowledgeMetaOptions): Promise<KnowledgeMetaBuildResult>;
|
|
112
|
+
declare function computeKnowledgeBasedAgentsMeta(projectRootInput: string, existingMeta?: AgentsMeta): Promise<AgentsMeta>;
|
|
113
|
+
declare function computeKnowledgeTestIndex(projectRootInput: string, computedMeta: AgentsMeta, previousIndex?: KnowledgeTestIndex): Promise<KnowledgeTestIndex>;
|
|
114
|
+
declare function deriveKnowledgeMetaLayer(relativePath: string): AgentsLayer;
|
|
115
|
+
declare function deriveKnowledgeMetaTopologyType(relativePath: string): AgentsTopologyType;
|
|
116
|
+
declare function isSameKnowledgeTestIndex(left: KnowledgeTestIndex, right: KnowledgeTestIndex): boolean;
|
|
98
117
|
declare function stableStringify(value: unknown): string;
|
|
99
118
|
|
|
100
119
|
/**
|
|
101
|
-
*
|
|
120
|
+
* v2.0 KnowledgeIdAllocator
|
|
121
|
+
*
|
|
122
|
+
* Wraps the pure `allocateKnowledgeId` allocator with persistence: it reads
|
|
123
|
+
* `agents.meta.json`, advances the counter for the requested (layer, type)
|
|
124
|
+
* pair, and writes the updated meta atomically (write-to-tmp + rename) so
|
|
125
|
+
* concurrent readers always see a consistent file.
|
|
126
|
+
*
|
|
127
|
+
* Counters are MONOTONIC across the lifetime of the meta file: deleting a
|
|
128
|
+
* knowledge entry does NOT free its counter slot, so previously-allocated
|
|
129
|
+
* stable_ids remain unique even after their files are removed.
|
|
130
|
+
*
|
|
131
|
+
* Key invariants:
|
|
132
|
+
* - Counters envelope is initialized to zeros if absent (v1.x meta compat).
|
|
133
|
+
* - Each allocate() call performs read → mutate → atomic-write in sequence.
|
|
134
|
+
* - The returned id is guaranteed to differ from every previously-returned
|
|
135
|
+
* id for the same meta path.
|
|
102
136
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
137
|
+
declare class KnowledgeIdAllocator {
|
|
138
|
+
private readonly metaPath;
|
|
139
|
+
constructor(metaPath: string);
|
|
140
|
+
/**
|
|
141
|
+
* Allocate the next stable_id for the given (layer, type) pair and persist
|
|
142
|
+
* the advanced counter to `agents.meta.json`.
|
|
143
|
+
*/
|
|
144
|
+
allocate(layer: Layer, type: KnowledgeType): Promise<StableId>;
|
|
145
|
+
/**
|
|
146
|
+
* Returns the current counters envelope, defaulting to all-zero slots when
|
|
147
|
+
* the meta file is absent or pre-v2.0 (counters key missing).
|
|
148
|
+
*/
|
|
149
|
+
getCounters(): Promise<AgentsMetaCounters>;
|
|
150
|
+
private readMeta;
|
|
151
|
+
private normalizeCounters;
|
|
152
|
+
private writeMetaAtomic;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Append-evidence-on-collision service for fab_extract_knowledge.
|
|
157
|
+
*
|
|
158
|
+
* Idempotency_key = sha256({source_session, type, slug}). When the same
|
|
159
|
+
* triple hits an existing pending file (verified by frontmatter
|
|
160
|
+
* `x-fabric-idempotency-key`), the body is preserved and a fresh
|
|
161
|
+
* `## Evidence (call N)` section is appended — LLM-regenerated summaries
|
|
162
|
+
* stay observable without overwriting prior context.
|
|
163
|
+
*
|
|
164
|
+
* NO `id` frontmatter is written — Q2 late-bind delegates id allocation
|
|
165
|
+
* to rc.3 fab_review approve. See planning-context.md "NO id frontmatter".
|
|
166
|
+
*/
|
|
167
|
+
declare function extractKnowledge(projectRoot: string, input: FabExtractKnowledgeInput): Promise<FabExtractKnowledgeOutput>;
|
|
105
168
|
|
|
169
|
+
/**
|
|
170
|
+
* v2.0 rc.3 fab_review service.
|
|
171
|
+
*
|
|
172
|
+
* Pure async dispatcher over a discriminated union of 6 actions (list, approve,
|
|
173
|
+
* reject, modify, search, defer). All branches are implemented as of TASK-002.
|
|
174
|
+
*
|
|
175
|
+
* Approve performs late-bind id allocation (KP-/KT- + type-code + monotonic
|
|
176
|
+
* counter via KnowledgeIdAllocator), emits 2-phase events (knowledge_promote_started
|
|
177
|
+
* → knowledge_promoted | knowledge_promote_failed), and uses git mv to preserve
|
|
178
|
+
* file history when the target lives in the team layer (same repo). Personal
|
|
179
|
+
* layer files use plain fs.rename because they live under ~/.fabric/ outside
|
|
180
|
+
* the project's git tree.
|
|
181
|
+
*
|
|
182
|
+
* Modify implements the only legal stable_id mutation: layer-flip across
|
|
183
|
+
* KP/KT counter spaces. Allocates a new id under the target layer, renames
|
|
184
|
+
* the file across layer roots, emits knowledge_layer_changed.
|
|
185
|
+
*/
|
|
186
|
+
declare function reviewKnowledge(projectRoot: string, input: FabReviewInput): Promise<FabReviewOutput>;
|
|
187
|
+
|
|
188
|
+
type StoredEventLedgerEvent = EventLedgerEvent;
|
|
189
|
+
declare function appendEventLedgerEvent(projectRoot: string, event: EventLedgerEventInput): Promise<StoredEventLedgerEvent>;
|
|
106
190
|
/**
|
|
107
191
|
* Synchronously fsync the event ledger file to ensure OS page-cache buffers are
|
|
108
192
|
* flushed to durable storage. Must be called AFTER in-flight drain but BEFORE
|
|
@@ -113,22 +197,79 @@ declare const AGENTS_MD_RESOURCE_URI = "fabric://bootstrap-readme";
|
|
|
113
197
|
*/
|
|
114
198
|
declare function flushAndSyncEventLedger(projectRoot: string): void;
|
|
115
199
|
|
|
200
|
+
type PlanContextInput = {
|
|
201
|
+
paths: string[];
|
|
202
|
+
intent?: string;
|
|
203
|
+
known_tech?: string[];
|
|
204
|
+
detected_entities?: Record<string, string[]>;
|
|
205
|
+
client_hash?: string;
|
|
206
|
+
correlation_id?: string;
|
|
207
|
+
session_id?: string;
|
|
208
|
+
target_paths?: string[];
|
|
209
|
+
};
|
|
210
|
+
type RequirementProfile = {
|
|
211
|
+
target_path: string;
|
|
212
|
+
path_segments: string[];
|
|
213
|
+
extension: string;
|
|
214
|
+
known_tech: string[];
|
|
215
|
+
user_intent: string;
|
|
216
|
+
detected_entities: string[];
|
|
217
|
+
};
|
|
218
|
+
type PlanContextEntry = {
|
|
219
|
+
path: string;
|
|
220
|
+
requirement_profile: RequirementProfile;
|
|
221
|
+
description_index: RuleDescriptionIndexItem[];
|
|
222
|
+
};
|
|
223
|
+
type PlanContextResult = {
|
|
224
|
+
revision_hash: string;
|
|
225
|
+
stale: boolean;
|
|
226
|
+
selection_token: string;
|
|
227
|
+
entries: PlanContextEntry[];
|
|
228
|
+
shared: {
|
|
229
|
+
description_index: RuleDescriptionIndexItem[];
|
|
230
|
+
preflight_diagnostics: Array<{
|
|
231
|
+
code: "missing_description";
|
|
232
|
+
severity: "warn";
|
|
233
|
+
message: string;
|
|
234
|
+
stable_ids?: string[];
|
|
235
|
+
path?: string;
|
|
236
|
+
}>;
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
type SelectionTokenState = {
|
|
240
|
+
token: string;
|
|
241
|
+
revision_hash: string;
|
|
242
|
+
target_paths: string[];
|
|
243
|
+
required_stable_ids: string[];
|
|
244
|
+
ai_selectable_stable_ids: string[];
|
|
245
|
+
created_at: number;
|
|
246
|
+
expires_at: number;
|
|
247
|
+
};
|
|
248
|
+
declare function planContext(projectRoot: string, input: PlanContextInput): Promise<PlanContextResult>;
|
|
249
|
+
declare function readSelectionToken(token: string, now?: number): SelectionTokenState | undefined;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Shared constants used across the server package.
|
|
253
|
+
*/
|
|
254
|
+
/** MCP resource URI for the project's bootstrap README (L0 rules) file. */
|
|
255
|
+
declare const AGENTS_MD_RESOURCE_URI = "fabric://bootstrap-readme";
|
|
256
|
+
|
|
116
257
|
/**
|
|
117
|
-
*
|
|
258
|
+
* knowledge-sync.ts — Rule-sync orchestrator framework (R28, TASK-011)
|
|
118
259
|
*
|
|
119
|
-
* Public surface:
|
|
260
|
+
* Public surface: ensureKnowledgeFresh, reconcileKnowledge + exported types.
|
|
120
261
|
* Internal helpers are co-located in this file.
|
|
121
262
|
* Does NOT wire any consumers (MCP tools, doctor, watchers).
|
|
122
263
|
*
|
|
123
264
|
* Distinction between the two public entry points:
|
|
124
265
|
*
|
|
125
|
-
* - `
|
|
266
|
+
* - `ensureKnowledgeFresh`: detects drift, emits ledger events, invalidates cache.
|
|
126
267
|
* Does NOT rewrite agents.meta.json. Optimised for hot-path consumers (MCP tools).
|
|
127
268
|
*
|
|
128
|
-
* - `
|
|
269
|
+
* - `reconcileKnowledge`: full scan + rewrites agents.meta.json (via knowledge-meta-builder)
|
|
129
270
|
* + emits ledger events. Used by startup (TASK-022) and doctor repair (TASK-023).
|
|
130
271
|
*/
|
|
131
|
-
interface
|
|
272
|
+
interface KnowledgeSyncOptions {
|
|
132
273
|
mode?: "incremental" | "full";
|
|
133
274
|
/** When true, invalid frontmatter throws RuleValidationError (default: false — collect as warning). */
|
|
134
275
|
throwOnInvalidFrontmatter?: boolean;
|
|
@@ -140,23 +281,23 @@ interface StructuredWarning {
|
|
|
140
281
|
action_hint: string;
|
|
141
282
|
}
|
|
142
283
|
/**
|
|
143
|
-
* Granular ledger event shape for
|
|
144
|
-
* These are returned in
|
|
145
|
-
* using the nearest available ledger event type (
|
|
146
|
-
*
|
|
284
|
+
* Granular ledger event shape for knowledge-sync operations.
|
|
285
|
+
* These are returned in KnowledgeSyncReport and also appended to the event ledger
|
|
286
|
+
* using the nearest available ledger event type (knowledge_drift_detected).
|
|
287
|
+
* The shape below is what callers receive in `.events`.
|
|
147
288
|
*/
|
|
148
|
-
interface
|
|
289
|
+
interface KnowledgeSyncLedgerEvent {
|
|
149
290
|
type: "rule_content_changed" | "rule_added" | "rule_removed";
|
|
150
291
|
stable_id: string;
|
|
151
292
|
path: string;
|
|
152
293
|
prev_hash: string | null;
|
|
153
294
|
new_hash: string | null;
|
|
154
295
|
changed_fields: string[];
|
|
155
|
-
source: "
|
|
296
|
+
source: "ensureKnowledgeFresh" | "reconcileKnowledge";
|
|
156
297
|
}
|
|
157
298
|
/** Alias so the public API says LedgerEvent (as documented). */
|
|
158
|
-
type LedgerEvent =
|
|
159
|
-
interface
|
|
299
|
+
type LedgerEvent = KnowledgeSyncLedgerEvent;
|
|
300
|
+
interface KnowledgeSyncReport {
|
|
160
301
|
status: "fresh" | "reconciled" | "errors";
|
|
161
302
|
events: LedgerEvent[];
|
|
162
303
|
warnings: StructuredWarning[];
|
|
@@ -167,8 +308,8 @@ interface RuleSyncReport {
|
|
|
167
308
|
* invalidates the cache. Does NOT rewrite agents.meta.json. Optimised for
|
|
168
309
|
* hot-path consumers (MCP tools).
|
|
169
310
|
*/
|
|
170
|
-
declare function
|
|
171
|
-
interface
|
|
311
|
+
declare function ensureKnowledgeFresh(projectRoot: string, opts?: KnowledgeSyncOptions): Promise<KnowledgeSyncReport>;
|
|
312
|
+
interface ReconcileKnowledgeOptions {
|
|
172
313
|
/** Identifies who triggered the reconcile; controls which summary ledger event is written. */
|
|
173
314
|
trigger?: "startup" | "doctor" | "manual";
|
|
174
315
|
}
|
|
@@ -181,7 +322,7 @@ interface ReconcileRulesOptions {
|
|
|
181
322
|
* ledger event is appended after per-file drift events. Other trigger values
|
|
182
323
|
* append a `meta_reconciled` event. Omitting the trigger skips the summary.
|
|
183
324
|
*/
|
|
184
|
-
declare function
|
|
325
|
+
declare function reconcileKnowledge(projectRoot: string, opts?: ReconcileKnowledgeOptions): Promise<KnowledgeSyncReport>;
|
|
185
326
|
|
|
186
327
|
declare class ServeLockHeldError extends IOFabricError {
|
|
187
328
|
readonly code = "SERVE_LOCK_HELD";
|
|
@@ -239,8 +380,6 @@ declare function startHttpServer(options: {
|
|
|
239
380
|
projectRoot: string;
|
|
240
381
|
host?: string;
|
|
241
382
|
authToken?: string;
|
|
242
|
-
dashboardDistPath?: string;
|
|
243
|
-
dev?: boolean;
|
|
244
383
|
}): Promise<Server>;
|
|
245
384
|
|
|
246
|
-
export { AGENTS_MD_RESOURCE_URI, type AcquireOptions, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type InFlightTracker, LEDGER_PATH, LEGACY_LEDGER_PATH, type LedgerEvent, type LockState, type
|
|
385
|
+
export { AGENTS_MD_RESOURCE_URI, type AcquireOptions, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type InFlightTracker, KnowledgeIdAllocator, type KnowledgeMetaBuildResult, type KnowledgeMetaBuildSource, type KnowledgeSyncLedgerEvent, type KnowledgeSyncOptions, type KnowledgeSyncReport, LEDGER_PATH, LEGACY_LEDGER_PATH, type LedgerEvent, type LockState, type PlanContextInput, type PlanContextResult, type ReconcileKnowledgeOptions, type RequirementProfile, type SelectionTokenState, ServeLockHeldError, type ShutdownHandlerDeps, type StructuredWarning, type WriteKnowledgeMetaOptions, acquireLock, appendEventLedgerEvent, buildKnowledgeMeta, checkLockOrThrow, computeKnowledgeBasedAgentsMeta, computeKnowledgeTestIndex, createFabricServer, createInFlightTracker, createShutdownHandler, deriveKnowledgeMetaLayer, deriveKnowledgeMetaTopologyType, ensureKnowledgeFresh, extractKnowledge, flushAndSyncEventLedger, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, isSameKnowledgeTestIndex, planContext, readLockState, readSelectionToken, reconcileKnowledge, releaseLock, reviewKnowledge, runDoctorApplyLint, runDoctorFix, runDoctorReport, stableStringify, startHttpServer, startStdioServer, writeKnowledgeMeta };
|