@fenglimg/fabric-server 1.6.0 → 1.7.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,382 +1,102 @@
1
1
  import {
2
2
  AGENTS_MD_RESOURCE_URI,
3
- FABRIC_DIR,
3
+ EVENT_LEDGER_PATH,
4
4
  LEDGER_PATH,
5
5
  LEGACY_LEDGER_PATH,
6
- appendEditIntentAuditEvents,
7
- appendLedgerEntry,
8
- appendRuleSelectionAuditEvent,
9
- approveHumanLock,
10
- atomicWriteText,
11
- contextCache,
6
+ RULE_SECTION_NAMES,
7
+ buildRuleMeta,
8
+ computeRuleTestIndex,
9
+ computeRulesBasedAgentsMeta,
10
+ deriveRuleMetaLayer,
11
+ deriveRuleMetaTopologyType,
12
+ getEventLedgerPath,
12
13
  getLedgerPath,
13
14
  getLegacyLedgerPath,
14
- normalizeRulesPath,
15
- readAgentsMeta,
16
- readHumanLock,
17
- readHumanLockEntry,
15
+ getRuleSections,
16
+ isSameRuleTestIndex,
17
+ planContext,
18
18
  resolveProjectRoot,
19
- runDoctorAuditReport,
20
19
  runDoctorFix,
21
20
  runDoctorReport,
22
- sha256
23
- } from "./chunk-TZCE2K4D.js";
21
+ stableStringify,
22
+ writeRuleMeta
23
+ } from "./chunk-PTFSYO4Y.js";
24
24
 
25
25
  // src/index.ts
26
- import { readFile as readFile2 } from "fs/promises";
27
- import { join as join3, resolve } from "path";
26
+ import { readFile } from "fs/promises";
27
+ import { join, resolve } from "path";
28
28
  import { fileURLToPath } from "url";
29
29
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
30
30
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
31
31
 
32
- // src/tools/append-intent.ts
33
- import { aiLedgerEntrySchema } from "@fenglimg/fabric-shared";
32
+ // src/tools/plan-context.ts
34
33
  import { z } from "zod";
35
-
36
- // src/services/append-intent.ts
37
- async function appendIntent(projectRoot, input) {
38
- const ts = Date.now();
39
- const entry = await appendLedgerEntry(projectRoot, {
40
- ...input.entry,
41
- ts,
42
- source: "ai"
43
- });
44
- let compliance;
45
- try {
46
- const auditResult = await appendEditIntentAuditEvents(projectRoot, {
47
- affected_paths: entry.affected_paths,
48
- intent: entry.intent,
49
- ledger_entry_id: entry.id,
50
- ts
51
- });
52
- compliance = auditResult.compliance;
53
- } catch {
54
- }
55
- return {
56
- success: true,
57
- timestamp: ts,
58
- entry,
59
- compliance
60
- };
61
- }
62
-
63
- // src/tools/append-intent.ts
64
34
  var inputSchema = {
65
- entry: aiLedgerEntrySchema.omit({
66
- id: true,
67
- source: true,
68
- ts: true
69
- })
35
+ paths: z.array(z.string()).min(1).describe("Candidate file paths to build neutral rule selection context for"),
36
+ intent: z.string().optional().describe("User-stated requirement or implementation intent; used only to build a neutral requirement profile"),
37
+ known_tech: z.array(z.string()).optional().describe("Known technologies involved in the requirement profile"),
38
+ detected_entities: z.record(z.array(z.string())).optional().describe("Optional path-keyed detected entities for the requirement profile"),
39
+ client_hash: z.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection"),
40
+ correlation_id: z.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
41
+ session_id: z.string().optional().describe("Optional caller-provided session id for Event Ledger records")
70
42
  };
71
- var outputSchema = z.object({
72
- success: z.boolean(),
73
- timestamp: z.number(),
74
- entry: z.record(z.unknown()),
75
- compliance: z.object({
76
- compliant: z.boolean(),
77
- matched_get_rules_ts: z.string().nullable(),
78
- window_ms: z.number()
79
- }).optional()
43
+ var ruleDescriptionSchema = z.object({
44
+ summary: z.string(),
45
+ intent_clues: z.array(z.string()),
46
+ tech_stack: z.array(z.string()),
47
+ impact: z.array(z.string()),
48
+ must_read_if: z.string(),
49
+ entities: z.array(z.string()).optional()
80
50
  });
81
- function registerAppendIntent(server) {
82
- server.registerTool(
83
- "fab_append_intent",
84
- {
85
- description: "Call after a completed task to append an intent ledger entry for Fabric.",
86
- inputSchema,
87
- outputSchema,
88
- annotations: { readOnlyHint: false }
89
- },
90
- async ({ entry }) => {
91
- const projectRoot = resolveProjectRoot();
92
- const result = await appendIntent(projectRoot, { entry });
93
- const structuredContent = {
94
- success: result.success,
95
- timestamp: result.timestamp,
96
- entry: { ...result.entry },
97
- compliance: result.compliance
98
- };
99
- return {
100
- content: [{ type: "text", text: JSON.stringify(result) }],
101
- structuredContent
102
- };
103
- }
104
- );
105
- }
106
-
107
- // src/tools/plan-context.ts
108
- import { z as z2 } from "zod";
109
-
110
- // src/services/plan-context.ts
111
- var SELECTION_TOKEN_TTL_MS = 5 * 60 * 1e3;
112
- var selectionTokenCache = /* @__PURE__ */ new Map();
113
- async function planContext(projectRoot, input) {
114
- const meta = await readAgentsMeta(projectRoot);
115
- const stale = input.client_hash !== void 0 && input.client_hash !== meta.revision;
116
- const uniquePaths = dedupePaths(input.paths);
117
- const allDescriptions = buildDescriptionIndex(meta);
118
- const entries = uniquePaths.map((path) => {
119
- const profile = buildRequirementProfile(path, input);
120
- const descriptionIndex = allDescriptions.filter((item) => shouldIncludeIndexItemForPath(item, meta, path));
121
- const requiredStableIds2 = descriptionIndex.filter((item) => item.required).map((item) => item.stable_id);
122
- const aiSelectableStableIds2 = descriptionIndex.filter((item) => item.selectable).map((item) => item.stable_id);
123
- return {
124
- path,
125
- requirement_profile: profile,
126
- description_index: descriptionIndex,
127
- required_stable_ids: requiredStableIds2,
128
- ai_selectable_stable_ids: aiSelectableStableIds2,
129
- initial_selected_stable_ids: requiredStableIds2,
130
- selection_policy: {
131
- required_levels: ["L0", "L2"],
132
- ai_selectable_levels: ["L1"],
133
- final_fetch_rule: "required_stable_ids + ai_selected_l1_stable_ids"
134
- }
135
- };
136
- });
137
- const requiredStableIds = dedupeStableIds(entries.flatMap((entry) => entry.required_stable_ids));
138
- const aiSelectableStableIds = dedupeStableIds(entries.flatMap((entry) => entry.ai_selectable_stable_ids));
139
- const sharedDescriptionIndex = dedupeDescriptionIndex(entries.flatMap((entry) => entry.description_index));
140
- const selectionToken = createSelectionToken(meta.revision, uniquePaths, requiredStableIds, aiSelectableStableIds);
141
- return {
142
- revision_hash: meta.revision,
143
- stale,
144
- selection_token: selectionToken,
145
- entries,
146
- shared: {
147
- required_stable_ids: requiredStableIds,
148
- ai_selectable_stable_ids: aiSelectableStableIds,
149
- description_index: sharedDescriptionIndex,
150
- preflight_diagnostics: buildPreflightDiagnostics(meta)
151
- }
152
- };
153
- }
154
- function readSelectionToken(token, now = Date.now()) {
155
- const state = selectionTokenCache.get(token);
156
- if (state === void 0) {
157
- return void 0;
158
- }
159
- if (state.expires_at <= now) {
160
- selectionTokenCache.delete(token);
161
- return void 0;
162
- }
163
- return state;
164
- }
165
- function createSelectionToken(revisionHash, targetPaths, requiredStableIds, aiSelectableStableIds, now = Date.now()) {
166
- const token = `selection:${revisionHash}:${now.toString(36)}:${Math.random().toString(36).slice(2)}`;
167
- selectionTokenCache.set(token, {
168
- token,
169
- revision_hash: revisionHash,
170
- target_paths: targetPaths,
171
- required_stable_ids: requiredStableIds,
172
- ai_selectable_stable_ids: aiSelectableStableIds,
173
- created_at: now,
174
- expires_at: now + SELECTION_TOKEN_TTL_MS
175
- });
176
- return token;
177
- }
178
- function dedupePaths(paths) {
179
- const seenPaths = /* @__PURE__ */ new Set();
180
- return paths.flatMap((path) => {
181
- const normalizedPath = normalizeRulesPath(path);
182
- if (seenPaths.has(normalizedPath)) {
183
- return [];
184
- }
185
- seenPaths.add(normalizedPath);
186
- return [normalizedPath];
187
- });
188
- }
189
- function buildRequirementProfile(path, input) {
190
- const normalizedPath = normalizeRulesPath(path);
191
- const extensionMatch = /(\.[^./\\]+)$/u.exec(normalizedPath);
192
- const knownTech = dedupeStableIds([
193
- ...input.known_tech ?? [],
194
- ...extensionMatch?.[1] === ".ts" ? ["TypeScript"] : []
195
- ]);
196
- return {
197
- target_path: normalizedPath,
198
- path_segments: normalizedPath.split("/").filter(Boolean),
199
- extension: extensionMatch?.[1] ?? "",
200
- inferred_domain: inferDomains(normalizedPath),
201
- known_tech: knownTech,
202
- user_intent: input.intent ?? "",
203
- intent_tokens: tokenizeIntent(input.intent ?? ""),
204
- impact_hints: inferImpactHints(input.intent ?? ""),
205
- detected_entities: input.detected_entities?.[normalizedPath] ?? input.detected_entities?.[path] ?? []
206
- };
207
- }
208
- function buildDescriptionIndex(meta) {
209
- return Object.entries(meta.nodes).flatMap(([nodeId, node]) => {
210
- const level = node.level ?? node.layer;
211
- const description = node.description ?? descriptionFromLegacyActivation(node.activation?.description);
212
- if (description === void 0) {
213
- return [];
214
- }
215
- return [{
216
- stable_id: node.stable_id ?? nodeId,
217
- level,
218
- required: level === "L0" || level === "L2",
219
- selectable: level === "L1",
220
- description
221
- }];
222
- }).sort(compareDescriptionIndexItems);
223
- }
224
- function descriptionFromLegacyActivation(summary) {
225
- if (summary === void 0) {
226
- return void 0;
227
- }
228
- return {
229
- summary,
230
- intent_clues: [],
231
- tech_stack: [],
232
- impact: [],
233
- must_read_if: summary
234
- };
235
- }
236
- function shouldIncludeIndexItemForPath(item, meta, path) {
237
- if (item.level === "L0" || item.level === "L1") {
238
- return true;
239
- }
240
- const node = Object.values(meta.nodes).find((candidate) => candidate.stable_id === item.stable_id);
241
- if (node === void 0) {
242
- return false;
243
- }
244
- return node.scope_glob === path || minimatchSimple(path, node.scope_glob);
245
- }
246
- function minimatchSimple(path, glob) {
247
- if (glob === "**") {
248
- return true;
249
- }
250
- if (glob.endsWith("/**")) {
251
- return path.startsWith(glob.slice(0, -3));
252
- }
253
- return path === glob;
254
- }
255
- function buildPreflightDiagnostics(meta) {
256
- const missingDescriptionStableIds = Object.entries(meta.nodes).filter(([, node]) => node.description === void 0 && node.activation?.description === void 0).map(([nodeId, node]) => node.stable_id ?? nodeId).sort();
257
- if (missingDescriptionStableIds.length === 0) {
258
- return [];
259
- }
260
- return [{
261
- code: "missing_description",
262
- severity: "warn",
263
- stable_ids: missingDescriptionStableIds,
264
- message: `Resolved registry includes ${missingDescriptionStableIds.length} node(s) without structured descriptions.`
265
- }];
266
- }
267
- function inferDomains(path) {
268
- const domains = [];
269
- if (path.includes("/ui/") || path.toLowerCase().includes("ui")) {
270
- domains.push("UI");
271
- }
272
- if (path.includes("assets/scripts")) {
273
- domains.push("Gameplay");
274
- }
275
- if (path.includes("resources") || path.includes("assets/resources")) {
276
- domains.push("Asset");
277
- }
278
- return domains;
279
- }
280
- function tokenizeIntent(intent) {
281
- const tokens = ["\u6027\u80FD", "\u4F18\u5316", "drawcall", "\u6E32\u67D3", "\u5361\u987F", "\u95EA\u70C1", "\u754C\u9762", "UI", "\u8D44\u6E90", "\u56FE\u96C6"].filter((token) => intent.toLowerCase().includes(token.toLowerCase()));
282
- return dedupeStableIds(tokens);
283
- }
284
- function inferImpactHints(intent) {
285
- return /性能|优化|drawcall|渲染|卡顿|闪烁/iu.test(intent) ? ["Performance"] : [];
286
- }
287
- function dedupeStableIds(stableIds) {
288
- return Array.from(new Set(stableIds));
289
- }
290
- function dedupeDescriptionIndex(items) {
291
- const seenStableIds = /* @__PURE__ */ new Set();
292
- return items.filter((item) => {
293
- if (seenStableIds.has(item.stable_id)) {
294
- return false;
295
- }
296
- seenStableIds.add(item.stable_id);
297
- return true;
298
- });
299
- }
300
- function compareDescriptionIndexItems(left, right) {
301
- const levelDelta = levelOrder(left.level) - levelOrder(right.level);
302
- return levelDelta !== 0 ? levelDelta : left.stable_id.localeCompare(right.stable_id);
303
- }
304
- function levelOrder(level) {
305
- switch (level) {
306
- case "L0":
307
- return 0;
308
- case "L1":
309
- return 1;
310
- case "L2":
311
- return 2;
312
- }
313
- }
314
-
315
- // src/tools/plan-context.ts
316
- var inputSchema2 = {
317
- paths: z2.array(z2.string()).min(1).describe("Candidate file paths to build neutral rule selection context for"),
318
- intent: z2.string().optional().describe("User-stated requirement or implementation intent; used only to build a neutral requirement profile"),
319
- known_tech: z2.array(z2.string()).optional().describe("Known technologies involved in the requirement profile"),
320
- detected_entities: z2.record(z2.array(z2.string())).optional().describe("Optional path-keyed detected entities for the requirement profile"),
321
- client_hash: z2.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection")
322
- };
323
- var ruleDescriptionSchema = z2.object({
324
- summary: z2.string(),
325
- intent_clues: z2.array(z2.string()),
326
- tech_stack: z2.array(z2.string()),
327
- impact: z2.array(z2.string()),
328
- must_read_if: z2.string(),
329
- entities: z2.array(z2.string()).optional()
330
- });
331
- var descriptionIndexItemSchema = z2.object({
332
- stable_id: z2.string(),
333
- level: z2.enum(["L0", "L1", "L2"]),
334
- required: z2.boolean(),
335
- selectable: z2.boolean(),
51
+ var descriptionIndexItemSchema = z.object({
52
+ stable_id: z.string(),
53
+ level: z.enum(["L0", "L1", "L2"]),
54
+ required: z.boolean(),
55
+ selectable: z.boolean(),
336
56
  description: ruleDescriptionSchema
337
57
  });
338
- var requirementProfileSchema = z2.object({
339
- target_path: z2.string(),
340
- path_segments: z2.array(z2.string()),
341
- extension: z2.string(),
342
- inferred_domain: z2.array(z2.string()),
343
- known_tech: z2.array(z2.string()),
344
- user_intent: z2.string(),
345
- intent_tokens: z2.array(z2.string()),
346
- impact_hints: z2.array(z2.string()),
347
- detected_entities: z2.array(z2.string())
58
+ var requirementProfileSchema = z.object({
59
+ target_path: z.string(),
60
+ path_segments: z.array(z.string()),
61
+ extension: z.string(),
62
+ inferred_domain: z.array(z.string()),
63
+ known_tech: z.array(z.string()),
64
+ user_intent: z.string(),
65
+ intent_tokens: z.array(z.string()),
66
+ impact_hints: z.array(z.string()),
67
+ detected_entities: z.array(z.string())
348
68
  });
349
- var selectionPolicySchema = z2.object({
350
- required_levels: z2.tuple([z2.literal("L0"), z2.literal("L2")]),
351
- ai_selectable_levels: z2.tuple([z2.literal("L1")]),
352
- final_fetch_rule: z2.literal("required_stable_ids + ai_selected_l1_stable_ids")
69
+ var selectionPolicySchema = z.object({
70
+ required_levels: z.tuple([z.literal("L0"), z.literal("L2")]),
71
+ ai_selectable_levels: z.tuple([z.literal("L1")]),
72
+ final_fetch_rule: z.literal("required_stable_ids + ai_selected_l1_stable_ids")
353
73
  });
354
- var outputSchema2 = z2.object({
355
- revision_hash: z2.string(),
356
- stale: z2.boolean(),
357
- selection_token: z2.string(),
358
- entries: z2.array(
359
- z2.object({
360
- path: z2.string(),
74
+ var outputSchema = z.object({
75
+ revision_hash: z.string(),
76
+ stale: z.boolean(),
77
+ selection_token: z.string(),
78
+ entries: z.array(
79
+ z.object({
80
+ path: z.string(),
361
81
  requirement_profile: requirementProfileSchema,
362
- description_index: z2.array(descriptionIndexItemSchema),
363
- required_stable_ids: z2.array(z2.string()),
364
- ai_selectable_stable_ids: z2.array(z2.string()),
365
- initial_selected_stable_ids: z2.array(z2.string()),
82
+ description_index: z.array(descriptionIndexItemSchema),
83
+ required_stable_ids: z.array(z.string()),
84
+ ai_selectable_stable_ids: z.array(z.string()),
85
+ initial_selected_stable_ids: z.array(z.string()),
366
86
  selection_policy: selectionPolicySchema
367
87
  })
368
88
  ),
369
- shared: z2.object({
370
- required_stable_ids: z2.array(z2.string()),
371
- ai_selectable_stable_ids: z2.array(z2.string()),
372
- description_index: z2.array(descriptionIndexItemSchema),
373
- preflight_diagnostics: z2.array(
374
- z2.object({
375
- code: z2.literal("missing_description"),
376
- severity: z2.literal("warn"),
377
- message: z2.string(),
378
- stable_ids: z2.array(z2.string()).optional(),
379
- path: z2.string().optional()
89
+ shared: z.object({
90
+ required_stable_ids: z.array(z.string()),
91
+ ai_selectable_stable_ids: z.array(z.string()),
92
+ description_index: z.array(descriptionIndexItemSchema),
93
+ preflight_diagnostics: z.array(
94
+ z.object({
95
+ code: z.literal("missing_description"),
96
+ severity: z.literal("warn"),
97
+ message: z.string(),
98
+ stable_ids: z.array(z.string()).optional(),
99
+ path: z.string().optional()
380
100
  })
381
101
  )
382
102
  })
@@ -386,13 +106,21 @@ function registerPlanContext(server) {
386
106
  "fab_plan_context",
387
107
  {
388
108
  description: "Use during plan or architecture phases to build a neutral Fabric rule description index and selection token before fetching rule sections.",
389
- inputSchema: inputSchema2,
390
- outputSchema: outputSchema2,
109
+ inputSchema,
110
+ outputSchema,
391
111
  annotations: { readOnlyHint: true }
392
112
  },
393
- async ({ paths, intent, known_tech, detected_entities, client_hash }) => {
113
+ async ({ paths, intent, known_tech, detected_entities, client_hash, correlation_id, session_id }) => {
394
114
  const projectRoot = resolveProjectRoot();
395
- const result = await planContext(projectRoot, { paths, intent, known_tech, detected_entities, client_hash });
115
+ const result = await planContext(projectRoot, {
116
+ paths,
117
+ intent,
118
+ known_tech,
119
+ detected_entities,
120
+ client_hash,
121
+ correlation_id,
122
+ session_id
123
+ });
396
124
  return {
397
125
  content: [{ type: "text", text: JSON.stringify(result) }],
398
126
  structuredContent: result
@@ -402,198 +130,34 @@ function registerPlanContext(server) {
402
130
  }
403
131
 
404
132
  // src/tools/rule-sections.ts
405
- import { z as z3 } from "zod";
406
-
407
- // src/services/rule-sections.ts
408
- import { readFile } from "fs/promises";
409
- import { join } from "path";
410
- var RULE_SECTION_NAMES = ["MANDATORY_INJECTION", "CONTEXT_INFO"];
411
- var PRIORITY_ORDER = {
412
- high: 0,
413
- medium: 1,
414
- low: 2
415
- };
416
- function parseRuleSections(content) {
417
- const sections = /* @__PURE__ */ new Map();
418
- const lines = content.split(/\r?\n/u);
419
- let activeSection;
420
- let buffer = [];
421
- const flush = () => {
422
- if (activeSection === void 0) {
423
- return;
424
- }
425
- const text = buffer.join("\n").trim();
426
- if (text.length === 0) {
427
- buffer = [];
428
- return;
429
- }
430
- sections.set(activeSection, [...sections.get(activeSection) ?? [], text]);
431
- buffer = [];
432
- };
433
- for (const line of lines) {
434
- const heading = /^(#{2,6})\s+\[([A-Z_]+)\]\s*$/u.exec(line.trim());
435
- if (heading !== null) {
436
- flush();
437
- activeSection = isRuleSectionName(heading[2]) ? heading[2] : void 0;
438
- continue;
439
- }
440
- if (/^#{1,6}\s+/u.test(line)) {
441
- flush();
442
- activeSection = void 0;
443
- continue;
444
- }
445
- if (activeSection !== void 0) {
446
- buffer.push(line);
447
- }
448
- }
449
- flush();
450
- return new Map(
451
- Array.from(sections.entries()).map(([section, values]) => [section, values.join("\n\n")])
452
- );
453
- }
454
- async function getRuleSections(projectRoot, input) {
455
- const token = readSelectionToken(input.selection_token);
456
- if (token === void 0) {
457
- throw new Error("selection_token is missing or expired");
458
- }
459
- validateAiSelections(token.ai_selectable_stable_ids, input.ai_selected_stable_ids, input.ai_selection_reasons);
460
- const meta = await readAgentsMeta(projectRoot);
461
- const selectedStableIds = [...token.required_stable_ids, ...input.ai_selected_stable_ids];
462
- const selectedRules = sortRuleNodes(selectedStableIds.map((stableId) => findRuleNode(meta, stableId)));
463
- const diagnostics = [];
464
- const rules = [];
465
- for (const rule of selectedRules) {
466
- const content = await readFile(join(projectRoot, rule.path), "utf8");
467
- const parsedSections = parseRuleSections(content);
468
- const sections = {};
469
- for (const section of input.sections) {
470
- const sectionContent = parsedSections.get(section);
471
- sections[section] = sectionContent ?? "";
472
- if (sectionContent === void 0) {
473
- diagnostics.push({
474
- code: "missing_section",
475
- severity: "warn",
476
- stable_id: rule.stable_id,
477
- section,
478
- message: `Rule ${rule.stable_id} does not define section ${section}.`
479
- });
480
- }
481
- }
482
- rules.push({
483
- stable_id: rule.stable_id,
484
- level: rule.level,
485
- path: rule.path,
486
- sections
487
- });
488
- }
489
- const result = {
490
- revision_hash: meta.revision,
491
- precedence: ["L2", "L1", "L0"],
492
- selected_stable_ids: rules.map((rule) => rule.stable_id),
493
- rules,
494
- diagnostics
495
- };
496
- await appendRuleSelectionAuditEvent(projectRoot, {
497
- path: token.target_paths[0] ?? "",
498
- selection_token: input.selection_token,
499
- target_paths: token.target_paths,
500
- required_stable_ids: token.required_stable_ids,
501
- ai_selectable_stable_ids: token.ai_selectable_stable_ids,
502
- ai_selected_stable_ids: input.ai_selected_stable_ids,
503
- final_stable_ids: result.selected_stable_ids,
504
- ai_selection_reasons: pickSelectionReasons(input.ai_selected_stable_ids, input.ai_selection_reasons),
505
- rejected_stable_ids: [],
506
- ignored_stable_ids: []
507
- });
508
- return result;
509
- }
510
- function validateAiSelections(aiSelectableStableIds, aiSelectedStableIds, aiSelectionReasons) {
511
- const selectable = new Set(aiSelectableStableIds);
512
- for (const stableId of aiSelectedStableIds) {
513
- if (!selectable.has(stableId)) {
514
- throw new Error(`Invalid L1 rule selection: ${stableId}`);
515
- }
516
- if (aiSelectionReasons[stableId]?.trim() === "") {
517
- throw new Error(`Missing AI selection reason for ${stableId}`);
518
- }
519
- if (aiSelectionReasons[stableId] === void 0) {
520
- throw new Error(`Missing AI selection reason for ${stableId}`);
521
- }
522
- }
523
- }
524
- function findRuleNode(meta, stableId) {
525
- for (const [nodeId, node] of Object.entries(meta.nodes)) {
526
- const nodeStableId = node.stable_id ?? nodeId;
527
- if (nodeStableId !== stableId) {
528
- continue;
529
- }
530
- const level = node.level ?? node.layer;
531
- return {
532
- stable_id: nodeStableId,
533
- level,
534
- path: normalizeRulesPath(node.content_ref ?? node.file),
535
- priority: node.priority,
536
- node
537
- };
538
- }
539
- throw new Error(`Selected rule is not present in agents.meta.json: ${stableId}`);
540
- }
541
- function sortRuleNodes(rules) {
542
- return [...rules].sort((left, right) => {
543
- const levelDelta = outputLevelOrder(left.level) - outputLevelOrder(right.level);
544
- if (levelDelta !== 0) {
545
- return levelDelta;
546
- }
547
- const priorityDelta = PRIORITY_ORDER[left.priority] - PRIORITY_ORDER[right.priority];
548
- if (priorityDelta !== 0) {
549
- return priorityDelta;
550
- }
551
- return left.stable_id.localeCompare(right.stable_id);
552
- });
553
- }
554
- function outputLevelOrder(level) {
555
- switch (level) {
556
- case "L0":
557
- return 0;
558
- case "L1":
559
- return 1;
560
- case "L2":
561
- return 2;
562
- }
563
- }
564
- function isRuleSectionName(value) {
565
- return RULE_SECTION_NAMES.includes(value);
566
- }
567
- function pickSelectionReasons(selectedStableIds, reasons) {
568
- return Object.fromEntries(selectedStableIds.map((stableId) => [stableId, reasons[stableId] ?? ""]));
569
- }
570
-
571
- // src/tools/rule-sections.ts
572
- var inputSchema3 = {
573
- selection_token: z3.string().min(1).describe("Selection token returned by fab_plan_context"),
574
- sections: z3.array(z3.enum(RULE_SECTION_NAMES)).min(1).describe("Structured rule sections to fetch"),
575
- ai_selected_stable_ids: z3.array(z3.string()).describe("AI-selected L1 stable_ids chosen from fab_plan_context ai_selectable_stable_ids"),
576
- ai_selection_reasons: z3.record(z3.string().min(1)).describe("Reason for each AI-selected L1 stable_id")
133
+ import { z as z2 } from "zod";
134
+ var inputSchema2 = {
135
+ selection_token: z2.string().min(1).describe("Selection token returned by fab_plan_context"),
136
+ sections: z2.array(z2.enum(RULE_SECTION_NAMES)).min(1).describe("Structured rule sections to fetch"),
137
+ ai_selected_stable_ids: z2.array(z2.string()).describe("AI-selected L1 stable_ids chosen from fab_plan_context ai_selectable_stable_ids"),
138
+ ai_selection_reasons: z2.record(z2.string().min(1)).describe("Reason for each AI-selected L1 stable_id"),
139
+ correlation_id: z2.string().optional().describe("Optional caller-provided correlation id for Event Ledger records"),
140
+ session_id: z2.string().optional().describe("Optional caller-provided session id for Event Ledger records")
577
141
  };
578
- var outputSchema3 = z3.object({
579
- revision_hash: z3.string(),
580
- precedence: z3.tuple([z3.literal("L2"), z3.literal("L1"), z3.literal("L0")]),
581
- selected_stable_ids: z3.array(z3.string()),
582
- rules: z3.array(
583
- z3.object({
584
- stable_id: z3.string(),
585
- level: z3.enum(["L0", "L1", "L2"]),
586
- path: z3.string(),
587
- sections: z3.record(z3.string())
142
+ var outputSchema2 = z2.object({
143
+ revision_hash: z2.string(),
144
+ precedence: z2.tuple([z2.literal("L2"), z2.literal("L1"), z2.literal("L0")]),
145
+ selected_stable_ids: z2.array(z2.string()),
146
+ rules: z2.array(
147
+ z2.object({
148
+ stable_id: z2.string(),
149
+ level: z2.enum(["L0", "L1", "L2"]),
150
+ path: z2.string(),
151
+ sections: z2.record(z2.string())
588
152
  })
589
153
  ),
590
- diagnostics: z3.array(
591
- z3.object({
592
- code: z3.literal("missing_section"),
593
- severity: z3.literal("warn"),
594
- stable_id: z3.string(),
595
- section: z3.enum(RULE_SECTION_NAMES),
596
- message: z3.string()
154
+ diagnostics: z2.array(
155
+ z2.object({
156
+ code: z2.literal("missing_section"),
157
+ severity: z2.literal("warn"),
158
+ stable_id: z2.string(),
159
+ section: z2.enum(RULE_SECTION_NAMES),
160
+ message: z2.string()
597
161
  })
598
162
  )
599
163
  });
@@ -602,8 +166,8 @@ function registerRuleSections(server) {
602
166
  "fab_get_rule_sections",
603
167
  {
604
168
  description: "Fetch structured Fabric rule sections after fab_plan_context. Required L0/L2 rules are merged with AI-selected L1 rules server-side.",
605
- inputSchema: inputSchema3,
606
- outputSchema: outputSchema3,
169
+ inputSchema: inputSchema2,
170
+ outputSchema: outputSchema2,
607
171
  annotations: { readOnlyHint: true }
608
172
  },
609
173
  async (input) => {
@@ -617,115 +181,6 @@ function registerRuleSections(server) {
617
181
  );
618
182
  }
619
183
 
620
- // src/tools/update-registry.ts
621
- import { agentsLayerSchema, agentsTopologyTypeSchema } from "@fenglimg/fabric-shared";
622
- import { z as z4 } from "zod";
623
-
624
- // src/services/update-registry.ts
625
- import { agentsMetaNodeSchema } from "@fenglimg/fabric-shared";
626
- import { join as join2 } from "path";
627
- async function updateRegistry(projectRoot, input) {
628
- const metaPath = join2(projectRoot, FABRIC_DIR, "agents.meta.json");
629
- const currentMeta = await readAgentsMeta(projectRoot);
630
- const nextMeta = applyRegistryOperation(currentMeta, input.op, input.node_id, input.data);
631
- const newRevision = computeRevision(nextMeta);
632
- await atomicWriteText(
633
- metaPath,
634
- `${JSON.stringify(
635
- {
636
- ...nextMeta,
637
- revision: newRevision
638
- },
639
- null,
640
- 2
641
- )}
642
- `
643
- );
644
- contextCache.invalidate("meta_write", projectRoot);
645
- return {
646
- revision_hash: newRevision,
647
- success: true
648
- };
649
- }
650
- function computeRevision(meta) {
651
- const joinedHashes = Object.entries(meta.nodes).sort(([leftId], [rightId]) => leftId.localeCompare(rightId)).map(([, node]) => node.hash).join("");
652
- return sha256(joinedHashes);
653
- }
654
- function assertNodeData(data, message) {
655
- if (data === void 0) {
656
- throw new Error(message);
657
- }
658
- return agentsMetaNodeSchema.parse(data);
659
- }
660
- function applyRegistryOperation(meta, op, nodeId, data) {
661
- const nextNodes = { ...meta.nodes };
662
- if (op === "remove-node") {
663
- delete nextNodes[nodeId];
664
- return {
665
- ...meta,
666
- nodes: nextNodes
667
- };
668
- }
669
- if (op === "add-node") {
670
- nextNodes[nodeId] = assertNodeData(data, `fab_update_registry requires data for ${op}`);
671
- return {
672
- ...meta,
673
- nodes: nextNodes
674
- };
675
- }
676
- const currentNode = nextNodes[nodeId];
677
- if (currentNode === void 0) {
678
- throw new Error(`Cannot update missing Fabric registry node: ${nodeId}`);
679
- }
680
- nextNodes[nodeId] = agentsMetaNodeSchema.parse({
681
- ...currentNode,
682
- ...data
683
- });
684
- return {
685
- ...meta,
686
- nodes: nextNodes
687
- };
688
- }
689
-
690
- // src/tools/update-registry.ts
691
- var nodeInputSchema = z4.object({
692
- file: z4.string().optional(),
693
- scope_glob: z4.string().optional(),
694
- deps: z4.array(z4.string()).optional(),
695
- priority: z4.enum(["high", "medium", "low"]).optional(),
696
- layer: agentsLayerSchema.optional(),
697
- topology_type: agentsTopologyTypeSchema.optional(),
698
- hash: z4.string().optional()
699
- });
700
- var inputSchema4 = {
701
- op: z4.enum(["add-node", "remove-node", "update-node"]),
702
- node_id: z4.string(),
703
- data: nodeInputSchema.optional()
704
- };
705
- var outputSchema4 = z4.object({
706
- success: z4.boolean(),
707
- revision_hash: z4.string()
708
- });
709
- function registerUpdateRegistry(server) {
710
- server.registerTool(
711
- "fab_update_registry",
712
- {
713
- description: "Call to add, remove, or update Fabric registry nodes. Use instead of editing .fabric/agents.meta.json directly.",
714
- inputSchema: inputSchema4,
715
- outputSchema: outputSchema4,
716
- annotations: { destructiveHint: true }
717
- },
718
- async ({ op, node_id, data }) => {
719
- const projectRoot = resolveProjectRoot();
720
- const result = await updateRegistry(projectRoot, { op, node_id, data });
721
- return {
722
- content: [{ type: "text", text: JSON.stringify(result) }],
723
- structuredContent: result
724
- };
725
- }
726
- );
727
- }
728
-
729
184
  // src/index.ts
730
185
  function writeStderr(message) {
731
186
  process.stderr.write(`${message}
@@ -740,12 +195,10 @@ function formatError(error) {
740
195
  function createFabricServer() {
741
196
  const server = new McpServer({
742
197
  name: "fabric-context-server",
743
- version: "1.6.0"
198
+ version: "1.7.0"
744
199
  });
745
200
  registerPlanContext(server);
746
201
  registerRuleSections(server);
747
- registerAppendIntent(server);
748
- registerUpdateRegistry(server);
749
202
  server.registerResource(
750
203
  "bootstrap README",
751
204
  AGENTS_MD_RESOURCE_URI,
@@ -755,7 +208,7 @@ function createFabricServer() {
755
208
  },
756
209
  async (_uri) => {
757
210
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
758
- const content = await readFile2(join3(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
211
+ const content = await readFile(join(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
759
212
  return {
760
213
  contents: [
761
214
  {
@@ -775,7 +228,7 @@ async function startStdioServer() {
775
228
  await server.connect(transport);
776
229
  }
777
230
  async function startHttpServer(options) {
778
- const { createFabricHttpApp } = await import("./http-DJCTLGF4.js");
231
+ const { createFabricHttpApp } = await import("./http-6LFZLHCN.js");
779
232
  const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
780
233
  const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
781
234
  return await new Promise((resolveServer, rejectServer) => {
@@ -802,17 +255,23 @@ if (isMainModule) {
802
255
  }
803
256
  export {
804
257
  AGENTS_MD_RESOURCE_URI,
258
+ EVENT_LEDGER_PATH,
805
259
  LEDGER_PATH,
806
260
  LEGACY_LEDGER_PATH,
807
- approveHumanLock,
261
+ buildRuleMeta,
262
+ computeRuleTestIndex,
263
+ computeRulesBasedAgentsMeta,
808
264
  createFabricServer,
265
+ deriveRuleMetaLayer,
266
+ deriveRuleMetaTopologyType,
267
+ getEventLedgerPath,
809
268
  getLedgerPath,
810
269
  getLegacyLedgerPath,
811
- readHumanLock,
812
- readHumanLockEntry,
813
- runDoctorAuditReport,
270
+ isSameRuleTestIndex,
814
271
  runDoctorFix,
815
272
  runDoctorReport,
273
+ stableStringify,
816
274
  startHttpServer,
817
- startStdioServer
275
+ startStdioServer,
276
+ writeRuleMeta
818
277
  };