@fenglimg/fabric-server 1.5.2 → 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/chunk-PTFSYO4Y.js +2083 -0
- package/dist/{http-U4S7BUP4.js → http-6LFZLHCN.js} +270 -199
- package/dist/index.d.ts +42 -60
- package/dist/index.js +141 -431
- package/dist/static/assets/index-DEc8gkD_.js +10 -0
- package/dist/static/assets/index-FoBU5Kta.css +1 -0
- package/dist/static/index.html +9 -7
- package/package.json +3 -3
- package/dist/chunk-4G6VFG5N.js +0 -1372
- package/dist/static/assets/index-B5hhHHl2.css +0 -1
- package/dist/static/assets/index-D22ASzsl.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1,347 +1,102 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AGENTS_MD_RESOURCE_URI,
|
|
3
|
-
|
|
3
|
+
EVENT_LEDGER_PATH,
|
|
4
4
|
LEDGER_PATH,
|
|
5
5
|
LEGACY_LEDGER_PATH,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
RULE_SECTION_NAMES,
|
|
7
|
+
buildRuleMeta,
|
|
8
|
+
computeRuleTestIndex,
|
|
9
|
+
computeRulesBasedAgentsMeta,
|
|
10
|
+
deriveRuleMetaLayer,
|
|
11
|
+
deriveRuleMetaTopologyType,
|
|
12
|
+
getEventLedgerPath,
|
|
12
13
|
getLedgerPath,
|
|
13
14
|
getLegacyLedgerPath,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
matchRuleNodes,
|
|
18
|
-
normalizeRulesPath,
|
|
19
|
-
readAgentsMeta,
|
|
20
|
-
readHumanLock,
|
|
21
|
-
readHumanLockEntry,
|
|
15
|
+
getRuleSections,
|
|
16
|
+
isSameRuleTestIndex,
|
|
17
|
+
planContext,
|
|
22
18
|
resolveProjectRoot,
|
|
23
|
-
runDoctorAuditReport,
|
|
24
19
|
runDoctorFix,
|
|
25
20
|
runDoctorReport,
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
stableStringify,
|
|
22
|
+
writeRuleMeta
|
|
23
|
+
} from "./chunk-PTFSYO4Y.js";
|
|
28
24
|
|
|
29
25
|
// src/index.ts
|
|
30
26
|
import { readFile } from "fs/promises";
|
|
31
|
-
import { join
|
|
27
|
+
import { join, resolve } from "path";
|
|
32
28
|
import { fileURLToPath } from "url";
|
|
33
29
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
34
30
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
35
31
|
|
|
36
|
-
// src/tools/
|
|
37
|
-
import { aiLedgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
32
|
+
// src/tools/plan-context.ts
|
|
38
33
|
import { z } from "zod";
|
|
39
|
-
|
|
40
|
-
// src/services/append-intent.ts
|
|
41
|
-
async function appendIntent(projectRoot, input) {
|
|
42
|
-
const ts = Date.now();
|
|
43
|
-
const entry = await appendLedgerEntry(projectRoot, {
|
|
44
|
-
...input.entry,
|
|
45
|
-
ts,
|
|
46
|
-
source: "ai"
|
|
47
|
-
});
|
|
48
|
-
let compliance;
|
|
49
|
-
try {
|
|
50
|
-
const auditResult = await appendEditIntentAuditEvents(projectRoot, {
|
|
51
|
-
affected_paths: entry.affected_paths,
|
|
52
|
-
intent: entry.intent,
|
|
53
|
-
ledger_entry_id: entry.id,
|
|
54
|
-
ts
|
|
55
|
-
});
|
|
56
|
-
compliance = auditResult.compliance;
|
|
57
|
-
} catch {
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
success: true,
|
|
61
|
-
timestamp: ts,
|
|
62
|
-
entry,
|
|
63
|
-
compliance
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/tools/append-intent.ts
|
|
68
34
|
var inputSchema = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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")
|
|
74
42
|
};
|
|
75
|
-
var
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
window_ms: z.number()
|
|
83
|
-
}).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()
|
|
84
50
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
outputSchema,
|
|
92
|
-
annotations: { readOnlyHint: false }
|
|
93
|
-
},
|
|
94
|
-
async ({ entry }) => {
|
|
95
|
-
const projectRoot = resolveProjectRoot();
|
|
96
|
-
const result = await appendIntent(projectRoot, { entry });
|
|
97
|
-
const structuredContent = {
|
|
98
|
-
success: result.success,
|
|
99
|
-
timestamp: result.timestamp,
|
|
100
|
-
entry: { ...result.entry },
|
|
101
|
-
compliance: result.compliance
|
|
102
|
-
};
|
|
103
|
-
return {
|
|
104
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
105
|
-
structuredContent
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// src/tools/get-rules.ts
|
|
112
|
-
import { z as z2 } from "zod";
|
|
113
|
-
var inputSchema2 = {
|
|
114
|
-
path: z2.string().describe("Target file path to query rules for"),
|
|
115
|
-
client_hash: z2.string().optional().describe("Revision hash from prior fab_get_rules response; enables stale detection")
|
|
116
|
-
};
|
|
117
|
-
var rulesEntrySchema = z2.object({ path: z2.string(), content: z2.string() });
|
|
118
|
-
var humanLockedSchema = z2.object({ file: z2.string(), excerpt: z2.string() });
|
|
119
|
-
var descriptionStubSchema = z2.object({ path: z2.string(), description: z2.string() });
|
|
120
|
-
var outputSchema2 = z2.object({
|
|
121
|
-
revision_hash: z2.string(),
|
|
122
|
-
stale: z2.boolean(),
|
|
123
|
-
rules: z2.object({
|
|
124
|
-
L0: z2.string(),
|
|
125
|
-
L1: z2.array(rulesEntrySchema),
|
|
126
|
-
L2: z2.array(rulesEntrySchema),
|
|
127
|
-
human_locked_nearby: z2.array(humanLockedSchema),
|
|
128
|
-
description_stubs: z2.array(descriptionStubSchema).optional()
|
|
129
|
-
})
|
|
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(),
|
|
56
|
+
description: ruleDescriptionSchema
|
|
130
57
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const projectRoot = resolveProjectRoot();
|
|
142
|
-
const result = await getRules(projectRoot, { path, client_hash });
|
|
143
|
-
return {
|
|
144
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
145
|
-
structuredContent: result
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// src/tools/plan-context.ts
|
|
152
|
-
import { z as z3 } from "zod";
|
|
153
|
-
|
|
154
|
-
// src/services/plan-context.ts
|
|
155
|
-
async function planContext(projectRoot, input) {
|
|
156
|
-
const context = await loadGetRulesContext(projectRoot);
|
|
157
|
-
const stale = input.client_hash !== void 0 && input.client_hash !== context.meta.revision;
|
|
158
|
-
const uniquePaths = dedupePaths(input.paths);
|
|
159
|
-
const fileContentCache = /* @__PURE__ */ new Map();
|
|
160
|
-
const matchedNodesByPath = new Map(
|
|
161
|
-
uniquePaths.map((path) => [path, matchRuleNodes(context.meta, path)])
|
|
162
|
-
);
|
|
163
|
-
const loadedByPath = new Map(
|
|
164
|
-
await Promise.all(
|
|
165
|
-
uniquePaths.map(async (path) => [
|
|
166
|
-
path,
|
|
167
|
-
await loadMatchedRules(projectRoot, matchedNodesByPath.get(path) ?? [], fileContentCache)
|
|
168
|
-
])
|
|
169
|
-
)
|
|
170
|
-
);
|
|
171
|
-
const entries = uniquePaths.map((path) => ({
|
|
172
|
-
path,
|
|
173
|
-
rules: buildRulesPayload(context, loadedByPath.get(path) ?? { rules: [], stubs: [] }, {
|
|
174
|
-
dedupeByPath: true
|
|
175
|
-
})
|
|
176
|
-
}));
|
|
177
|
-
const shared = buildSharedView(context.meta.revision, uniquePaths, matchedNodesByPath, loadedByPath);
|
|
178
|
-
return {
|
|
179
|
-
revision_hash: context.meta.revision,
|
|
180
|
-
stale,
|
|
181
|
-
entries,
|
|
182
|
-
shared
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function dedupePaths(paths) {
|
|
186
|
-
const seenPaths = /* @__PURE__ */ new Set();
|
|
187
|
-
return paths.flatMap((path) => {
|
|
188
|
-
const normalizedPath = normalizeRulesPath(path);
|
|
189
|
-
if (seenPaths.has(normalizedPath)) {
|
|
190
|
-
return [];
|
|
191
|
-
}
|
|
192
|
-
seenPaths.add(normalizedPath);
|
|
193
|
-
return [normalizedPath];
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
function buildSharedView(revision, uniquePaths, matchedNodesByPath, loadedByPath) {
|
|
197
|
-
const sharedEntriesByStableId = /* @__PURE__ */ new Map();
|
|
198
|
-
const descriptionStubByStableId = /* @__PURE__ */ new Map();
|
|
199
|
-
const derivedStableIds = /* @__PURE__ */ new Set();
|
|
200
|
-
const bundleStableIds = /* @__PURE__ */ new Set();
|
|
201
|
-
const fileMap = Object.fromEntries(
|
|
202
|
-
uniquePaths.map((path) => {
|
|
203
|
-
const matchedNodes = matchedNodesByPath.get(path) ?? [];
|
|
204
|
-
const loaded = loadedByPath.get(path) ?? { rules: [], stubs: [] };
|
|
205
|
-
const l1 = collectPerPathStableIds(loaded.rules, "L1");
|
|
206
|
-
const l2 = collectPerPathStableIds(loaded.rules, "L2");
|
|
207
|
-
const descriptionStubs = dedupeStableIds(loaded.stubs.map((stub) => stub.stable_id));
|
|
208
|
-
for (const matchedNode of matchedNodes) {
|
|
209
|
-
bundleStableIds.add(matchedNode.stable_id);
|
|
210
|
-
if (matchedNode.identity_source === "derived") {
|
|
211
|
-
derivedStableIds.add(matchedNode.stable_id);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
for (const rule of loaded.rules) {
|
|
215
|
-
sharedEntriesByStableId.set(rule.stable_id, {
|
|
216
|
-
stable_id: rule.stable_id,
|
|
217
|
-
identity_source: rule.identity_source,
|
|
218
|
-
level: rule.level,
|
|
219
|
-
path: rule.entry.path,
|
|
220
|
-
content: rule.entry.content
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
for (const stub of loaded.stubs) {
|
|
224
|
-
descriptionStubByStableId.set(stub.stable_id, stub);
|
|
225
|
-
}
|
|
226
|
-
return [
|
|
227
|
-
path,
|
|
228
|
-
{
|
|
229
|
-
L1: l1,
|
|
230
|
-
L2: l2,
|
|
231
|
-
description_stubs: descriptionStubs
|
|
232
|
-
}
|
|
233
|
-
];
|
|
234
|
-
})
|
|
235
|
-
);
|
|
236
|
-
const descriptionStubUnion = Array.from(descriptionStubByStableId.values()).sort(compareStableIds);
|
|
237
|
-
const sharedEntries = Array.from(sharedEntriesByStableId.values()).sort(compareStableIds);
|
|
238
|
-
const preflightDiagnostics = [];
|
|
239
|
-
for (const path of uniquePaths) {
|
|
240
|
-
const slice = fileMap[path];
|
|
241
|
-
if (slice !== void 0 && slice.L1.length === 0 && slice.L2.length === 0 && slice.description_stubs.length > 0) {
|
|
242
|
-
preflightDiagnostics.push({
|
|
243
|
-
code: "description_stub_only",
|
|
244
|
-
severity: "info",
|
|
245
|
-
path,
|
|
246
|
-
stable_ids: slice.description_stubs,
|
|
247
|
-
message: `Path ${path} only matched description stubs and no loadable L1/L2 rules. Run fab_get_rules on the final target before editing if you need the full rule text.`
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (derivedStableIds.size > 0) {
|
|
252
|
-
const stableIds = Array.from(derivedStableIds).sort();
|
|
253
|
-
preflightDiagnostics.push({
|
|
254
|
-
code: "derived_identity",
|
|
255
|
-
severity: "warn",
|
|
256
|
-
stable_ids: stableIds,
|
|
257
|
-
message: `Resolved bundle includes ${stableIds.length} rule node${stableIds.length === 1 ? "" : "s"} that still rely on derived identities. Declare \`<!-- fab:rule-id ... -->\` in the source rule file to stabilize audit references.`
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
resolved_bundle_id: sha256([revision, ...Array.from(bundleStableIds).sort()].join("\n")),
|
|
262
|
-
shared_entries: sharedEntries,
|
|
263
|
-
file_map: fileMap,
|
|
264
|
-
description_stub_union: descriptionStubUnion,
|
|
265
|
-
preflight_diagnostics: preflightDiagnostics
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
function dedupeStableIds(stableIds) {
|
|
269
|
-
return Array.from(new Set(stableIds));
|
|
270
|
-
}
|
|
271
|
-
function collectPerPathStableIds(rules, level) {
|
|
272
|
-
const seenPaths = /* @__PURE__ */ new Set();
|
|
273
|
-
const stableIds = [];
|
|
274
|
-
for (const rule of rules) {
|
|
275
|
-
if (rule.level !== level || seenPaths.has(rule.entry.path)) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
seenPaths.add(rule.entry.path);
|
|
279
|
-
stableIds.push(rule.stable_id);
|
|
280
|
-
}
|
|
281
|
-
return stableIds;
|
|
282
|
-
}
|
|
283
|
-
function compareStableIds(left, right) {
|
|
284
|
-
return left.stable_id.localeCompare(right.stable_id);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// src/tools/plan-context.ts
|
|
288
|
-
var inputSchema3 = {
|
|
289
|
-
paths: z3.array(z3.string()).min(2).describe("Candidate file paths to query rules for during planning or architecture review"),
|
|
290
|
-
client_hash: z3.string().optional().describe("Revision hash from a prior fab_plan_context response; enables stale detection")
|
|
291
|
-
};
|
|
292
|
-
var rulesEntrySchema2 = z3.object({ path: z3.string(), content: z3.string() });
|
|
293
|
-
var humanLockedSchema2 = z3.object({ file: z3.string(), excerpt: z3.string() });
|
|
294
|
-
var descriptionStubSchema2 = z3.object({ path: z3.string(), description: z3.string() });
|
|
295
|
-
var rulesPayloadSchema = z3.object({
|
|
296
|
-
L0: z3.string(),
|
|
297
|
-
L1: z3.array(rulesEntrySchema2),
|
|
298
|
-
L2: z3.array(rulesEntrySchema2),
|
|
299
|
-
human_locked_nearby: z3.array(humanLockedSchema2),
|
|
300
|
-
description_stubs: z3.array(descriptionStubSchema2).optional()
|
|
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())
|
|
301
68
|
});
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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")
|
|
73
|
+
});
|
|
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(),
|
|
81
|
+
requirement_profile: requirementProfileSchema,
|
|
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()),
|
|
86
|
+
selection_policy: selectionPolicySchema
|
|
309
87
|
})
|
|
310
88
|
),
|
|
311
|
-
shared:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
file_map: z3.record(
|
|
323
|
-
z3.object({
|
|
324
|
-
L1: z3.array(z3.string()),
|
|
325
|
-
L2: z3.array(z3.string()),
|
|
326
|
-
description_stubs: z3.array(z3.string())
|
|
327
|
-
})
|
|
328
|
-
),
|
|
329
|
-
description_stub_union: z3.array(
|
|
330
|
-
z3.object({
|
|
331
|
-
stable_id: z3.string(),
|
|
332
|
-
identity_source: z3.enum(["declared", "derived"]),
|
|
333
|
-
level: z3.enum(["L1", "L2"]),
|
|
334
|
-
path: z3.string(),
|
|
335
|
-
description: z3.string()
|
|
336
|
-
})
|
|
337
|
-
),
|
|
338
|
-
preflight_diagnostics: z3.array(
|
|
339
|
-
z3.object({
|
|
340
|
-
code: z3.enum(["description_stub_only", "derived_identity"]),
|
|
341
|
-
severity: z3.enum(["info", "warn"]),
|
|
342
|
-
message: z3.string(),
|
|
343
|
-
path: z3.string().optional(),
|
|
344
|
-
stable_ids: z3.array(z3.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()
|
|
345
100
|
})
|
|
346
101
|
)
|
|
347
102
|
})
|
|
@@ -350,14 +105,22 @@ function registerPlanContext(server) {
|
|
|
350
105
|
server.registerTool(
|
|
351
106
|
"fab_plan_context",
|
|
352
107
|
{
|
|
353
|
-
description: "Use during plan or architecture phases to
|
|
354
|
-
inputSchema
|
|
355
|
-
outputSchema
|
|
108
|
+
description: "Use during plan or architecture phases to build a neutral Fabric rule description index and selection token before fetching rule sections.",
|
|
109
|
+
inputSchema,
|
|
110
|
+
outputSchema,
|
|
356
111
|
annotations: { readOnlyHint: true }
|
|
357
112
|
},
|
|
358
|
-
async ({ paths, client_hash }) => {
|
|
113
|
+
async ({ paths, intent, known_tech, detected_entities, client_hash, correlation_id, session_id }) => {
|
|
359
114
|
const projectRoot = resolveProjectRoot();
|
|
360
|
-
const result = await planContext(projectRoot, {
|
|
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
|
+
});
|
|
361
124
|
return {
|
|
362
125
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
363
126
|
structuredContent: result
|
|
@@ -366,107 +129,50 @@ function registerPlanContext(server) {
|
|
|
366
129
|
);
|
|
367
130
|
}
|
|
368
131
|
|
|
369
|
-
// src/tools/
|
|
370
|
-
import {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const currentMeta = await readAgentsMeta(projectRoot);
|
|
379
|
-
const nextMeta = applyRegistryOperation(currentMeta, input.op, input.node_id, input.data);
|
|
380
|
-
const newRevision = computeRevision(nextMeta);
|
|
381
|
-
await atomicWriteText(
|
|
382
|
-
metaPath,
|
|
383
|
-
`${JSON.stringify(
|
|
384
|
-
{
|
|
385
|
-
...nextMeta,
|
|
386
|
-
revision: newRevision
|
|
387
|
-
},
|
|
388
|
-
null,
|
|
389
|
-
2
|
|
390
|
-
)}
|
|
391
|
-
`
|
|
392
|
-
);
|
|
393
|
-
contextCache.invalidate("meta_write", projectRoot);
|
|
394
|
-
return {
|
|
395
|
-
revision_hash: newRevision,
|
|
396
|
-
success: true
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
function computeRevision(meta) {
|
|
400
|
-
const joinedHashes = Object.entries(meta.nodes).sort(([leftId], [rightId]) => leftId.localeCompare(rightId)).map(([, node]) => node.hash).join("");
|
|
401
|
-
return sha256(joinedHashes);
|
|
402
|
-
}
|
|
403
|
-
function assertNodeData(data, message) {
|
|
404
|
-
if (data === void 0) {
|
|
405
|
-
throw new Error(message);
|
|
406
|
-
}
|
|
407
|
-
return agentsMetaNodeSchema.parse(data);
|
|
408
|
-
}
|
|
409
|
-
function applyRegistryOperation(meta, op, nodeId, data) {
|
|
410
|
-
const nextNodes = { ...meta.nodes };
|
|
411
|
-
if (op === "remove-node") {
|
|
412
|
-
delete nextNodes[nodeId];
|
|
413
|
-
return {
|
|
414
|
-
...meta,
|
|
415
|
-
nodes: nextNodes
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
if (op === "add-node") {
|
|
419
|
-
nextNodes[nodeId] = assertNodeData(data, `fab_update_registry requires data for ${op}`);
|
|
420
|
-
return {
|
|
421
|
-
...meta,
|
|
422
|
-
nodes: nextNodes
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
const currentNode = nextNodes[nodeId];
|
|
426
|
-
if (currentNode === void 0) {
|
|
427
|
-
throw new Error(`Cannot update missing Fabric registry node: ${nodeId}`);
|
|
428
|
-
}
|
|
429
|
-
nextNodes[nodeId] = agentsMetaNodeSchema.parse({
|
|
430
|
-
...currentNode,
|
|
431
|
-
...data
|
|
432
|
-
});
|
|
433
|
-
return {
|
|
434
|
-
...meta,
|
|
435
|
-
nodes: nextNodes
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// src/tools/update-registry.ts
|
|
440
|
-
var nodeInputSchema = z4.object({
|
|
441
|
-
file: z4.string().optional(),
|
|
442
|
-
scope_glob: z4.string().optional(),
|
|
443
|
-
deps: z4.array(z4.string()).optional(),
|
|
444
|
-
priority: z4.enum(["high", "medium", "low"]).optional(),
|
|
445
|
-
layer: agentsLayerSchema.optional(),
|
|
446
|
-
topology_type: agentsTopologyTypeSchema.optional(),
|
|
447
|
-
hash: z4.string().optional()
|
|
448
|
-
});
|
|
449
|
-
var inputSchema4 = {
|
|
450
|
-
op: z4.enum(["add-node", "remove-node", "update-node"]),
|
|
451
|
-
node_id: z4.string(),
|
|
452
|
-
data: nodeInputSchema.optional()
|
|
132
|
+
// src/tools/rule-sections.ts
|
|
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")
|
|
453
141
|
};
|
|
454
|
-
var
|
|
455
|
-
|
|
456
|
-
|
|
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())
|
|
152
|
+
})
|
|
153
|
+
),
|
|
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()
|
|
161
|
+
})
|
|
162
|
+
)
|
|
457
163
|
});
|
|
458
|
-
function
|
|
164
|
+
function registerRuleSections(server) {
|
|
459
165
|
server.registerTool(
|
|
460
|
-
"
|
|
166
|
+
"fab_get_rule_sections",
|
|
461
167
|
{
|
|
462
|
-
description: "
|
|
463
|
-
inputSchema:
|
|
464
|
-
outputSchema:
|
|
465
|
-
annotations: {
|
|
168
|
+
description: "Fetch structured Fabric rule sections after fab_plan_context. Required L0/L2 rules are merged with AI-selected L1 rules server-side.",
|
|
169
|
+
inputSchema: inputSchema2,
|
|
170
|
+
outputSchema: outputSchema2,
|
|
171
|
+
annotations: { readOnlyHint: true }
|
|
466
172
|
},
|
|
467
|
-
async (
|
|
173
|
+
async (input) => {
|
|
468
174
|
const projectRoot = resolveProjectRoot();
|
|
469
|
-
const result = await
|
|
175
|
+
const result = await getRuleSections(projectRoot, input);
|
|
470
176
|
return {
|
|
471
177
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
472
178
|
structuredContent: result
|
|
@@ -489,12 +195,10 @@ function formatError(error) {
|
|
|
489
195
|
function createFabricServer() {
|
|
490
196
|
const server = new McpServer({
|
|
491
197
|
name: "fabric-context-server",
|
|
492
|
-
version: "1.
|
|
198
|
+
version: "1.7.0"
|
|
493
199
|
});
|
|
494
|
-
registerGetRules(server);
|
|
495
200
|
registerPlanContext(server);
|
|
496
|
-
|
|
497
|
-
registerUpdateRegistry(server);
|
|
201
|
+
registerRuleSections(server);
|
|
498
202
|
server.registerResource(
|
|
499
203
|
"bootstrap README",
|
|
500
204
|
AGENTS_MD_RESOURCE_URI,
|
|
@@ -504,7 +208,7 @@ function createFabricServer() {
|
|
|
504
208
|
},
|
|
505
209
|
async (_uri) => {
|
|
506
210
|
const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
507
|
-
const content = await readFile(
|
|
211
|
+
const content = await readFile(join(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
|
|
508
212
|
return {
|
|
509
213
|
contents: [
|
|
510
214
|
{
|
|
@@ -524,7 +228,7 @@ async function startStdioServer() {
|
|
|
524
228
|
await server.connect(transport);
|
|
525
229
|
}
|
|
526
230
|
async function startHttpServer(options) {
|
|
527
|
-
const { createFabricHttpApp } = await import("./http-
|
|
231
|
+
const { createFabricHttpApp } = await import("./http-6LFZLHCN.js");
|
|
528
232
|
const { port, projectRoot, host = "127.0.0.1", authToken, dashboardDistPath, dev } = options;
|
|
529
233
|
const app = createFabricHttpApp({ projectRoot, host, authToken, dashboardDistPath, dev });
|
|
530
234
|
return await new Promise((resolveServer, rejectServer) => {
|
|
@@ -551,17 +255,23 @@ if (isMainModule) {
|
|
|
551
255
|
}
|
|
552
256
|
export {
|
|
553
257
|
AGENTS_MD_RESOURCE_URI,
|
|
258
|
+
EVENT_LEDGER_PATH,
|
|
554
259
|
LEDGER_PATH,
|
|
555
260
|
LEGACY_LEDGER_PATH,
|
|
556
|
-
|
|
261
|
+
buildRuleMeta,
|
|
262
|
+
computeRuleTestIndex,
|
|
263
|
+
computeRulesBasedAgentsMeta,
|
|
557
264
|
createFabricServer,
|
|
265
|
+
deriveRuleMetaLayer,
|
|
266
|
+
deriveRuleMetaTopologyType,
|
|
267
|
+
getEventLedgerPath,
|
|
558
268
|
getLedgerPath,
|
|
559
269
|
getLegacyLedgerPath,
|
|
560
|
-
|
|
561
|
-
readHumanLockEntry,
|
|
562
|
-
runDoctorAuditReport,
|
|
270
|
+
isSameRuleTestIndex,
|
|
563
271
|
runDoctorFix,
|
|
564
272
|
runDoctorReport,
|
|
273
|
+
stableStringify,
|
|
565
274
|
startHttpServer,
|
|
566
|
-
startStdioServer
|
|
275
|
+
startStdioServer,
|
|
276
|
+
writeRuleMeta
|
|
567
277
|
};
|