@fenglimg/fabric-server 1.0.0 → 1.2.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,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-KZO24GUQ.js";
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 matchedNodes = Object.entries(meta.nodes).filter(([, node]) => minimatch(requestedPath, normalizePath(node.scope_glob), { dot: true })).sort((left, right) => {
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
- const loadedRules = await Promise.all(
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
- revision_hash: meta.revision,
115
- stale,
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 normalizePath(value) {
125
- return value.replaceAll("\\", "/");
126
- }
127
- function classifyNode(nodeId) {
128
- if (nodeId.startsWith("L1/")) {
129
- return "L1";
130
- }
131
- if (nodeId.startsWith("L2/")) {
132
- return "L2";
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/update-registry.ts
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 inputSchema3 = {
235
- op: z2.enum(["add-node", "remove-node", "update-node"]),
236
- node_id: z2.string(),
237
- data: z2.record(z2.unknown()).optional()
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 createTextResponse3(payload) {
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
- inputSchema3,
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 createTextResponse3(result);
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.0.0"
389
+ version: "1.2.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-3BNWQWPP.js");
403
+ const { createFabricHttpApp } = await import("./http-EQBDM4C7.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
  };