@fenglimg/fabric-server 1.1.0 → 1.3.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/index.js CHANGED
@@ -1,25 +1,29 @@
1
1
  import {
2
+ AGENTS_MD_RESOURCE_URI,
2
3
  FABRIC_DIR,
3
4
  appendEditIntentAuditEvents,
4
5
  appendGetRulesAuditEvent,
5
6
  appendLedgerEntry,
6
7
  atomicWriteText,
8
+ contextCache,
7
9
  readAgentsMeta,
8
10
  readHumanLock,
9
11
  resolveProjectRoot,
10
12
  runDoctorAuditReport,
11
13
  runDoctorReport,
12
14
  sha256
13
- } from "./chunk-U3IQH5H6.js";
15
+ } from "./chunk-DQ7RCYKB.js";
14
16
 
15
17
  // src/index.ts
16
- import { resolve } from "path";
18
+ import { readFile as readFile2 } from "fs/promises";
19
+ import { join as join3, resolve } from "path";
17
20
  import { fileURLToPath } from "url";
18
21
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
22
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
23
 
21
24
  // src/tools/append-intent.ts
22
25
  import { aiLedgerEntrySchema } from "@fenglimg/fabric-shared";
26
+ import { z } from "zod";
23
27
 
24
28
  // src/services/append-intent.ts
25
29
  async function appendIntent(projectRoot, input) {
@@ -29,19 +33,22 @@ async function appendIntent(projectRoot, input) {
29
33
  ts,
30
34
  source: "ai"
31
35
  });
36
+ let compliance;
32
37
  try {
33
- await appendEditIntentAuditEvents(projectRoot, {
38
+ const auditResult = await appendEditIntentAuditEvents(projectRoot, {
34
39
  affected_paths: entry.affected_paths,
35
40
  intent: entry.intent,
36
41
  ledger_entry_id: entry.id,
37
42
  ts
38
43
  });
44
+ compliance = auditResult.compliance;
39
45
  } catch {
40
46
  }
41
47
  return {
42
48
  success: true,
43
49
  timestamp: ts,
44
- entry
50
+ entry,
51
+ compliance
45
52
  };
46
53
  }
47
54
 
@@ -53,31 +60,38 @@ var inputSchema = {
53
60
  ts: true
54
61
  })
55
62
  };
56
- function createTextResponse(payload) {
57
- return {
58
- content: [
59
- {
60
- type: "text",
61
- text: JSON.stringify(payload)
62
- }
63
- ]
64
- };
65
- }
63
+ var outputSchema = z.object({
64
+ success: z.boolean(),
65
+ timestamp: z.number(),
66
+ entry: z.record(z.unknown()),
67
+ compliance: z.object({
68
+ compliant: z.boolean(),
69
+ matched_get_rules_ts: z.string().nullable(),
70
+ window_ms: z.number()
71
+ }).optional()
72
+ });
66
73
  function registerAppendIntent(server) {
67
- server.tool(
74
+ server.registerTool(
68
75
  "fab_append_intent",
69
- "MANDATORY: Call after a completed task to append an intent ledger entry for Fabric.",
70
- inputSchema,
76
+ {
77
+ description: "Call after a completed task to append an intent ledger entry for Fabric.",
78
+ inputSchema,
79
+ outputSchema,
80
+ annotations: { readOnlyHint: false }
81
+ },
71
82
  async ({ entry }) => {
72
83
  const projectRoot = resolveProjectRoot();
73
84
  const result = await appendIntent(projectRoot, { entry });
74
- return createTextResponse(result);
85
+ return {
86
+ content: [{ type: "text", text: JSON.stringify(result) }],
87
+ structuredContent: result
88
+ };
75
89
  }
76
90
  );
77
91
  }
78
92
 
79
93
  // src/tools/get-rules.ts
80
- import { z } from "zod";
94
+ import { z as z2 } from "zod";
81
95
 
82
96
  // src/services/get-rules.ts
83
97
  import { readFile } from "fs/promises";
@@ -107,17 +121,23 @@ async function getRules(projectRoot, input) {
107
121
  return result;
108
122
  }
109
123
  async function loadGetRulesContext(projectRoot) {
110
- const meta = readAgentsMeta(projectRoot);
124
+ const cached = contextCache.get("context", projectRoot);
125
+ if (cached !== void 0) {
126
+ return cached;
127
+ }
128
+ const meta = await readAgentsMeta(projectRoot);
111
129
  const l0Content = await readFile(join(projectRoot, "AGENTS.md"), "utf8");
112
130
  const humanLockedNearby = (await readHumanLock(projectRoot)).map((entry) => ({
113
131
  file: entry.file,
114
132
  excerpt: JSON.stringify(entry)
115
133
  }));
116
- return {
134
+ const context = {
117
135
  meta,
118
136
  l0Content,
119
137
  humanLockedNearby
120
138
  };
139
+ contextCache.set("context", projectRoot, context);
140
+ return context;
121
141
  }
122
142
  async function resolveRulesForPath(projectRoot, context, path, options = {}) {
123
143
  const loadedRules = await loadRulesForPath(projectRoot, context.meta, path);
@@ -189,34 +209,43 @@ function dedupeEntriesByPath(entries) {
189
209
 
190
210
  // src/tools/get-rules.ts
191
211
  var inputSchema2 = {
192
- path: z.string().describe("Target file path to query rules for"),
193
- client_hash: z.string().optional().describe("Revision hash from prior fab_get_rules response; enables stale detection")
212
+ path: z2.string().describe("Target file path to query rules for"),
213
+ client_hash: z2.string().optional().describe("Revision hash from prior fab_get_rules response; enables stale detection")
194
214
  };
195
- function createTextResponse2(payload) {
196
- return {
197
- content: [
198
- {
199
- type: "text",
200
- text: JSON.stringify(payload)
201
- }
202
- ]
203
- };
204
- }
215
+ var rulesEntrySchema = z2.object({ path: z2.string(), content: z2.string() });
216
+ var humanLockedSchema = z2.object({ file: z2.string(), excerpt: z2.string() });
217
+ var outputSchema2 = z2.object({
218
+ revision_hash: z2.string(),
219
+ stale: z2.boolean(),
220
+ rules: z2.object({
221
+ L0: z2.string(),
222
+ L1: z2.array(rulesEntrySchema),
223
+ L2: z2.array(rulesEntrySchema),
224
+ human_locked_nearby: z2.array(humanLockedSchema)
225
+ })
226
+ });
205
227
  function registerGetRules(server) {
206
- server.tool(
228
+ server.registerTool(
207
229
  "fab_get_rules",
208
- "MANDATORY: Call before modifying any file to retrieve Fabric rules for a target path.",
209
- inputSchema2,
230
+ {
231
+ description: "Call before modifying any file to retrieve Fabric rules for a target path.",
232
+ inputSchema: inputSchema2,
233
+ outputSchema: outputSchema2,
234
+ annotations: { readOnlyHint: true }
235
+ },
210
236
  async ({ path, client_hash }) => {
211
237
  const projectRoot = resolveProjectRoot();
212
238
  const result = await getRules(projectRoot, { path, client_hash });
213
- return createTextResponse2(result);
239
+ return {
240
+ content: [{ type: "text", text: JSON.stringify(result) }],
241
+ structuredContent: result
242
+ };
214
243
  }
215
244
  );
216
245
  }
217
246
 
218
247
  // src/tools/plan-context.ts
219
- import { z as z2 } from "zod";
248
+ import { z as z3 } from "zod";
220
249
 
221
250
  // src/services/plan-context.ts
222
251
  async function planContext(projectRoot, input) {
@@ -249,41 +278,56 @@ function dedupePaths(paths) {
249
278
 
250
279
  // src/tools/plan-context.ts
251
280
  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")
281
+ paths: z3.array(z3.string()).min(2).describe("Candidate file paths to query rules for during planning or architecture review"),
282
+ client_hash: z3.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection")
254
283
  };
255
- function createTextResponse3(payload) {
256
- return {
257
- content: [
258
- {
259
- type: "text",
260
- text: JSON.stringify(payload)
261
- }
262
- ]
263
- };
264
- }
284
+ var rulesEntrySchema2 = z3.object({ path: z3.string(), content: z3.string() });
285
+ var humanLockedSchema2 = z3.object({ file: z3.string(), excerpt: z3.string() });
286
+ var rulesPayloadSchema = z3.object({
287
+ L0: z3.string(),
288
+ L1: z3.array(rulesEntrySchema2),
289
+ L2: z3.array(rulesEntrySchema2),
290
+ human_locked_nearby: z3.array(humanLockedSchema2)
291
+ });
292
+ var outputSchema3 = z3.object({
293
+ revision_hash: z3.string(),
294
+ stale: z3.boolean(),
295
+ entries: z3.array(
296
+ z3.object({
297
+ path: z3.string(),
298
+ rules: rulesPayloadSchema
299
+ })
300
+ )
301
+ });
265
302
  function registerPlanContext(server) {
266
- server.tool(
303
+ server.registerTool(
267
304
  "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,
305
+ {
306
+ description: "Use during plan or architecture phases to batch-query Fabric rules for multiple candidate paths in one round-trip. Use fab_get_rules for single-file queries; use fab_plan_context for 2+ files.",
307
+ inputSchema: inputSchema3,
308
+ outputSchema: outputSchema3,
309
+ annotations: { readOnlyHint: true }
310
+ },
270
311
  async ({ paths, client_hash }) => {
271
312
  const projectRoot = resolveProjectRoot();
272
313
  const result = await planContext(projectRoot, { paths, client_hash });
273
- return createTextResponse3(result);
314
+ return {
315
+ content: [{ type: "text", text: JSON.stringify(result) }],
316
+ structuredContent: result
317
+ };
274
318
  }
275
319
  );
276
320
  }
277
321
 
278
322
  // src/tools/update-registry.ts
279
- import { z as z3 } from "zod";
323
+ import { z as z4 } from "zod";
280
324
 
281
325
  // src/services/update-registry.ts
282
326
  import { agentsMetaNodeSchema } from "@fenglimg/fabric-shared";
283
327
  import { join as join2 } from "path";
284
328
  async function updateRegistry(projectRoot, input) {
285
329
  const metaPath = join2(projectRoot, FABRIC_DIR, "agents.meta.json");
286
- const currentMeta = readAgentsMeta(projectRoot);
330
+ const currentMeta = await readAgentsMeta(projectRoot);
287
331
  const nextMeta = applyRegistryOperation(currentMeta, input.op, input.node_id, input.data);
288
332
  const newRevision = computeRevision(nextMeta);
289
333
  await atomicWriteText(
@@ -298,6 +342,7 @@ async function updateRegistry(projectRoot, input) {
298
342
  )}
299
343
  `
300
344
  );
345
+ contextCache.invalidate("meta_write", projectRoot);
301
346
  return {
302
347
  revision_hash: newRevision,
303
348
  success: true
@@ -345,29 +390,35 @@ function applyRegistryOperation(meta, op, nodeId, data) {
345
390
 
346
391
  // src/tools/update-registry.ts
347
392
  var inputSchema4 = {
348
- op: z3.enum(["add-node", "remove-node", "update-node"]),
349
- node_id: z3.string(),
350
- data: z3.record(z3.unknown()).optional()
393
+ op: z4.enum(["add-node", "remove-node", "update-node"]),
394
+ node_id: z4.string(),
395
+ data: z4.object({
396
+ file: z4.string(),
397
+ scope_glob: z4.string(),
398
+ deps: z4.array(z4.string()).optional(),
399
+ priority: z4.number().optional()
400
+ }).optional()
351
401
  };
352
- function createTextResponse4(payload) {
353
- return {
354
- content: [
355
- {
356
- type: "text",
357
- text: JSON.stringify(payload)
358
- }
359
- ]
360
- };
361
- }
402
+ var outputSchema4 = z4.object({
403
+ success: z4.boolean(),
404
+ revision_hash: z4.string()
405
+ });
362
406
  function registerUpdateRegistry(server) {
363
- server.tool(
407
+ server.registerTool(
364
408
  "fab_update_registry",
365
- "MANDATORY: Call to add, remove, or update Fabric registry nodes instead of editing registry files directly.",
366
- inputSchema4,
409
+ {
410
+ description: "Call to add, remove, or update Fabric registry nodes. Use instead of editing .fabric/agents.meta.json directly.",
411
+ inputSchema: inputSchema4,
412
+ outputSchema: outputSchema4,
413
+ annotations: { destructiveHint: true }
414
+ },
367
415
  async ({ op, node_id, data }) => {
368
416
  const projectRoot = resolveProjectRoot();
369
417
  const result = await updateRegistry(projectRoot, { op, node_id, data });
370
- return createTextResponse4(result);
418
+ return {
419
+ content: [{ type: "text", text: JSON.stringify(result) }],
420
+ structuredContent: result
421
+ };
371
422
  }
372
423
  );
373
424
  }
@@ -386,12 +437,33 @@ function formatError(error) {
386
437
  function createFabricServer() {
387
438
  const server = new McpServer({
388
439
  name: "fabric-context-server",
389
- version: "1.1.0"
440
+ version: "1.3.0"
390
441
  });
391
442
  registerGetRules(server);
392
443
  registerPlanContext(server);
393
444
  registerAppendIntent(server);
394
445
  registerUpdateRegistry(server);
446
+ server.registerResource(
447
+ "AGENTS.md",
448
+ AGENTS_MD_RESOURCE_URI,
449
+ {
450
+ description: "L0 fabric rules file \u2014 global agent instructions for this project",
451
+ mimeType: "text/markdown"
452
+ },
453
+ async (_uri) => {
454
+ const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
455
+ const content = await readFile2(join3(projectRoot, "AGENTS.md"), "utf8");
456
+ return {
457
+ contents: [
458
+ {
459
+ uri: AGENTS_MD_RESOURCE_URI,
460
+ mimeType: "text/markdown",
461
+ text: content
462
+ }
463
+ ]
464
+ };
465
+ }
466
+ );
395
467
  return server;
396
468
  }
397
469
  async function startStdioServer() {
@@ -400,7 +472,7 @@ async function startStdioServer() {
400
472
  await server.connect(transport);
401
473
  }
402
474
  async function startHttpServer(options) {
403
- const { createFabricHttpApp } = await import("./http-FL3MB4L2.js");
475
+ const { createFabricHttpApp } = await import("./http-YPXWM5QS.js");
404
476
  const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
405
477
  const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
406
478
  return await new Promise((resolveServer, rejectServer) => {
@@ -423,6 +495,7 @@ if (isMainModule) {
423
495
  });
424
496
  }
425
497
  export {
498
+ AGENTS_MD_RESOURCE_URI,
426
499
  createFabricServer,
427
500
  runDoctorAuditReport,
428
501
  runDoctorReport,