@fenglimg/fabric-server 1.2.0 → 1.3.1

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-GU7AMRM3.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,44 @@ 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
+ const structuredContent = {
86
+ success: result.success,
87
+ timestamp: result.timestamp,
88
+ entry: { ...result.entry },
89
+ compliance: result.compliance
90
+ };
91
+ return {
92
+ content: [{ type: "text", text: JSON.stringify(result) }],
93
+ structuredContent
94
+ };
75
95
  }
76
96
  );
77
97
  }
78
98
 
79
99
  // src/tools/get-rules.ts
80
- import { z } from "zod";
100
+ import { z as z2 } from "zod";
81
101
 
82
102
  // src/services/get-rules.ts
83
103
  import { readFile } from "fs/promises";
@@ -107,17 +127,23 @@ async function getRules(projectRoot, input) {
107
127
  return result;
108
128
  }
109
129
  async function loadGetRulesContext(projectRoot) {
110
- const meta = readAgentsMeta(projectRoot);
111
- const l0Content = await readFile(join(projectRoot, "AGENTS.md"), "utf8");
130
+ const cached = contextCache.get("context", projectRoot);
131
+ if (cached !== void 0) {
132
+ return cached;
133
+ }
134
+ const meta = await readAgentsMeta(projectRoot);
135
+ const l0Content = await readFile(join(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
112
136
  const humanLockedNearby = (await readHumanLock(projectRoot)).map((entry) => ({
113
137
  file: entry.file,
114
138
  excerpt: JSON.stringify(entry)
115
139
  }));
116
- return {
140
+ const context = {
117
141
  meta,
118
142
  l0Content,
119
143
  humanLockedNearby
120
144
  };
145
+ contextCache.set("context", projectRoot, context);
146
+ return context;
121
147
  }
122
148
  async function resolveRulesForPath(projectRoot, context, path, options = {}) {
123
149
  const loadedRules = await loadRulesForPath(projectRoot, context.meta, path);
@@ -189,34 +215,43 @@ function dedupeEntriesByPath(entries) {
189
215
 
190
216
  // src/tools/get-rules.ts
191
217
  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")
218
+ path: z2.string().describe("Target file path to query rules for"),
219
+ client_hash: z2.string().optional().describe("Revision hash from prior fab_get_rules response; enables stale detection")
194
220
  };
195
- function createTextResponse2(payload) {
196
- return {
197
- content: [
198
- {
199
- type: "text",
200
- text: JSON.stringify(payload)
201
- }
202
- ]
203
- };
204
- }
221
+ var rulesEntrySchema = z2.object({ path: z2.string(), content: z2.string() });
222
+ var humanLockedSchema = z2.object({ file: z2.string(), excerpt: z2.string() });
223
+ var outputSchema2 = z2.object({
224
+ revision_hash: z2.string(),
225
+ stale: z2.boolean(),
226
+ rules: z2.object({
227
+ L0: z2.string(),
228
+ L1: z2.array(rulesEntrySchema),
229
+ L2: z2.array(rulesEntrySchema),
230
+ human_locked_nearby: z2.array(humanLockedSchema)
231
+ })
232
+ });
205
233
  function registerGetRules(server) {
206
- server.tool(
234
+ server.registerTool(
207
235
  "fab_get_rules",
208
- "MANDATORY: Call before modifying any file to retrieve Fabric rules for a target path.",
209
- inputSchema2,
236
+ {
237
+ description: "Call before modifying any file to retrieve Fabric rules for a target path.",
238
+ inputSchema: inputSchema2,
239
+ outputSchema: outputSchema2,
240
+ annotations: { readOnlyHint: true }
241
+ },
210
242
  async ({ path, client_hash }) => {
211
243
  const projectRoot = resolveProjectRoot();
212
244
  const result = await getRules(projectRoot, { path, client_hash });
213
- return createTextResponse2(result);
245
+ return {
246
+ content: [{ type: "text", text: JSON.stringify(result) }],
247
+ structuredContent: result
248
+ };
214
249
  }
215
250
  );
216
251
  }
217
252
 
218
253
  // src/tools/plan-context.ts
219
- import { z as z2 } from "zod";
254
+ import { z as z3 } from "zod";
220
255
 
221
256
  // src/services/plan-context.ts
222
257
  async function planContext(projectRoot, input) {
@@ -249,41 +284,57 @@ function dedupePaths(paths) {
249
284
 
250
285
  // src/tools/plan-context.ts
251
286
  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")
287
+ paths: z3.array(z3.string()).min(2).describe("Candidate file paths to query rules for during planning or architecture review"),
288
+ client_hash: z3.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection")
254
289
  };
255
- function createTextResponse3(payload) {
256
- return {
257
- content: [
258
- {
259
- type: "text",
260
- text: JSON.stringify(payload)
261
- }
262
- ]
263
- };
264
- }
290
+ var rulesEntrySchema2 = z3.object({ path: z3.string(), content: z3.string() });
291
+ var humanLockedSchema2 = z3.object({ file: z3.string(), excerpt: z3.string() });
292
+ var rulesPayloadSchema = z3.object({
293
+ L0: z3.string(),
294
+ L1: z3.array(rulesEntrySchema2),
295
+ L2: z3.array(rulesEntrySchema2),
296
+ human_locked_nearby: z3.array(humanLockedSchema2)
297
+ });
298
+ var outputSchema3 = z3.object({
299
+ revision_hash: z3.string(),
300
+ stale: z3.boolean(),
301
+ entries: z3.array(
302
+ z3.object({
303
+ path: z3.string(),
304
+ rules: rulesPayloadSchema
305
+ })
306
+ )
307
+ });
265
308
  function registerPlanContext(server) {
266
- server.tool(
309
+ server.registerTool(
267
310
  "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,
311
+ {
312
+ 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.",
313
+ inputSchema: inputSchema3,
314
+ outputSchema: outputSchema3,
315
+ annotations: { readOnlyHint: true }
316
+ },
270
317
  async ({ paths, client_hash }) => {
271
318
  const projectRoot = resolveProjectRoot();
272
319
  const result = await planContext(projectRoot, { paths, client_hash });
273
- return createTextResponse3(result);
320
+ return {
321
+ content: [{ type: "text", text: JSON.stringify(result) }],
322
+ structuredContent: result
323
+ };
274
324
  }
275
325
  );
276
326
  }
277
327
 
278
328
  // src/tools/update-registry.ts
279
- import { z as z3 } from "zod";
329
+ import { agentsLayerSchema, agentsTopologyTypeSchema } from "@fenglimg/fabric-shared";
330
+ import { z as z4 } from "zod";
280
331
 
281
332
  // src/services/update-registry.ts
282
333
  import { agentsMetaNodeSchema } from "@fenglimg/fabric-shared";
283
334
  import { join as join2 } from "path";
284
335
  async function updateRegistry(projectRoot, input) {
285
336
  const metaPath = join2(projectRoot, FABRIC_DIR, "agents.meta.json");
286
- const currentMeta = readAgentsMeta(projectRoot);
337
+ const currentMeta = await readAgentsMeta(projectRoot);
287
338
  const nextMeta = applyRegistryOperation(currentMeta, input.op, input.node_id, input.data);
288
339
  const newRevision = computeRevision(nextMeta);
289
340
  await atomicWriteText(
@@ -298,6 +349,7 @@ async function updateRegistry(projectRoot, input) {
298
349
  )}
299
350
  `
300
351
  );
352
+ contextCache.invalidate("meta_write", projectRoot);
301
353
  return {
302
354
  revision_hash: newRevision,
303
355
  success: true
@@ -344,30 +396,40 @@ function applyRegistryOperation(meta, op, nodeId, data) {
344
396
  }
345
397
 
346
398
  // src/tools/update-registry.ts
399
+ var nodeInputSchema = z4.object({
400
+ file: z4.string().optional(),
401
+ scope_glob: z4.string().optional(),
402
+ deps: z4.array(z4.string()).optional(),
403
+ priority: z4.enum(["high", "medium", "low"]).optional(),
404
+ layer: agentsLayerSchema.optional(),
405
+ topology_type: agentsTopologyTypeSchema.optional(),
406
+ hash: z4.string().optional()
407
+ });
347
408
  var inputSchema4 = {
348
- op: z3.enum(["add-node", "remove-node", "update-node"]),
349
- node_id: z3.string(),
350
- data: z3.record(z3.unknown()).optional()
409
+ op: z4.enum(["add-node", "remove-node", "update-node"]),
410
+ node_id: z4.string(),
411
+ data: nodeInputSchema.optional()
351
412
  };
352
- function createTextResponse4(payload) {
353
- return {
354
- content: [
355
- {
356
- type: "text",
357
- text: JSON.stringify(payload)
358
- }
359
- ]
360
- };
361
- }
413
+ var outputSchema4 = z4.object({
414
+ success: z4.boolean(),
415
+ revision_hash: z4.string()
416
+ });
362
417
  function registerUpdateRegistry(server) {
363
- server.tool(
418
+ server.registerTool(
364
419
  "fab_update_registry",
365
- "MANDATORY: Call to add, remove, or update Fabric registry nodes instead of editing registry files directly.",
366
- inputSchema4,
420
+ {
421
+ description: "Call to add, remove, or update Fabric registry nodes. Use instead of editing .fabric/agents.meta.json directly.",
422
+ inputSchema: inputSchema4,
423
+ outputSchema: outputSchema4,
424
+ annotations: { destructiveHint: true }
425
+ },
367
426
  async ({ op, node_id, data }) => {
368
427
  const projectRoot = resolveProjectRoot();
369
428
  const result = await updateRegistry(projectRoot, { op, node_id, data });
370
- return createTextResponse4(result);
429
+ return {
430
+ content: [{ type: "text", text: JSON.stringify(result) }],
431
+ structuredContent: result
432
+ };
371
433
  }
372
434
  );
373
435
  }
@@ -386,12 +448,33 @@ function formatError(error) {
386
448
  function createFabricServer() {
387
449
  const server = new McpServer({
388
450
  name: "fabric-context-server",
389
- version: "1.2.0"
451
+ version: "1.3.1"
390
452
  });
391
453
  registerGetRules(server);
392
454
  registerPlanContext(server);
393
455
  registerAppendIntent(server);
394
456
  registerUpdateRegistry(server);
457
+ server.registerResource(
458
+ "bootstrap README",
459
+ AGENTS_MD_RESOURCE_URI,
460
+ {
461
+ description: "L0 fabric bootstrap file \u2014 global agent instructions for this project",
462
+ mimeType: "text/markdown"
463
+ },
464
+ async (_uri) => {
465
+ const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
466
+ const content = await readFile2(join3(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
467
+ return {
468
+ contents: [
469
+ {
470
+ uri: AGENTS_MD_RESOURCE_URI,
471
+ mimeType: "text/markdown",
472
+ text: content
473
+ }
474
+ ]
475
+ };
476
+ }
477
+ );
395
478
  return server;
396
479
  }
397
480
  async function startStdioServer() {
@@ -400,11 +483,14 @@ async function startStdioServer() {
400
483
  await server.connect(transport);
401
484
  }
402
485
  async function startHttpServer(options) {
403
- const { createFabricHttpApp } = await import("./http-EQBDM4C7.js");
486
+ const { createFabricHttpApp } = await import("./http-6V75VA23.js");
404
487
  const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
405
488
  const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
406
489
  return await new Promise((resolveServer, rejectServer) => {
407
490
  const server = app.listen(port, host);
491
+ server.once("close", () => {
492
+ void app.dispose();
493
+ });
408
494
  server.once("listening", () => {
409
495
  resolveServer(server);
410
496
  });
@@ -423,6 +509,7 @@ if (isMainModule) {
423
509
  });
424
510
  }
425
511
  export {
512
+ AGENTS_MD_RESOURCE_URI,
426
513
  createFabricServer,
427
514
  runDoctorAuditReport,
428
515
  runDoctorReport,