gasp-standard-mcp 1.2.1 → 1.3.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/build/index.js +35 -0
- package/build/parser.js +109 -0
- package/build/standard-data.json +1 -1
- package/build/standard-version.json +1 -1
- package/build/types.d.ts +24 -0
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -94,6 +94,41 @@ server.tool("lookup_metric", "Look up a GASP Standard metric by name or abbrevia
|
|
|
94
94
|
if (metric.aliases.length > 0) {
|
|
95
95
|
parts.push(`\n**Aliases:** ${metric.aliases.join(", ")}`);
|
|
96
96
|
}
|
|
97
|
+
// Dual-lens content
|
|
98
|
+
if (metric.dualLens) {
|
|
99
|
+
const dl = metric.dualLens;
|
|
100
|
+
parts.push("\n## Dual-Lens: Operating / Market Forms");
|
|
101
|
+
parts.push(`\n### Operating Form: ${dl.operating.label}`);
|
|
102
|
+
if (dl.operating.description)
|
|
103
|
+
parts.push(dl.operating.description);
|
|
104
|
+
if (dl.operating.celSource)
|
|
105
|
+
parts.push(`**CEL Source:** ${dl.operating.celSource}`);
|
|
106
|
+
if (dl.operating.atlInclusion)
|
|
107
|
+
parts.push(`**ATL Inclusion:** ${dl.operating.atlInclusion}`);
|
|
108
|
+
if (dl.operating.formula)
|
|
109
|
+
parts.push("```\n" + dl.operating.formula + "\n```");
|
|
110
|
+
if (dl.operating.insight)
|
|
111
|
+
parts.push(`**What it tells you:** ${dl.operating.insight}`);
|
|
112
|
+
parts.push(`\n### Market Form: ${dl.market.label}`);
|
|
113
|
+
if (dl.market.description)
|
|
114
|
+
parts.push(dl.market.description);
|
|
115
|
+
if (dl.market.celSource)
|
|
116
|
+
parts.push(`**CEL Source:** ${dl.market.celSource}`);
|
|
117
|
+
if (dl.market.atlInclusion)
|
|
118
|
+
parts.push(`**ATL Inclusion:** ${dl.market.atlInclusion}`);
|
|
119
|
+
if (dl.market.formula)
|
|
120
|
+
parts.push("```\n" + dl.market.formula + "\n```");
|
|
121
|
+
if (dl.market.insight)
|
|
122
|
+
parts.push(`**What it tells you:** ${dl.market.insight}`);
|
|
123
|
+
parts.push("\n### Bridge");
|
|
124
|
+
if (dl.bridge.reconciliation)
|
|
125
|
+
parts.push("**Reconciliation:**\n```\n" + dl.bridge.reconciliation + "\n```");
|
|
126
|
+
if (dl.bridge.diagnostic)
|
|
127
|
+
parts.push(`**Diagnostic:** ${dl.bridge.diagnostic}`);
|
|
128
|
+
}
|
|
129
|
+
if (metric.dualLensNote) {
|
|
130
|
+
parts.push(`\n## Dual-Lens Note\n\n${metric.dualLensNote}`);
|
|
131
|
+
}
|
|
97
132
|
// Swap EOM → GASP branding
|
|
98
133
|
const text = parts.join("\n").replace(/\bEOM Standard\b/g, "GASP Standard");
|
|
99
134
|
return { content: [{ type: "text", text }] };
|
package/build/parser.js
CHANGED
|
@@ -132,6 +132,7 @@ function parseSectionFields(name, slug, raw, department, category, aliases) {
|
|
|
132
132
|
const formulas = extractFormulas(raw);
|
|
133
133
|
const commonMistakes = extractListField(raw, "Common mistakes");
|
|
134
134
|
const sources = extractSources(raw);
|
|
135
|
+
const { dualLens, dualLensNote } = parseDualLens(raw);
|
|
135
136
|
return {
|
|
136
137
|
name,
|
|
137
138
|
slug,
|
|
@@ -147,6 +148,8 @@ function parseSectionFields(name, slug, raw, department, category, aliases) {
|
|
|
147
148
|
rawMarkdown: `### ${name}\n\n${raw}`,
|
|
148
149
|
signalType: "operational",
|
|
149
150
|
signalTypeSecondary: null,
|
|
151
|
+
dualLens,
|
|
152
|
+
dualLensNote,
|
|
150
153
|
};
|
|
151
154
|
}
|
|
152
155
|
/** Extract a **FieldName:** value from markdown */
|
|
@@ -203,6 +206,112 @@ function extractSources(text) {
|
|
|
203
206
|
}
|
|
204
207
|
return sources;
|
|
205
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Extract a field value from a dual-lens subsection.
|
|
211
|
+
* Terminates at next bold field (**X), code fence, or #### heading — not at \n\n.
|
|
212
|
+
*/
|
|
213
|
+
function extractSubsectionField(text, fieldName) {
|
|
214
|
+
const fence = "\\`\\`\\`";
|
|
215
|
+
const pattern = new RegExp("\\*\\*" + fieldName + ":\\*\\*\\s*([\\s\\S]*?)(?=\\n\\*\\*[A-Z]|\\n" + fence + "|\\n####|$)", "");
|
|
216
|
+
const match = text.match(pattern);
|
|
217
|
+
return match ? match[1].trim() : "";
|
|
218
|
+
}
|
|
219
|
+
/** Extract the first code block from text */
|
|
220
|
+
function extractFirstCodeBlock(text) {
|
|
221
|
+
const fence = "```";
|
|
222
|
+
const regex = new RegExp(fence + "\\n?([\\s\\S]*?)" + fence);
|
|
223
|
+
const match = text.match(regex);
|
|
224
|
+
return match ? match[1].trim() : null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Parse dual-lens content from a metric's raw markdown.
|
|
228
|
+
* Returns { dualLens, dualLensNote }.
|
|
229
|
+
*
|
|
230
|
+
* Pattern 1 — Standard O/M: #### Operating Form: ... / #### Market Form: ... / #### Bridge:
|
|
231
|
+
* Pattern 2 — Shared Churn: #### Dual-Lens: ... with **Operating Form:** / **Market Form:** bold labels
|
|
232
|
+
* Pattern 3 — Note only: #### Dual-Lens Note
|
|
233
|
+
*/
|
|
234
|
+
function parseDualLens(raw) {
|
|
235
|
+
// --- Pattern 3: Note only ---
|
|
236
|
+
const noteHeadingMatch = raw.match(/#### Dual-Lens Note\s*\n([\s\S]*?)(?=\n---|\n####|\n###|$)/);
|
|
237
|
+
if (noteHeadingMatch) {
|
|
238
|
+
const noteText = noteHeadingMatch[1].trim();
|
|
239
|
+
return { dualLens: null, dualLensNote: noteText || null };
|
|
240
|
+
}
|
|
241
|
+
// --- Pattern 1: Standard O/M ---
|
|
242
|
+
const opHeadingMatch = raw.match(/#### Operating Form:\s*(.+)/);
|
|
243
|
+
if (opHeadingMatch) {
|
|
244
|
+
// Split into Operating, Market, Bridge sections
|
|
245
|
+
const opStart = raw.indexOf(opHeadingMatch[0]);
|
|
246
|
+
const mktMatch = raw.match(/#### Market Form:\s*(.+)/);
|
|
247
|
+
const bridgeMatch = raw.match(/#### Bridge:/);
|
|
248
|
+
if (!mktMatch || !bridgeMatch) {
|
|
249
|
+
return { dualLens: null, dualLensNote: null };
|
|
250
|
+
}
|
|
251
|
+
const mktStart = raw.indexOf(mktMatch[0]);
|
|
252
|
+
const bridgeStart = raw.indexOf(bridgeMatch[0]);
|
|
253
|
+
const opSection = raw.substring(opStart + opHeadingMatch[0].length, mktStart).trim();
|
|
254
|
+
const mktSection = raw.substring(mktStart + mktMatch[0].length, bridgeStart).trim();
|
|
255
|
+
const bridgeSection = raw.substring(bridgeStart + bridgeMatch[0].length).trim();
|
|
256
|
+
const operating = parseStandardForm(opHeadingMatch[1].trim(), opSection);
|
|
257
|
+
const market = parseStandardForm(mktMatch[1].trim(), mktSection);
|
|
258
|
+
const bridge = parseBridgeSection(bridgeSection);
|
|
259
|
+
return { dualLens: { operating, market, bridge }, dualLensNote: null };
|
|
260
|
+
}
|
|
261
|
+
// --- Pattern 2: Shared Churn ---
|
|
262
|
+
const dualLensHeadingMatch = raw.match(/#### Dual-Lens:/);
|
|
263
|
+
if (dualLensHeadingMatch) {
|
|
264
|
+
const dlStart = raw.indexOf(dualLensHeadingMatch[0]);
|
|
265
|
+
const dlSection = raw.substring(dlStart + dualLensHeadingMatch[0].length).trim();
|
|
266
|
+
// Split on bold Operating Form / Market Form / Bridge labels
|
|
267
|
+
const opBoldMatch = dlSection.match(/\*\*Operating Form:\s*([^*]+)\*\*/);
|
|
268
|
+
const mktBoldMatch = dlSection.match(/\*\*Market Form:\s*([^*]+)\*\*/);
|
|
269
|
+
const bridgeBoldMatch = dlSection.match(/\*\*Bridge:\*\*/);
|
|
270
|
+
if (!opBoldMatch || !mktBoldMatch || !bridgeBoldMatch) {
|
|
271
|
+
return { dualLens: null, dualLensNote: null };
|
|
272
|
+
}
|
|
273
|
+
const opBoldStart = dlSection.indexOf(opBoldMatch[0]);
|
|
274
|
+
const mktBoldStart = dlSection.indexOf(mktBoldMatch[0]);
|
|
275
|
+
const bridgeBoldStart = dlSection.indexOf(bridgeBoldMatch[0]);
|
|
276
|
+
const opText = dlSection.substring(opBoldStart + opBoldMatch[0].length, mktBoldStart).trim();
|
|
277
|
+
const mktText = dlSection.substring(mktBoldStart + mktBoldMatch[0].length, bridgeBoldStart).trim();
|
|
278
|
+
const bridgeText = dlSection.substring(bridgeBoldStart + bridgeBoldMatch[0].length).trim();
|
|
279
|
+
const operating = parseSharedForm(opBoldMatch[1].trim(), opText);
|
|
280
|
+
const market = parseSharedForm(mktBoldMatch[1].trim(), mktText);
|
|
281
|
+
const bridge = {
|
|
282
|
+
reconciliation: null,
|
|
283
|
+
diagnostic: bridgeText,
|
|
284
|
+
};
|
|
285
|
+
return { dualLens: { operating, market, bridge }, dualLensNote: null };
|
|
286
|
+
}
|
|
287
|
+
return { dualLens: null, dualLensNote: null };
|
|
288
|
+
}
|
|
289
|
+
/** Parse an Operating/Market form from a Standard O/M section (Pattern 1) */
|
|
290
|
+
function parseStandardForm(label, section) {
|
|
291
|
+
// Description = text before the first **bold** field
|
|
292
|
+
const firstBoldIdx = section.search(/\*\*[A-Z]/);
|
|
293
|
+
const description = firstBoldIdx > 0 ? section.substring(0, firstBoldIdx).trim() : "";
|
|
294
|
+
const celSource = extractSubsectionField(section, "CEL Source");
|
|
295
|
+
const atlInclusion = extractSubsectionField(section, "ATL Inclusion(?:[^:]*)?");
|
|
296
|
+
const formula = extractFirstCodeBlock(section);
|
|
297
|
+
const insight = extractSubsectionField(section, "What it tells you");
|
|
298
|
+
return { label, description, celSource, atlInclusion, formula, insight };
|
|
299
|
+
}
|
|
300
|
+
/** Parse an Operating/Market form from a Shared Churn section (Pattern 2) */
|
|
301
|
+
function parseSharedForm(label, section) {
|
|
302
|
+
// Description = text before the first **bold** field
|
|
303
|
+
const firstBoldIdx = section.search(/\*\*[A-Z]/);
|
|
304
|
+
const description = firstBoldIdx > 0 ? section.substring(0, firstBoldIdx).trim() : "";
|
|
305
|
+
const celSource = extractSubsectionField(section, "CEL Source");
|
|
306
|
+
const insight = extractSubsectionField(section, "What it tells you");
|
|
307
|
+
return { label, description, celSource, atlInclusion: "", formula: null, insight };
|
|
308
|
+
}
|
|
309
|
+
/** Parse a Bridge section */
|
|
310
|
+
function parseBridgeSection(section) {
|
|
311
|
+
const reconciliation = extractFirstCodeBlock(section);
|
|
312
|
+
const diagnostic = extractSubsectionField(section, "Diagnostic");
|
|
313
|
+
return { reconciliation, diagnostic };
|
|
314
|
+
}
|
|
206
315
|
/**
|
|
207
316
|
* Parse definitions.md into Definition objects.
|
|
208
317
|
* Each ### heading is a term.
|