@fenglimg/fabric-server 1.0.0 → 1.1.0
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-U3IQH5H6.js +851 -0
- package/dist/{http-3BNWQWPP.js → http-FL3MB4L2.js} +45 -422
- package/dist/index.d.ts +61 -1
- package/dist/index.js +154 -38
- package/dist/static/assets/index-BiK8yn_c.js +5 -0
- package/dist/static/index.html +1 -1
- package/package.json +3 -3
- package/dist/chunk-KZO24GUQ.js +0 -199
- package/dist/static/assets/index-D_EcxWYV.js +0 -5
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FABRIC_DIR,
|
|
3
|
+
appendEditIntentAuditEvents,
|
|
4
|
+
appendGetRulesAuditEvent,
|
|
3
5
|
appendLedgerEntry,
|
|
4
6
|
atomicWriteText,
|
|
5
7
|
readAgentsMeta,
|
|
6
8
|
readHumanLock,
|
|
7
9
|
resolveProjectRoot,
|
|
10
|
+
runDoctorAuditReport,
|
|
11
|
+
runDoctorReport,
|
|
8
12
|
sha256
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-U3IQH5H6.js";
|
|
10
14
|
|
|
11
15
|
// src/index.ts
|
|
12
16
|
import { resolve } from "path";
|
|
@@ -25,6 +29,15 @@ async function appendIntent(projectRoot, input) {
|
|
|
25
29
|
ts,
|
|
26
30
|
source: "ai"
|
|
27
31
|
});
|
|
32
|
+
try {
|
|
33
|
+
await appendEditIntentAuditEvents(projectRoot, {
|
|
34
|
+
affected_paths: entry.affected_paths,
|
|
35
|
+
intent: entry.intent,
|
|
36
|
+
ledger_entry_id: entry.id,
|
|
37
|
+
ts
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
28
41
|
return {
|
|
29
42
|
success: true,
|
|
30
43
|
timestamp: ts,
|
|
@@ -76,17 +89,67 @@ var PRIORITY_ORDER = {
|
|
|
76
89
|
low: 2
|
|
77
90
|
};
|
|
78
91
|
async function getRules(projectRoot, input) {
|
|
92
|
+
const context = await loadGetRulesContext(projectRoot);
|
|
93
|
+
const stale = input.client_hash !== void 0 && input.client_hash !== context.meta.revision;
|
|
94
|
+
const rules = await resolveRulesForPath(projectRoot, context, input.path);
|
|
95
|
+
const result = {
|
|
96
|
+
revision_hash: context.meta.revision,
|
|
97
|
+
stale,
|
|
98
|
+
rules
|
|
99
|
+
};
|
|
100
|
+
try {
|
|
101
|
+
await appendGetRulesAuditEvent(projectRoot, {
|
|
102
|
+
path: input.path,
|
|
103
|
+
client_hash: input.client_hash
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
async function loadGetRulesContext(projectRoot) {
|
|
79
110
|
const meta = readAgentsMeta(projectRoot);
|
|
80
|
-
const stale = input.client_hash !== void 0 && input.client_hash !== meta.revision;
|
|
81
|
-
const requestedPath = normalizePath(input.path);
|
|
82
111
|
const l0Content = await readFile(join(projectRoot, "AGENTS.md"), "utf8");
|
|
83
|
-
const
|
|
112
|
+
const humanLockedNearby = (await readHumanLock(projectRoot)).map((entry) => ({
|
|
113
|
+
file: entry.file,
|
|
114
|
+
excerpt: JSON.stringify(entry)
|
|
115
|
+
}));
|
|
116
|
+
return {
|
|
117
|
+
meta,
|
|
118
|
+
l0Content,
|
|
119
|
+
humanLockedNearby
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function resolveRulesForPath(projectRoot, context, path, options = {}) {
|
|
123
|
+
const loadedRules = await loadRulesForPath(projectRoot, context.meta, path);
|
|
124
|
+
const { L1, L2 } = partitionRulesByLevel(loadedRules, options.dedupeByPath ?? false);
|
|
125
|
+
return {
|
|
126
|
+
L0: context.l0Content,
|
|
127
|
+
L1,
|
|
128
|
+
L2,
|
|
129
|
+
human_locked_nearby: context.humanLockedNearby
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function normalizeRulesPath(value) {
|
|
133
|
+
return value.replaceAll("\\", "/");
|
|
134
|
+
}
|
|
135
|
+
function classifyNode(nodeId) {
|
|
136
|
+
if (nodeId.startsWith("L1/")) {
|
|
137
|
+
return "L1";
|
|
138
|
+
}
|
|
139
|
+
if (nodeId.startsWith("L2/")) {
|
|
140
|
+
return "L2";
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
async function loadRulesForPath(projectRoot, meta, path) {
|
|
145
|
+
const requestedPath = normalizeRulesPath(path);
|
|
146
|
+
const matchedNodes = Object.entries(meta.nodes).filter(([, node]) => minimatch(requestedPath, normalizeRulesPath(node.scope_glob), { dot: true })).sort((left, right) => {
|
|
84
147
|
const [leftId, leftNode] = left;
|
|
85
148
|
const [rightId, rightNode] = right;
|
|
86
149
|
const priorityDelta = PRIORITY_ORDER[leftNode.priority] - PRIORITY_ORDER[rightNode.priority];
|
|
87
150
|
return priorityDelta !== 0 ? priorityDelta : leftId.localeCompare(rightId);
|
|
88
151
|
});
|
|
89
|
-
|
|
152
|
+
return await Promise.all(
|
|
90
153
|
matchedNodes.map(async ([nodeId, node]) => ({
|
|
91
154
|
level: classifyNode(nodeId),
|
|
92
155
|
entry: {
|
|
@@ -95,6 +158,8 @@ async function getRules(projectRoot, input) {
|
|
|
95
158
|
}
|
|
96
159
|
}))
|
|
97
160
|
);
|
|
161
|
+
}
|
|
162
|
+
function partitionRulesByLevel(loadedRules, dedupeByPath) {
|
|
98
163
|
const l1 = [];
|
|
99
164
|
const l2 = [];
|
|
100
165
|
for (const rule of loadedRules) {
|
|
@@ -106,32 +171,20 @@ async function getRules(projectRoot, input) {
|
|
|
106
171
|
l2.push(rule.entry);
|
|
107
172
|
}
|
|
108
173
|
}
|
|
109
|
-
const humanLockedNearby = (await readHumanLock(projectRoot)).map((entry) => ({
|
|
110
|
-
file: entry.file,
|
|
111
|
-
excerpt: JSON.stringify(entry)
|
|
112
|
-
}));
|
|
113
174
|
return {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
rules: {
|
|
117
|
-
L0: l0Content,
|
|
118
|
-
L1: l1,
|
|
119
|
-
L2: l2,
|
|
120
|
-
human_locked_nearby: humanLockedNearby
|
|
121
|
-
}
|
|
175
|
+
L1: dedupeByPath ? dedupeEntriesByPath(l1) : l1,
|
|
176
|
+
L2: dedupeByPath ? dedupeEntriesByPath(l2) : l2
|
|
122
177
|
};
|
|
123
178
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
179
|
+
function dedupeEntriesByPath(entries) {
|
|
180
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
181
|
+
return entries.filter((entry) => {
|
|
182
|
+
if (seenPaths.has(entry.path)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
seenPaths.add(entry.path);
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
135
188
|
}
|
|
136
189
|
|
|
137
190
|
// src/tools/get-rules.ts
|
|
@@ -162,9 +215,69 @@ function registerGetRules(server) {
|
|
|
162
215
|
);
|
|
163
216
|
}
|
|
164
217
|
|
|
165
|
-
// src/tools/
|
|
218
|
+
// src/tools/plan-context.ts
|
|
166
219
|
import { z as z2 } from "zod";
|
|
167
220
|
|
|
221
|
+
// src/services/plan-context.ts
|
|
222
|
+
async function planContext(projectRoot, input) {
|
|
223
|
+
const context = await loadGetRulesContext(projectRoot);
|
|
224
|
+
const stale = input.client_hash !== void 0 && input.client_hash !== context.meta.revision;
|
|
225
|
+
const uniquePaths = dedupePaths(input.paths);
|
|
226
|
+
const entries = await Promise.all(
|
|
227
|
+
uniquePaths.map(async (path) => ({
|
|
228
|
+
path,
|
|
229
|
+
rules: await resolveRulesForPath(projectRoot, context, path, { dedupeByPath: true })
|
|
230
|
+
}))
|
|
231
|
+
);
|
|
232
|
+
return {
|
|
233
|
+
revision_hash: context.meta.revision,
|
|
234
|
+
stale,
|
|
235
|
+
entries
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function dedupePaths(paths) {
|
|
239
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
240
|
+
return paths.flatMap((path) => {
|
|
241
|
+
const normalizedPath = normalizeRulesPath(path);
|
|
242
|
+
if (seenPaths.has(normalizedPath)) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
seenPaths.add(normalizedPath);
|
|
246
|
+
return [normalizedPath];
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/tools/plan-context.ts
|
|
251
|
+
var inputSchema3 = {
|
|
252
|
+
paths: z2.array(z2.string()).min(1).describe("Candidate file paths to query rules for during planning or architecture review"),
|
|
253
|
+
client_hash: z2.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection")
|
|
254
|
+
};
|
|
255
|
+
function createTextResponse3(payload) {
|
|
256
|
+
return {
|
|
257
|
+
content: [
|
|
258
|
+
{
|
|
259
|
+
type: "text",
|
|
260
|
+
text: JSON.stringify(payload)
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function registerPlanContext(server) {
|
|
266
|
+
server.tool(
|
|
267
|
+
"fab_plan_context",
|
|
268
|
+
"Use during plan or architecture phases to batch-query Fabric rules for multiple candidate paths in one round-trip.",
|
|
269
|
+
inputSchema3,
|
|
270
|
+
async ({ paths, client_hash }) => {
|
|
271
|
+
const projectRoot = resolveProjectRoot();
|
|
272
|
+
const result = await planContext(projectRoot, { paths, client_hash });
|
|
273
|
+
return createTextResponse3(result);
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/tools/update-registry.ts
|
|
279
|
+
import { z as z3 } from "zod";
|
|
280
|
+
|
|
168
281
|
// src/services/update-registry.ts
|
|
169
282
|
import { agentsMetaNodeSchema } from "@fenglimg/fabric-shared";
|
|
170
283
|
import { join as join2 } from "path";
|
|
@@ -231,12 +344,12 @@ function applyRegistryOperation(meta, op, nodeId, data) {
|
|
|
231
344
|
}
|
|
232
345
|
|
|
233
346
|
// src/tools/update-registry.ts
|
|
234
|
-
var
|
|
235
|
-
op:
|
|
236
|
-
node_id:
|
|
237
|
-
data:
|
|
347
|
+
var inputSchema4 = {
|
|
348
|
+
op: z3.enum(["add-node", "remove-node", "update-node"]),
|
|
349
|
+
node_id: z3.string(),
|
|
350
|
+
data: z3.record(z3.unknown()).optional()
|
|
238
351
|
};
|
|
239
|
-
function
|
|
352
|
+
function createTextResponse4(payload) {
|
|
240
353
|
return {
|
|
241
354
|
content: [
|
|
242
355
|
{
|
|
@@ -250,11 +363,11 @@ function registerUpdateRegistry(server) {
|
|
|
250
363
|
server.tool(
|
|
251
364
|
"fab_update_registry",
|
|
252
365
|
"MANDATORY: Call to add, remove, or update Fabric registry nodes instead of editing registry files directly.",
|
|
253
|
-
|
|
366
|
+
inputSchema4,
|
|
254
367
|
async ({ op, node_id, data }) => {
|
|
255
368
|
const projectRoot = resolveProjectRoot();
|
|
256
369
|
const result = await updateRegistry(projectRoot, { op, node_id, data });
|
|
257
|
-
return
|
|
370
|
+
return createTextResponse4(result);
|
|
258
371
|
}
|
|
259
372
|
);
|
|
260
373
|
}
|
|
@@ -273,9 +386,10 @@ function formatError(error) {
|
|
|
273
386
|
function createFabricServer() {
|
|
274
387
|
const server = new McpServer({
|
|
275
388
|
name: "fabric-context-server",
|
|
276
|
-
version: "1.
|
|
389
|
+
version: "1.1.0"
|
|
277
390
|
});
|
|
278
391
|
registerGetRules(server);
|
|
392
|
+
registerPlanContext(server);
|
|
279
393
|
registerAppendIntent(server);
|
|
280
394
|
registerUpdateRegistry(server);
|
|
281
395
|
return server;
|
|
@@ -286,7 +400,7 @@ async function startStdioServer() {
|
|
|
286
400
|
await server.connect(transport);
|
|
287
401
|
}
|
|
288
402
|
async function startHttpServer(options) {
|
|
289
|
-
const { createFabricHttpApp } = await import("./http-
|
|
403
|
+
const { createFabricHttpApp } = await import("./http-FL3MB4L2.js");
|
|
290
404
|
const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
|
|
291
405
|
const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
|
|
292
406
|
return await new Promise((resolveServer, rejectServer) => {
|
|
@@ -310,6 +424,8 @@ if (isMainModule) {
|
|
|
310
424
|
}
|
|
311
425
|
export {
|
|
312
426
|
createFabricServer,
|
|
427
|
+
runDoctorAuditReport,
|
|
428
|
+
runDoctorReport,
|
|
313
429
|
startHttpServer,
|
|
314
430
|
startStdioServer
|
|
315
431
|
};
|