@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/chunk-PTFSYO4Y.js +2083 -0
- package/dist/{http-DJCTLGF4.js → http-6LFZLHCN.js} +269 -198
- package/dist/index.d.ts +42 -60
- package/dist/index.js +125 -666
- 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-TZCE2K4D.js +0 -1447
- package/dist/static/assets/index-B5hhHHl2.css +0 -1
- package/dist/static/assets/index-LJh6IezM.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1,382 +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
|
-
readHumanLockEntry,
|
|
15
|
+
getRuleSections,
|
|
16
|
+
isSameRuleTestIndex,
|
|
17
|
+
planContext,
|
|
18
18
|
resolveProjectRoot,
|
|
19
|
-
runDoctorAuditReport,
|
|
20
19
|
runDoctorFix,
|
|
21
20
|
runDoctorReport,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
stableStringify,
|
|
22
|
+
writeRuleMeta
|
|
23
|
+
} from "./chunk-PTFSYO4Y.js";
|
|
24
24
|
|
|
25
25
|
// src/index.ts
|
|
26
|
-
import { readFile
|
|
27
|
-
import { join
|
|
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/
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 =
|
|
339
|
-
target_path:
|
|
340
|
-
path_segments:
|
|
341
|
-
extension:
|
|
342
|
-
inferred_domain:
|
|
343
|
-
known_tech:
|
|
344
|
-
user_intent:
|
|
345
|
-
intent_tokens:
|
|
346
|
-
impact_hints:
|
|
347
|
-
detected_entities:
|
|
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 =
|
|
350
|
-
required_levels:
|
|
351
|
-
ai_selectable_levels:
|
|
352
|
-
final_fetch_rule:
|
|
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
|
|
355
|
-
revision_hash:
|
|
356
|
-
stale:
|
|
357
|
-
selection_token:
|
|
358
|
-
entries:
|
|
359
|
-
|
|
360
|
-
path:
|
|
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:
|
|
363
|
-
required_stable_ids:
|
|
364
|
-
ai_selectable_stable_ids:
|
|
365
|
-
initial_selected_stable_ids:
|
|
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:
|
|
370
|
-
required_stable_ids:
|
|
371
|
-
ai_selectable_stable_ids:
|
|
372
|
-
description_index:
|
|
373
|
-
preflight_diagnostics:
|
|
374
|
-
|
|
375
|
-
code:
|
|
376
|
-
severity:
|
|
377
|
-
message:
|
|
378
|
-
stable_ids:
|
|
379
|
-
path:
|
|
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
|
|
390
|
-
outputSchema
|
|
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, {
|
|
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
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
|
579
|
-
revision_hash:
|
|
580
|
-
precedence:
|
|
581
|
-
selected_stable_ids:
|
|
582
|
-
rules:
|
|
583
|
-
|
|
584
|
-
stable_id:
|
|
585
|
-
level:
|
|
586
|
-
path:
|
|
587
|
-
sections:
|
|
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:
|
|
591
|
-
|
|
592
|
-
code:
|
|
593
|
-
severity:
|
|
594
|
-
stable_id:
|
|
595
|
-
section:
|
|
596
|
-
message:
|
|
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:
|
|
606
|
-
outputSchema:
|
|
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.
|
|
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
|
|
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-
|
|
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
|
-
|
|
261
|
+
buildRuleMeta,
|
|
262
|
+
computeRuleTestIndex,
|
|
263
|
+
computeRulesBasedAgentsMeta,
|
|
808
264
|
createFabricServer,
|
|
265
|
+
deriveRuleMetaLayer,
|
|
266
|
+
deriveRuleMetaTopologyType,
|
|
267
|
+
getEventLedgerPath,
|
|
809
268
|
getLedgerPath,
|
|
810
269
|
getLegacyLedgerPath,
|
|
811
|
-
|
|
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
|
};
|