@fenglimg/fabric-server 1.8.0-rc.2 → 2.0.0-rc.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,8 +1,9 @@
1
1
  import {
2
- AGENTS_MD_RESOURCE_URI,
3
2
  EVENT_LEDGER_PATH,
4
3
  LEDGER_PATH,
5
4
  LEGACY_LEDGER_PATH,
5
+ appendEventLedgerEvent,
6
+ appendRuleSelectionAuditEvent,
6
7
  buildRuleMeta,
7
8
  computeRuleTestIndex,
8
9
  computeRulesBasedAgentsMeta,
@@ -13,25 +14,28 @@ import {
13
14
  getEventLedgerPath,
14
15
  getLedgerPath,
15
16
  getLegacyLedgerPath,
16
- getRuleSections,
17
17
  isSameRuleTestIndex,
18
- planContext,
18
+ normalizeRulesPath,
19
+ readAgentsMeta,
19
20
  reconcileRules,
20
21
  resolveProjectRoot,
21
22
  runDoctorFix,
22
23
  runDoctorReport,
23
24
  stableStringify,
24
25
  writeRuleMeta
25
- } from "./chunk-EGGZFXMO.js";
26
+ } from "./chunk-NRWDWAVO.js";
26
27
 
27
28
  // src/index.ts
28
29
  import { existsSync as existsSync2 } from "fs";
29
- import { readFile } from "fs/promises";
30
- import { join as join2, resolve } from "path";
30
+ import { readFile as readFile3 } from "fs/promises";
31
+ import { join as join3, resolve } from "path";
31
32
  import { fileURLToPath } from "url";
32
33
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
33
34
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
34
35
 
36
+ // src/constants.ts
37
+ var AGENTS_MD_RESOURCE_URI = "fabric://bootstrap-readme";
38
+
35
39
  // src/services/in-flight-tracker.ts
36
40
  function createInFlightTracker() {
37
41
  const active = /* @__PURE__ */ new Map();
@@ -95,6 +99,254 @@ function readPayloadLimits(projectRoot) {
95
99
  return readFabricConfig(projectRoot).mcpPayloadLimits;
96
100
  }
97
101
 
102
+ // src/services/plan-context.ts
103
+ var SELECTION_TOKEN_TTL_MS = 5 * 60 * 1e3;
104
+ var selectionTokenCache = /* @__PURE__ */ new Map();
105
+ async function planContext(projectRoot, input) {
106
+ const meta = await readAgentsMeta(projectRoot);
107
+ const stale = input.client_hash !== void 0 && input.client_hash !== meta.revision;
108
+ const uniquePaths = dedupePaths(input.paths);
109
+ const includeDeprecated = input.include_deprecated === true;
110
+ const allDescriptions = buildDescriptionIndex(meta).filter(
111
+ (item) => includeDeprecated ? true : !isDeprecatedMaturity(item)
112
+ );
113
+ const entries = uniquePaths.map((path2) => {
114
+ const profile = buildRequirementProfile(path2, input);
115
+ const descriptionIndex = allDescriptions.filter((item) => shouldIncludeIndexItemForPath(item, meta, path2));
116
+ const requiredStableIds2 = descriptionIndex.filter((item) => item.required).map((item) => item.stable_id);
117
+ const aiSelectableStableIds2 = descriptionIndex.filter((item) => item.selectable).map((item) => item.stable_id);
118
+ return {
119
+ path: path2,
120
+ requirement_profile: profile,
121
+ description_index: descriptionIndex,
122
+ required_stable_ids: requiredStableIds2,
123
+ ai_selectable_stable_ids: aiSelectableStableIds2,
124
+ initial_selected_stable_ids: requiredStableIds2,
125
+ selection_policy: {
126
+ required_levels: ["L0", "L2"],
127
+ ai_selectable_levels: ["L1"],
128
+ final_fetch_rule: "required_stable_ids + ai_selected_l1_stable_ids"
129
+ }
130
+ };
131
+ });
132
+ const requiredStableIds = dedupeStableIds(entries.flatMap((entry) => entry.required_stable_ids));
133
+ const aiSelectableStableIds = dedupeStableIds(entries.flatMap((entry) => entry.ai_selectable_stable_ids));
134
+ const sharedDescriptionIndex = dedupeDescriptionIndex(entries.flatMap((entry) => entry.description_index));
135
+ const selectionToken = createSelectionToken(meta.revision, uniquePaths, requiredStableIds, aiSelectableStableIds);
136
+ const result = {
137
+ revision_hash: meta.revision,
138
+ stale,
139
+ selection_token: selectionToken,
140
+ entries,
141
+ shared: {
142
+ required_stable_ids: requiredStableIds,
143
+ ai_selectable_stable_ids: aiSelectableStableIds,
144
+ description_index: sharedDescriptionIndex,
145
+ preflight_diagnostics: buildPreflightDiagnostics(meta)
146
+ }
147
+ };
148
+ try {
149
+ await appendEventLedgerEvent(projectRoot, {
150
+ event_type: "knowledge_context_planned",
151
+ target_paths: uniquePaths,
152
+ required_stable_ids: requiredStableIds,
153
+ ai_selectable_stable_ids: aiSelectableStableIds,
154
+ final_stable_ids: requiredStableIds,
155
+ selection_token: selectionToken,
156
+ client_hash: input.client_hash,
157
+ intent: input.intent,
158
+ known_tech: input.known_tech,
159
+ diagnostics: result.shared.preflight_diagnostics,
160
+ correlation_id: input.correlation_id,
161
+ session_id: input.session_id
162
+ });
163
+ } catch {
164
+ }
165
+ return result;
166
+ }
167
+ function readSelectionToken(token, now = Date.now()) {
168
+ const state = selectionTokenCache.get(token);
169
+ if (state === void 0) {
170
+ return void 0;
171
+ }
172
+ if (state.expires_at <= now) {
173
+ selectionTokenCache.delete(token);
174
+ return void 0;
175
+ }
176
+ return state;
177
+ }
178
+ function createSelectionToken(revisionHash, targetPaths, requiredStableIds, aiSelectableStableIds, now = Date.now()) {
179
+ const token = `selection:${revisionHash}:${now.toString(36)}:${Math.random().toString(36).slice(2)}`;
180
+ selectionTokenCache.set(token, {
181
+ token,
182
+ revision_hash: revisionHash,
183
+ target_paths: targetPaths,
184
+ required_stable_ids: requiredStableIds,
185
+ ai_selectable_stable_ids: aiSelectableStableIds,
186
+ created_at: now,
187
+ expires_at: now + SELECTION_TOKEN_TTL_MS
188
+ });
189
+ return token;
190
+ }
191
+ function dedupePaths(paths) {
192
+ const seenPaths = /* @__PURE__ */ new Set();
193
+ return paths.flatMap((path2) => {
194
+ const normalizedPath = normalizeRulesPath(path2);
195
+ if (seenPaths.has(normalizedPath)) {
196
+ return [];
197
+ }
198
+ seenPaths.add(normalizedPath);
199
+ return [normalizedPath];
200
+ });
201
+ }
202
+ function buildRequirementProfile(path2, input) {
203
+ const normalizedPath = normalizeRulesPath(path2);
204
+ const extensionMatch = /(\.[^./\\]+)$/u.exec(normalizedPath);
205
+ const knownTech = dedupeStableIds([
206
+ ...input.known_tech ?? [],
207
+ ...extensionMatch?.[1] === ".ts" ? ["TypeScript"] : []
208
+ ]);
209
+ return {
210
+ target_path: normalizedPath,
211
+ path_segments: normalizedPath.split("/").filter(Boolean),
212
+ extension: extensionMatch?.[1] ?? "",
213
+ inferred_domain: inferDomains(normalizedPath),
214
+ known_tech: knownTech,
215
+ user_intent: input.intent ?? "",
216
+ intent_tokens: tokenizeIntent(input.intent ?? ""),
217
+ impact_hints: inferImpactHints(input.intent ?? ""),
218
+ detected_entities: input.detected_entities?.[normalizedPath] ?? input.detected_entities?.[path2] ?? []
219
+ };
220
+ }
221
+ function buildDescriptionIndex(meta) {
222
+ return Object.entries(meta.nodes).flatMap(([nodeId, node]) => {
223
+ const level = node.level ?? node.layer;
224
+ const description = node.description ?? descriptionFromLegacyActivation(node.activation?.description);
225
+ if (description === void 0) {
226
+ return [];
227
+ }
228
+ const inferredLayer = inferKnowledgeLayerFromContentRef(node.content_ref ?? node.file);
229
+ return [{
230
+ stable_id: node.stable_id ?? nodeId,
231
+ level,
232
+ required: level === "L0" || level === "L2",
233
+ selectable: level === "L1",
234
+ description,
235
+ type: description.knowledge_type,
236
+ maturity: description.maturity,
237
+ layer: description.knowledge_layer ?? inferredLayer,
238
+ layer_reason: description.layer_reason
239
+ }];
240
+ }).sort(compareDescriptionIndexItems);
241
+ }
242
+ function inferKnowledgeLayerFromContentRef(contentRef) {
243
+ if (contentRef === void 0) {
244
+ return void 0;
245
+ }
246
+ if (contentRef.startsWith("~/.fabric/knowledge/")) {
247
+ return "personal";
248
+ }
249
+ if (contentRef.startsWith(".fabric/knowledge/")) {
250
+ return "team";
251
+ }
252
+ return void 0;
253
+ }
254
+ function isDeprecatedMaturity(item) {
255
+ const a = item.maturity;
256
+ const b = item.description.maturity;
257
+ return a === "deprecated" || b === "deprecated";
258
+ }
259
+ function descriptionFromLegacyActivation(summary) {
260
+ if (summary === void 0) {
261
+ return void 0;
262
+ }
263
+ return {
264
+ summary,
265
+ intent_clues: [],
266
+ tech_stack: [],
267
+ impact: [],
268
+ must_read_if: summary
269
+ };
270
+ }
271
+ function shouldIncludeIndexItemForPath(item, meta, path2) {
272
+ if (item.level === "L0" || item.level === "L1") {
273
+ return true;
274
+ }
275
+ const node = Object.values(meta.nodes).find((candidate) => candidate.stable_id === item.stable_id);
276
+ if (node === void 0) {
277
+ return false;
278
+ }
279
+ return node.scope_glob === path2 || minimatchSimple(path2, node.scope_glob);
280
+ }
281
+ function minimatchSimple(path2, glob) {
282
+ if (glob === "**") {
283
+ return true;
284
+ }
285
+ if (glob.endsWith("/**")) {
286
+ return path2.startsWith(glob.slice(0, -3));
287
+ }
288
+ return path2 === glob;
289
+ }
290
+ function buildPreflightDiagnostics(meta) {
291
+ 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();
292
+ if (missingDescriptionStableIds.length === 0) {
293
+ return [];
294
+ }
295
+ return [{
296
+ code: "missing_description",
297
+ severity: "warn",
298
+ stable_ids: missingDescriptionStableIds,
299
+ message: `Resolved registry includes ${missingDescriptionStableIds.length} node(s) without structured descriptions.`
300
+ }];
301
+ }
302
+ function inferDomains(path2) {
303
+ const domains = [];
304
+ if (path2.includes("/ui/") || path2.toLowerCase().includes("ui")) {
305
+ domains.push("UI");
306
+ }
307
+ if (path2.includes("assets/scripts")) {
308
+ domains.push("Gameplay");
309
+ }
310
+ if (path2.includes("resources") || path2.includes("assets/resources")) {
311
+ domains.push("Asset");
312
+ }
313
+ return domains;
314
+ }
315
+ function tokenizeIntent(intent) {
316
+ 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()));
317
+ return dedupeStableIds(tokens);
318
+ }
319
+ function inferImpactHints(intent) {
320
+ return /性能|优化|drawcall|渲染|卡顿|闪烁/iu.test(intent) ? ["Performance"] : [];
321
+ }
322
+ function dedupeStableIds(stableIds) {
323
+ return Array.from(new Set(stableIds));
324
+ }
325
+ function dedupeDescriptionIndex(items) {
326
+ const seenStableIds = /* @__PURE__ */ new Set();
327
+ return items.filter((item) => {
328
+ if (seenStableIds.has(item.stable_id)) {
329
+ return false;
330
+ }
331
+ seenStableIds.add(item.stable_id);
332
+ return true;
333
+ });
334
+ }
335
+ function compareDescriptionIndexItems(left, right) {
336
+ const levelDelta = levelOrder(left.level) - levelOrder(right.level);
337
+ return levelDelta !== 0 ? levelDelta : left.stable_id.localeCompare(right.stable_id);
338
+ }
339
+ function levelOrder(level) {
340
+ switch (level) {
341
+ case "L0":
342
+ return 0;
343
+ case "L1":
344
+ return 1;
345
+ case "L2":
346
+ return 2;
347
+ }
348
+ }
349
+
98
350
  // src/tools/plan-context.ts
99
351
  function registerPlanContext(server, tracker) {
100
352
  server.registerTool(
@@ -105,7 +357,7 @@ function registerPlanContext(server, tracker) {
105
357
  outputSchema: planContextOutputSchema,
106
358
  annotations: planContextAnnotations
107
359
  },
108
- async ({ paths, intent, known_tech, detected_entities, client_hash, correlation_id, session_id }) => {
360
+ async ({ paths, intent, known_tech, detected_entities, client_hash, correlation_id, session_id, include_deprecated }) => {
109
361
  const requestId = randomUUID();
110
362
  tracker?.enter(requestId);
111
363
  try {
@@ -118,7 +370,8 @@ function registerPlanContext(server, tracker) {
118
370
  detected_entities,
119
371
  client_hash,
120
372
  correlation_id,
121
- session_id
373
+ session_id,
374
+ include_deprecated
122
375
  });
123
376
  const response = {
124
377
  ...result,
@@ -156,6 +409,218 @@ import {
156
409
  ruleSectionsOutputSchema
157
410
  } from "@fenglimg/fabric-shared/schemas/api-contracts";
158
411
  import { enforcePayloadLimit as enforcePayloadLimit2 } from "@fenglimg/fabric-shared/node/mcp-payload-guard";
412
+
413
+ // src/services/rule-sections.ts
414
+ import { readFile } from "fs/promises";
415
+ import { homedir } from "os";
416
+ import { join as join2 } from "path";
417
+ var RULE_SECTION_NAMES = [
418
+ "MISSION_STATEMENT",
419
+ "MANDATORY_INJECTION",
420
+ "BUSINESS_LOGIC_CHUNKS",
421
+ "CONTEXT_INFO"
422
+ ];
423
+ var PRIORITY_ORDER = {
424
+ high: 0,
425
+ medium: 1,
426
+ low: 2
427
+ };
428
+ function parseRuleSections(content) {
429
+ const sections = /* @__PURE__ */ new Map();
430
+ const lines = content.split(/\r?\n/u);
431
+ let activeSection;
432
+ let activeSectionDepth = 0;
433
+ let buffer = [];
434
+ const flush = () => {
435
+ if (activeSection === void 0) {
436
+ return;
437
+ }
438
+ const text = buffer.join("\n").trim();
439
+ if (text.length === 0) {
440
+ buffer = [];
441
+ return;
442
+ }
443
+ sections.set(activeSection, [...sections.get(activeSection) ?? [], text]);
444
+ buffer = [];
445
+ };
446
+ for (const line of lines) {
447
+ const heading = /^(#{2,6})\s+\[([A-Z_]+)\]\s*$/u.exec(line.trim());
448
+ if (heading !== null) {
449
+ flush();
450
+ activeSection = isRuleSectionName(heading[2]) ? heading[2] : void 0;
451
+ activeSectionDepth = activeSection === void 0 ? 0 : heading[1].length;
452
+ continue;
453
+ }
454
+ const ordinaryHeading = /^(#{1,6})\s+/u.exec(line.trim());
455
+ if (ordinaryHeading !== null) {
456
+ if (activeSection !== void 0 && ordinaryHeading[1].length > activeSectionDepth) {
457
+ buffer.push(line);
458
+ continue;
459
+ }
460
+ flush();
461
+ activeSection = void 0;
462
+ activeSectionDepth = 0;
463
+ continue;
464
+ }
465
+ if (activeSection !== void 0) {
466
+ buffer.push(line);
467
+ }
468
+ }
469
+ flush();
470
+ return new Map(
471
+ Array.from(sections.entries()).map(([section, values]) => [section, values.join("\n\n")])
472
+ );
473
+ }
474
+ async function getRuleSections(projectRoot, input) {
475
+ const token = readSelectionToken(input.selection_token);
476
+ if (token === void 0) {
477
+ throw new Error("selection_token is missing or expired");
478
+ }
479
+ validateAiSelections(token.ai_selectable_stable_ids, input.ai_selected_stable_ids, input.ai_selection_reasons);
480
+ const meta = await readAgentsMeta(projectRoot);
481
+ const selectedStableIds = [...token.required_stable_ids, ...input.ai_selected_stable_ids];
482
+ const selectedRules = sortRuleNodes(selectedStableIds.map((stableId) => findRuleNode(meta, stableId)));
483
+ const diagnostics = [];
484
+ const rules = [];
485
+ for (const rule of selectedRules) {
486
+ const content = await readFile(resolveRuleSourcePath(projectRoot, rule.path), "utf8");
487
+ const parsedSections = parseRuleSections(content);
488
+ const sections = {};
489
+ for (const section of input.sections) {
490
+ const sectionContent = parsedSections.get(section);
491
+ sections[section] = sectionContent ?? "";
492
+ if (sectionContent === void 0) {
493
+ diagnostics.push({
494
+ code: "missing_section",
495
+ severity: "warn",
496
+ stable_id: rule.stable_id,
497
+ section,
498
+ message: `Rule ${rule.stable_id} does not define section ${section}.`
499
+ });
500
+ }
501
+ }
502
+ const description = rule.node.description;
503
+ if (description !== void 0 && description.knowledge_type === void 0 && description.knowledge_layer === void 0) {
504
+ diagnostics.push({
505
+ code: "missing_knowledge_metadata",
506
+ severity: "warn",
507
+ stable_id: rule.stable_id,
508
+ message: `Rule ${rule.stable_id} has no knowledge metadata (type/layer) \u2014 likely an un-migrated v1.x entry.`
509
+ });
510
+ }
511
+ rules.push({
512
+ stable_id: rule.stable_id,
513
+ level: rule.level,
514
+ path: rule.path,
515
+ sections
516
+ });
517
+ }
518
+ const result = {
519
+ revision_hash: meta.revision,
520
+ precedence: ["L2", "L1", "L0"],
521
+ selected_stable_ids: rules.map((rule) => rule.stable_id),
522
+ rules,
523
+ diagnostics
524
+ };
525
+ await appendRuleSelectionAuditEvent(projectRoot, {
526
+ path: token.target_paths[0] ?? "",
527
+ selection_token: input.selection_token,
528
+ target_paths: token.target_paths,
529
+ required_stable_ids: token.required_stable_ids,
530
+ ai_selectable_stable_ids: token.ai_selectable_stable_ids,
531
+ ai_selected_stable_ids: input.ai_selected_stable_ids,
532
+ final_stable_ids: result.selected_stable_ids,
533
+ ai_selection_reasons: pickSelectionReasons(input.ai_selected_stable_ids, input.ai_selection_reasons),
534
+ rejected_stable_ids: [],
535
+ ignored_stable_ids: [],
536
+ correlation_id: input.correlation_id,
537
+ session_id: input.session_id
538
+ });
539
+ try {
540
+ await appendEventLedgerEvent(projectRoot, {
541
+ event_type: "knowledge_sections_fetched",
542
+ selection_token: input.selection_token,
543
+ target_paths: token.target_paths,
544
+ requested_sections: input.sections,
545
+ final_stable_ids: result.selected_stable_ids,
546
+ ai_selected_stable_ids: input.ai_selected_stable_ids,
547
+ diagnostics,
548
+ correlation_id: input.correlation_id,
549
+ session_id: input.session_id
550
+ });
551
+ } catch {
552
+ }
553
+ return result;
554
+ }
555
+ function validateAiSelections(aiSelectableStableIds, aiSelectedStableIds, aiSelectionReasons) {
556
+ const selectable = new Set(aiSelectableStableIds);
557
+ for (const stableId of aiSelectedStableIds) {
558
+ if (!selectable.has(stableId)) {
559
+ throw new Error(`Invalid L1 rule selection: ${stableId}`);
560
+ }
561
+ if (aiSelectionReasons[stableId]?.trim() === "") {
562
+ throw new Error(`Missing AI selection reason for ${stableId}`);
563
+ }
564
+ if (aiSelectionReasons[stableId] === void 0) {
565
+ throw new Error(`Missing AI selection reason for ${stableId}`);
566
+ }
567
+ }
568
+ }
569
+ function findRuleNode(meta, stableId) {
570
+ for (const [nodeId, node] of Object.entries(meta.nodes)) {
571
+ const nodeStableId = node.stable_id ?? nodeId;
572
+ if (nodeStableId !== stableId) {
573
+ continue;
574
+ }
575
+ const level = node.level ?? node.layer;
576
+ return {
577
+ stable_id: nodeStableId,
578
+ level,
579
+ path: normalizeRulesPath(node.content_ref ?? node.file),
580
+ priority: node.priority,
581
+ node
582
+ };
583
+ }
584
+ throw new Error(`Selected rule is not present in agents.meta.json: ${stableId}`);
585
+ }
586
+ function sortRuleNodes(rules) {
587
+ return [...rules].sort((left, right) => {
588
+ const levelDelta = outputLevelOrder(left.level) - outputLevelOrder(right.level);
589
+ if (levelDelta !== 0) {
590
+ return levelDelta;
591
+ }
592
+ const priorityDelta = PRIORITY_ORDER[left.priority] - PRIORITY_ORDER[right.priority];
593
+ if (priorityDelta !== 0) {
594
+ return priorityDelta;
595
+ }
596
+ return left.stable_id.localeCompare(right.stable_id);
597
+ });
598
+ }
599
+ function outputLevelOrder(level) {
600
+ switch (level) {
601
+ case "L0":
602
+ return 0;
603
+ case "L1":
604
+ return 1;
605
+ case "L2":
606
+ return 2;
607
+ }
608
+ }
609
+ function isRuleSectionName(value) {
610
+ return RULE_SECTION_NAMES.includes(value);
611
+ }
612
+ function resolveRuleSourcePath(projectRoot, contentRef) {
613
+ if (contentRef.startsWith("~/.fabric/knowledge/")) {
614
+ const home = process.env.FABRIC_HOME ?? homedir();
615
+ return join2(home, ".fabric", "knowledge", contentRef.slice("~/.fabric/knowledge/".length));
616
+ }
617
+ return join2(projectRoot, contentRef);
618
+ }
619
+ function pickSelectionReasons(selectedStableIds, reasons) {
620
+ return Object.fromEntries(selectedStableIds.map((stableId) => [stableId, reasons[stableId] ?? ""]));
621
+ }
622
+
623
+ // src/tools/rule-sections.ts
159
624
  function registerRuleSections(server, tracker) {
160
625
  server.registerTool(
161
626
  "fab_get_rule_sections",
@@ -200,6 +665,83 @@ function registerRuleSections(server, tracker) {
200
665
  );
201
666
  }
202
667
 
668
+ // src/services/knowledge-id-allocator.ts
669
+ import { readFile as readFile2 } from "fs/promises";
670
+ import { dirname } from "path";
671
+ import { mkdir } from "fs/promises";
672
+ import {
673
+ AgentsMetaCountersSchema,
674
+ agentsMetaSchema,
675
+ allocateKnowledgeId,
676
+ defaultAgentsMetaCounters
677
+ } from "@fenglimg/fabric-shared";
678
+ import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
679
+ var KnowledgeIdAllocator = class {
680
+ constructor(metaPath) {
681
+ this.metaPath = metaPath;
682
+ }
683
+ metaPath;
684
+ /**
685
+ * Allocate the next stable_id for the given (layer, type) pair and persist
686
+ * the advanced counter to `agents.meta.json`.
687
+ */
688
+ async allocate(layer, type) {
689
+ const meta = await this.readMeta();
690
+ const counters = this.normalizeCounters(meta.counters);
691
+ const { id, nextCounters } = allocateKnowledgeId(layer, type, counters);
692
+ await this.writeMetaAtomic({ ...meta, counters: nextCounters });
693
+ return id;
694
+ }
695
+ /**
696
+ * Returns the current counters envelope, defaulting to all-zero slots when
697
+ * the meta file is absent or pre-v2.0 (counters key missing).
698
+ */
699
+ async getCounters() {
700
+ const meta = await this.readMeta();
701
+ return this.normalizeCounters(meta.counters);
702
+ }
703
+ // ---- internal helpers ------------------------------------------------
704
+ async readMeta() {
705
+ let raw;
706
+ try {
707
+ raw = await readFile2(this.metaPath, "utf8");
708
+ } catch (err) {
709
+ if (isNodeError(err) && err.code === "ENOENT") {
710
+ return { revision: "", nodes: {} };
711
+ }
712
+ throw err;
713
+ }
714
+ let parsed;
715
+ try {
716
+ parsed = JSON.parse(raw);
717
+ } catch {
718
+ return { revision: "", nodes: {} };
719
+ }
720
+ const validation = agentsMetaSchema.safeParse(parsed);
721
+ if (validation.success) {
722
+ return validation.data;
723
+ }
724
+ return parsed && typeof parsed === "object" ? parsed : {};
725
+ }
726
+ normalizeCounters(input) {
727
+ if (input === void 0 || input === null) {
728
+ return defaultAgentsMetaCounters();
729
+ }
730
+ const parsed = AgentsMetaCountersSchema.safeParse(input);
731
+ return parsed.success ? parsed.data : defaultAgentsMetaCounters();
732
+ }
733
+ async writeMetaAtomic(meta) {
734
+ await ensureParentDirectory(this.metaPath);
735
+ await atomicWriteJson(this.metaPath, meta, { indent: 2 });
736
+ }
737
+ };
738
+ async function ensureParentDirectory(filePath) {
739
+ await mkdir(dirname(filePath), { recursive: true });
740
+ }
741
+ function isNodeError(err) {
742
+ return err instanceof Error && typeof err.code === "string";
743
+ }
744
+
203
745
  // src/services/serve-lock.ts
204
746
  import fs from "fs";
205
747
  import path from "path";
@@ -304,15 +846,15 @@ function formatError(error) {
304
846
  }
305
847
  function formatPreexistingRootMessage(projectRoot) {
306
848
  const preexisting = [];
307
- if (existsSync2(join2(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
308
- if (existsSync2(join2(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
849
+ if (existsSync2(join3(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
850
+ if (existsSync2(join3(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
309
851
  if (preexisting.length === 0) return null;
310
- return `[startup] info: detected ${preexisting.join(", ")} at project root. Note: Fabric serves rules from .fabric/rules/ via MCP \u2014 root markdown files are not auto-loaded into the AI context.`;
852
+ return `[startup] info: detected ${preexisting.join(", ")} at project root. Note: Fabric serves knowledge from .fabric/knowledge/ via MCP \u2014 root markdown files are not auto-loaded into the AI context.`;
311
853
  }
312
854
  function createFabricServer(tracker) {
313
855
  const server = new McpServer({
314
856
  name: "fabric-context-server",
315
- version: "1.8.0-rc.2"
857
+ version: "2.0.0-rc.1"
316
858
  });
317
859
  registerPlanContext(server, tracker);
318
860
  registerRuleSections(server, tracker);
@@ -320,18 +862,22 @@ function createFabricServer(tracker) {
320
862
  "bootstrap README",
321
863
  AGENTS_MD_RESOURCE_URI,
322
864
  {
323
- description: "L0 fabric bootstrap file \u2014 global agent instructions for this project",
865
+ description: "Legacy v1.x bootstrap anchor (deprecated in v2.0; kept as MCP contract shim)",
324
866
  mimeType: "text/markdown"
325
867
  },
326
868
  async (_uri) => {
327
869
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
328
- const content = await readFile(join2(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
870
+ const path2 = join3(projectRoot, ".fabric", "bootstrap", "README.md");
871
+ let text = "";
872
+ if (existsSync2(path2)) {
873
+ text = await readFile3(path2, "utf8");
874
+ }
329
875
  return {
330
876
  contents: [
331
877
  {
332
878
  uri: AGENTS_MD_RESOURCE_URI,
333
879
  mimeType: "text/markdown",
334
- text: content
880
+ text
335
881
  }
336
882
  ]
337
883
  };
@@ -406,7 +952,7 @@ function createShutdownHandler(deps) {
406
952
  };
407
953
  }
408
954
  async function startHttpServer(options) {
409
- const { createFabricHttpApp } = await import("./http-Q7GIL23Y.js");
955
+ const { createFabricHttpApp } = await import("./http-CHCOF6DJ.js");
410
956
  const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
411
957
  const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
412
958
  return await new Promise((resolveServer, rejectServer) => {
@@ -434,10 +980,12 @@ if (isMainModule) {
434
980
  export {
435
981
  AGENTS_MD_RESOURCE_URI,
436
982
  EVENT_LEDGER_PATH,
983
+ KnowledgeIdAllocator,
437
984
  LEDGER_PATH,
438
985
  LEGACY_LEDGER_PATH,
439
986
  ServeLockHeldError,
440
987
  acquireLock,
988
+ appendEventLedgerEvent,
441
989
  buildRuleMeta,
442
990
  checkLockOrThrow,
443
991
  computeRuleTestIndex,