@skillsmith/core 0.11.2 → 0.11.3
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/CHANGELOG.md +15 -0
- package/README.md +6 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/McpReferenceExtractor.d.ts +59 -5
- package/dist/src/analysis/McpReferenceExtractor.d.ts.map +1 -1
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts +2 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.js +278 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.js.map +1 -0
- package/dist/src/analysis/McpReferenceExtractor.js +276 -20
- package/dist/src/analysis/McpReferenceExtractor.js.map +1 -1
- package/dist/src/analysis/McpReferenceExtractor.test.js +4 -0
- package/dist/src/analysis/McpReferenceExtractor.test.js.map +1 -1
- package/dist/src/config/config-atomic-write.d.ts +58 -0
- package/dist/src/config/config-atomic-write.d.ts.map +1 -0
- package/dist/src/config/config-atomic-write.js +151 -0
- package/dist/src/config/config-atomic-write.js.map +1 -0
- package/dist/src/config/config-atomic-write.test.d.ts +9 -0
- package/dist/src/config/config-atomic-write.test.d.ts.map +1 -0
- package/dist/src/config/config-atomic-write.test.js +92 -0
- package/dist/src/config/config-atomic-write.test.js.map +1 -0
- package/dist/src/config/device-identity.d.ts +16 -0
- package/dist/src/config/device-identity.d.ts.map +1 -1
- package/dist/src/config/device-identity.js +82 -14
- package/dist/src/config/device-identity.js.map +1 -1
- package/dist/src/config/device-identity.test.js +158 -1
- package/dist/src/config/device-identity.test.js.map +1 -1
- package/dist/src/config/index.d.ts +14 -0
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +31 -15
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/exports/services.d.ts +2 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +1 -0
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SwarmIndexer.d.ts +1 -1
- package/dist/src/indexer/SwarmIndexer.js +2 -2
- package/dist/src/indexer/SwarmIndexer.js.map +1 -1
- package/dist/src/install/paths.d.ts +1 -1
- package/dist/src/install/paths.d.ts.map +1 -1
- package/dist/src/install/paths.js +2 -0
- package/dist/src/install/paths.js.map +1 -1
- package/dist/src/install/paths.test.js +10 -1
- package/dist/src/install/paths.test.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/security/audit-types.d.ts +1 -1
- package/dist/src/security/audit-types.d.ts.map +1 -1
- package/dist/src/security/audit-types.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts +7 -0
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.js +10 -74
- package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.d.ts +14 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.js +33 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts +9 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.js +65 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js +15 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
- package/dist/src/security/scanner/confusables.d.ts +28 -0
- package/dist/src/security/scanner/confusables.d.ts.map +1 -0
- package/dist/src/security/scanner/confusables.js +87 -0
- package/dist/src/security/scanner/confusables.js.map +1 -0
- package/dist/src/security/scanner/confusables.test.d.ts +10 -0
- package/dist/src/security/scanner/confusables.test.d.ts.map +1 -0
- package/dist/src/security/scanner/confusables.test.js +44 -0
- package/dist/src/security/scanner/confusables.test.js.map +1 -0
- package/dist/src/security/scanner/index.d.ts +7 -1
- package/dist/src/security/scanner/index.d.ts.map +1 -1
- package/dist/src/security/scanner/index.js +13 -0
- package/dist/src/security/scanner/index.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +14 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/typosquat-reference-list.d.ts +54 -0
- package/dist/src/security/scanner/typosquat-reference-list.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.js +55 -0
- package/dist/src/security/scanner/typosquat-reference-list.js.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.d.ts +5 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.js +52 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.js.map +1 -0
- package/dist/src/security/scanner/typosquat.d.ts +100 -0
- package/dist/src/security/scanner/typosquat.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat.js +256 -0
- package/dist/src/security/scanner/typosquat.js.map +1 -0
- package/dist/src/security/scanner/typosquat.test.d.ts +12 -0
- package/dist/src/security/scanner/typosquat.test.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat.test.js +140 -0
- package/dist/src/security/scanner/typosquat.test.js.map +1 -0
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +10 -0
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.d.ts +21 -0
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +46 -2
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.test.d.ts +2 -0
- package/dist/src/services/skill-installation.helpers.test.d.ts.map +1 -0
- package/dist/src/services/skill-installation.helpers.test.js +80 -0
- package/dist/src/services/skill-installation.helpers.test.js.map +1 -0
- package/dist/src/sync/inventory-collector.d.ts +15 -4
- package/dist/src/sync/inventory-collector.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.js +73 -24
- package/dist/src/sync/inventory-collector.js.map +1 -1
- package/dist/src/sync/inventory-collector.test.d.ts +15 -1
- package/dist/src/sync/inventory-collector.test.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.test.js +75 -5
- package/dist/src/sync/inventory-collector.test.js.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +1 -1
- package/dist/src/telemetry/tracer-imports.js +1 -1
- package/dist/src/telemetry/tracer.js +1 -1
- package/dist/tests/SecurityScanner.scoring.test.js +60 -0
- package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
- package/dist/tests/SwarmIndexer.test.js +4 -5
- package/dist/tests/SwarmIndexer.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +1 -0
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/services/aidefence-feedback.test.js +1 -0
- package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +1 -0
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/package.json +13 -8
|
@@ -2,10 +2,29 @@
|
|
|
2
2
|
* @fileoverview Extracts MCP tool references from SKILL.md content
|
|
3
3
|
* @module @skillsmith/core/analysis/McpReferenceExtractor
|
|
4
4
|
* @see SMI-3145: Build McpReferenceExtractor
|
|
5
|
+
* @see SMI-5676: Wave 1 Step 3b — harden extraction (26-file validation found a
|
|
6
|
+
* 71% false-negative rate against real SKILL.md files)
|
|
5
7
|
*
|
|
6
|
-
* Scans skill content for
|
|
7
|
-
*
|
|
8
|
+
* Scans skill content for three kinds of MCP dependency signal:
|
|
9
|
+
* 1. Inline `mcp__<server>__<tool>` references in prose/code (original
|
|
10
|
+
* SMI-3145 behavior), tracking fenced-code-block state for confidence.
|
|
11
|
+
* 2. Frontmatter `allowed-tools`/`tools` YAML fields, including the
|
|
12
|
+
* bare-server (`mcp__linear`) and wildcard (`mcp__linear__*`) forms.
|
|
13
|
+
* 3. Embedded `mcpServers` JSON registration blocks (e.g. a fenced code
|
|
14
|
+
* block showing how to add the skill's server to `.mcp.json`).
|
|
15
|
+
*
|
|
16
|
+
* All three feed the same `references`/`servers`/`highConfidenceServers`
|
|
17
|
+
* output — hardening changes what gets *extracted*, not how extracted
|
|
18
|
+
* results get scored (see `DependencyMerger.ts`, unchanged).
|
|
19
|
+
*
|
|
20
|
+
* A fourth signal, `registeredServers`, lets a caller cross-check every
|
|
21
|
+
* candidate server name against the consuming project's own `.mcp.json`.
|
|
22
|
+
* This is advisory tagging only (`serverResolutions`) — a name is NEVER
|
|
23
|
+
* excluded from `references`/`servers` just because it doesn't resolve,
|
|
24
|
+
* since a real (but not-yet-installed) dependency looks identical to a
|
|
25
|
+
* stale/renamed one from here. See `serverResolutions` doc below.
|
|
8
26
|
*/
|
|
27
|
+
import { LineCounter, isScalar, isSeq, parseDocument } from 'yaml';
|
|
9
28
|
/** Maximum input size in bytes before truncation */
|
|
10
29
|
const MAX_INPUT_BYTES = 100 * 1024;
|
|
11
30
|
/**
|
|
@@ -16,17 +35,48 @@ const MAX_INPUT_BYTES = 100 * 1024;
|
|
|
16
35
|
const MCP_PATTERN = /mcp__([a-z][a-z0-9-]*)__([a-z][a-z0-9_]*)/g;
|
|
17
36
|
/** Matches the opening or closing of a fenced code block */
|
|
18
37
|
const FENCE_PATTERN = /^(`{3,}|~{3,})/;
|
|
38
|
+
/** Matches a bare `---` frontmatter delimiter line */
|
|
39
|
+
const FRONTMATTER_DELIMITER = /^---\s*$/;
|
|
40
|
+
/** Frontmatter fields scanned for `mcp__*` tool references (SMI-5676) */
|
|
41
|
+
const TOOL_LIST_FIELDS = ['allowed-tools', 'tools'];
|
|
42
|
+
/**
|
|
43
|
+
* Matches a single `mcp__` token from a frontmatter tools list. Supports:
|
|
44
|
+
* - bare server: `mcp__linear` (capture 2 undefined)
|
|
45
|
+
* - wildcard: `mcp__linear__*` (capture 2 === '*')
|
|
46
|
+
* - full: `mcp__linear__save_issue` (capture 2 is the tool name)
|
|
47
|
+
*/
|
|
48
|
+
const FRONTMATTER_MCP_TOKEN = /^mcp__([a-z][a-z0-9-]*)(?:__([a-z][a-z0-9_]*|\*))?$/;
|
|
49
|
+
/** Plausible JSON object key for an MCP server name in an `mcpServers` block */
|
|
50
|
+
const JSON_SERVER_NAME = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
51
|
+
/**
|
|
52
|
+
* Cap on `"mcpServers"` marker occurrences processed per document. Real
|
|
53
|
+
* skills have at most 1-2 legitimate registration blocks; without a cap,
|
|
54
|
+
* adversarial content with many unclosed `"mcpServers": {` occurrences would
|
|
55
|
+
* each trigger an O(remaining-document-length) brace scan in
|
|
56
|
+
* {@link findMatchingBrace} — O(n^2) worst case even though the 100KB input
|
|
57
|
+
* cap already bounds n (SMI-5676 review finding).
|
|
58
|
+
*/
|
|
59
|
+
const MAX_MCP_SERVERS_MARKERS = 20;
|
|
19
60
|
/**
|
|
20
61
|
* Extract all MCP tool references from skill content.
|
|
21
62
|
*
|
|
22
63
|
* Scans each line for `mcp__server__tool` patterns, tracking fenced
|
|
23
64
|
* code block state to distinguish high-confidence (prose) references
|
|
24
|
-
* from low-confidence (code example) references.
|
|
65
|
+
* from low-confidence (code example) references. Also parses frontmatter
|
|
66
|
+
* `allowed-tools`/`tools` fields and embedded `mcpServers` JSON blocks
|
|
67
|
+
* (SMI-5676).
|
|
25
68
|
*
|
|
26
69
|
* @param content - Raw SKILL.md content (markdown)
|
|
70
|
+
* @param registeredServers - Optional list of MCP server names actually
|
|
71
|
+
* registered in the consuming project's `.mcp.json`. When provided
|
|
72
|
+
* (even as `[]`), every candidate server name is tagged `registered` or
|
|
73
|
+
* `unregistered` in the result's `serverResolutions` map — never filtered
|
|
74
|
+
* out. Omit (or pass `undefined`) when the caller couldn't determine this
|
|
75
|
+
* (e.g. `.mcp.json` missing/unparseable); every name is then tagged
|
|
76
|
+
* `unknown` rather than assumed unregistered.
|
|
27
77
|
* @returns Extraction result with references, servers, and confidence info
|
|
28
78
|
*/
|
|
29
|
-
export function extractMcpReferences(content) {
|
|
79
|
+
export function extractMcpReferences(content, registeredServers) {
|
|
30
80
|
let truncated;
|
|
31
81
|
// Cap input at 100KB
|
|
32
82
|
if (new TextEncoder().encode(content).byteLength > MAX_INPUT_BYTES) {
|
|
@@ -37,12 +87,21 @@ export function extractMcpReferences(content) {
|
|
|
37
87
|
const references = [];
|
|
38
88
|
const serverSet = new Set();
|
|
39
89
|
const highConfidenceSet = new Set();
|
|
90
|
+
/** Per-line fenced-code-block state, reused by the mcpServers JSON pass below */
|
|
91
|
+
const lineInCodeBlock = [];
|
|
92
|
+
// SMI-5676 review fix: the frontmatter block (open delimiter, YAML body,
|
|
93
|
+
// close delimiter) is excluded from the base inline-regex scan below, so a
|
|
94
|
+
// full-form frontmatter entry like `- mcp__linear__save_issue` is only
|
|
95
|
+
// ever counted once (by extractFrontmatterMcpRefs), not twice.
|
|
96
|
+
const frontmatterBlock = extractFrontmatterBlock(lines);
|
|
97
|
+
const frontmatterEndLine = frontmatterBlock ? frontmatterBlock.endLine : 0;
|
|
40
98
|
let inCodeBlock = false;
|
|
41
99
|
let fenceChar = null;
|
|
42
100
|
let fenceLength = 0;
|
|
43
101
|
for (let i = 0; i < lines.length; i++) {
|
|
44
102
|
const line = lines[i];
|
|
45
103
|
const lineNumber = i + 1;
|
|
104
|
+
const inFrontmatter = lineNumber <= frontmatterEndLine;
|
|
46
105
|
// Check for fence toggle
|
|
47
106
|
const fenceMatch = FENCE_PATTERN.exec(line);
|
|
48
107
|
if (fenceMatch) {
|
|
@@ -60,33 +119,230 @@ export function extractMcpReferences(content) {
|
|
|
60
119
|
fenceLength = 0;
|
|
61
120
|
}
|
|
62
121
|
}
|
|
63
|
-
// Find all MCP references on this line
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
// Find all MCP references on this line (skip the frontmatter block —
|
|
123
|
+
// handled separately below, so full-form entries aren't double-counted)
|
|
124
|
+
if (!inFrontmatter) {
|
|
125
|
+
let match;
|
|
126
|
+
// Reset lastIndex for each line since we reuse the global regex
|
|
127
|
+
MCP_PATTERN.lastIndex = 0;
|
|
128
|
+
while ((match = MCP_PATTERN.exec(line)) !== null) {
|
|
129
|
+
const server = match[1];
|
|
130
|
+
const tool = match[2];
|
|
131
|
+
references.push({
|
|
132
|
+
server,
|
|
133
|
+
tool,
|
|
134
|
+
line: lineNumber,
|
|
135
|
+
inCodeBlock,
|
|
136
|
+
});
|
|
137
|
+
serverSet.add(server);
|
|
138
|
+
if (!inCodeBlock) {
|
|
139
|
+
highConfidenceSet.add(server);
|
|
140
|
+
}
|
|
79
141
|
}
|
|
80
142
|
}
|
|
143
|
+
lineInCodeBlock.push(inCodeBlock);
|
|
144
|
+
}
|
|
145
|
+
// SMI-5676: frontmatter allowed-tools/tools YAML fields (bare-server +
|
|
146
|
+
// wildcard forms). Always high-confidence — an explicit declaration, not a
|
|
147
|
+
// prose/code example.
|
|
148
|
+
for (const ref of extractFrontmatterMcpRefs(frontmatterBlock)) {
|
|
149
|
+
references.push({ server: ref.server, tool: ref.tool, line: ref.line, inCodeBlock: false });
|
|
150
|
+
serverSet.add(ref.server);
|
|
151
|
+
highConfidenceSet.add(ref.server);
|
|
152
|
+
}
|
|
153
|
+
// SMI-5676: embedded mcpServers JSON registration blocks. Confidence
|
|
154
|
+
// follows the same fenced-code-block state as inline references, since
|
|
155
|
+
// these blocks are themselves almost always fenced JSON examples.
|
|
156
|
+
for (const ref of extractMcpServersJsonRefs(content)) {
|
|
157
|
+
const refInCodeBlock = lineInCodeBlock[ref.line - 1] ?? false;
|
|
158
|
+
references.push({ server: ref.server, tool: '*', line: ref.line, inCodeBlock: refInCodeBlock });
|
|
159
|
+
serverSet.add(ref.server);
|
|
160
|
+
if (!refInCodeBlock) {
|
|
161
|
+
highConfidenceSet.add(ref.server);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Keep a stable, file-order-ish `references` array across the three
|
|
165
|
+
// detection passes above (each pass is internally in-order already;
|
|
166
|
+
// Array#sort is stable in Node, so same-line entries keep their order).
|
|
167
|
+
references.sort((a, b) => a.line - b.line);
|
|
168
|
+
// SMI-5676: cross-check every candidate against the consuming project's
|
|
169
|
+
// .mcp.json — tag, never exclude (see McpServerResolution doc).
|
|
170
|
+
const serverResolutions = {};
|
|
171
|
+
for (const server of serverSet) {
|
|
172
|
+
serverResolutions[server] =
|
|
173
|
+
registeredServers === undefined
|
|
174
|
+
? 'unknown'
|
|
175
|
+
: registeredServers.includes(server)
|
|
176
|
+
? 'registered'
|
|
177
|
+
: 'unregistered';
|
|
81
178
|
}
|
|
82
179
|
const result = {
|
|
83
180
|
references,
|
|
84
181
|
servers: [...serverSet].sort(),
|
|
85
182
|
highConfidenceServers: [...highConfidenceSet].sort(),
|
|
183
|
+
serverResolutions,
|
|
86
184
|
};
|
|
87
185
|
if (truncated) {
|
|
88
186
|
result.truncated = true;
|
|
89
187
|
}
|
|
90
188
|
return result;
|
|
91
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Locate the frontmatter block (between the first two `---` delimiter
|
|
192
|
+
* lines) and return its YAML lines, the absolute file line number of the
|
|
193
|
+
* first YAML content line, and the absolute line number of the closing
|
|
194
|
+
* delimiter (used by callers to exclude the whole block from other line
|
|
195
|
+
* scans). Returns null if there's no well-formed frontmatter (no delimiters
|
|
196
|
+
* at all, or an unterminated block) — fails open.
|
|
197
|
+
*/
|
|
198
|
+
function extractFrontmatterBlock(lines) {
|
|
199
|
+
if (lines.length === 0 || !FRONTMATTER_DELIMITER.test(lines[0]))
|
|
200
|
+
return null;
|
|
201
|
+
for (let i = 1; i < lines.length; i++) {
|
|
202
|
+
if (FRONTMATTER_DELIMITER.test(lines[i])) {
|
|
203
|
+
return { yamlLines: lines.slice(1, i), startLine: 2, endLine: i + 1 };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Parse the frontmatter's `allowed-tools`/`tools` fields (structurally, via
|
|
210
|
+
* the `yaml` package — already a `@skillsmith/core` dependency, used the
|
|
211
|
+
* same way in `agent-config-merge.yaml.ts`) and pull out any `mcp__*`
|
|
212
|
+
* tokens, in whichever YAML shape the author used: block list (`- item`),
|
|
213
|
+
* flow list (`[item, item]`), or a single bare scalar. Fails open (returns
|
|
214
|
+
* `[]`) on missing frontmatter, missing fields, or a YAML parse error.
|
|
215
|
+
*
|
|
216
|
+
* @param block - The already-located frontmatter block (or null if the
|
|
217
|
+
* content has none), from {@link extractFrontmatterBlock} — computed once
|
|
218
|
+
* by the caller and shared with the base inline-regex scan's exclusion
|
|
219
|
+
* range, rather than re-derived here.
|
|
220
|
+
*/
|
|
221
|
+
function extractFrontmatterMcpRefs(block) {
|
|
222
|
+
if (!block || block.yamlLines.length === 0)
|
|
223
|
+
return [];
|
|
224
|
+
const yamlSource = block.yamlLines.join('\n');
|
|
225
|
+
const lineCounter = new LineCounter();
|
|
226
|
+
let doc;
|
|
227
|
+
try {
|
|
228
|
+
doc = parseDocument(yamlSource, { lineCounter });
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
if (doc.errors.length > 0)
|
|
234
|
+
return [];
|
|
235
|
+
const refs = [];
|
|
236
|
+
for (const field of TOOL_LIST_FIELDS) {
|
|
237
|
+
let node;
|
|
238
|
+
try {
|
|
239
|
+
node = doc.get(field, true);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (node === undefined || node === null)
|
|
245
|
+
continue;
|
|
246
|
+
const scalarNodes = isSeq(node) ? node.items : isScalar(node) ? [node] : [];
|
|
247
|
+
for (const item of scalarNodes) {
|
|
248
|
+
if (!isScalar(item) || typeof item.value !== 'string')
|
|
249
|
+
continue;
|
|
250
|
+
const tokenMatch = FRONTMATTER_MCP_TOKEN.exec(item.value.trim());
|
|
251
|
+
if (!tokenMatch)
|
|
252
|
+
continue;
|
|
253
|
+
const server = tokenMatch[1];
|
|
254
|
+
const tool = tokenMatch[2] && tokenMatch[2] !== '*' ? tokenMatch[2] : '*';
|
|
255
|
+
const range = item.range;
|
|
256
|
+
const relativeLine = range ? lineCounter.linePos(range[0]).line : 1;
|
|
257
|
+
refs.push({ server, tool, line: block.startLine + relativeLine - 1 });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return refs;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Find the index of the `}` that closes the `{` at `openIdx`, respecting
|
|
264
|
+
* JSON string literals (so braces inside string values don't miscount).
|
|
265
|
+
* Returns -1 if unterminated.
|
|
266
|
+
*/
|
|
267
|
+
function findMatchingBrace(text, openIdx) {
|
|
268
|
+
let depth = 0;
|
|
269
|
+
let inString = false;
|
|
270
|
+
let escapeNext = false;
|
|
271
|
+
for (let i = openIdx; i < text.length; i++) {
|
|
272
|
+
const ch = text[i];
|
|
273
|
+
if (escapeNext) {
|
|
274
|
+
escapeNext = false;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (inString) {
|
|
278
|
+
if (ch === '\\')
|
|
279
|
+
escapeNext = true;
|
|
280
|
+
else if (ch === '"')
|
|
281
|
+
inString = false;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (ch === '"') {
|
|
285
|
+
inString = true;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (ch === '{') {
|
|
289
|
+
depth++;
|
|
290
|
+
}
|
|
291
|
+
else if (ch === '}') {
|
|
292
|
+
depth--;
|
|
293
|
+
if (depth === 0)
|
|
294
|
+
return i;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return -1;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Detect `"mcpServers": { ... }` JSON registration blocks embedded anywhere
|
|
301
|
+
* in the content (typically inside a fenced code example showing how to add
|
|
302
|
+
* the skill's server to `.mcp.json`/`settings.json`). Each top-level key of
|
|
303
|
+
* the object is a candidate server name. Fails open (skips) on invalid JSON
|
|
304
|
+
* rather than throwing.
|
|
305
|
+
*/
|
|
306
|
+
function extractMcpServersJsonRefs(content) {
|
|
307
|
+
const refs = [];
|
|
308
|
+
const marker = '"mcpServers"';
|
|
309
|
+
let fromIdx = 0;
|
|
310
|
+
let markersProcessed = 0;
|
|
311
|
+
while (markersProcessed < MAX_MCP_SERVERS_MARKERS) {
|
|
312
|
+
const markerIdx = content.indexOf(marker, fromIdx);
|
|
313
|
+
if (markerIdx === -1)
|
|
314
|
+
break;
|
|
315
|
+
fromIdx = markerIdx + marker.length;
|
|
316
|
+
markersProcessed++;
|
|
317
|
+
let i = markerIdx + marker.length;
|
|
318
|
+
while (i < content.length && /\s/.test(content[i]))
|
|
319
|
+
i++;
|
|
320
|
+
if (content[i] !== ':')
|
|
321
|
+
continue;
|
|
322
|
+
i++;
|
|
323
|
+
while (i < content.length && /\s/.test(content[i]))
|
|
324
|
+
i++;
|
|
325
|
+
if (content[i] !== '{')
|
|
326
|
+
continue;
|
|
327
|
+
const closeIdx = findMatchingBrace(content, i);
|
|
328
|
+
if (closeIdx === -1)
|
|
329
|
+
continue;
|
|
330
|
+
let parsed;
|
|
331
|
+
try {
|
|
332
|
+
parsed = JSON.parse(content.slice(i, closeIdx + 1));
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
338
|
+
const lineNumber = content.slice(0, markerIdx).split('\n').length;
|
|
339
|
+
for (const server of Object.keys(parsed)) {
|
|
340
|
+
if (JSON_SERVER_NAME.test(server)) {
|
|
341
|
+
refs.push({ server, line: lineNumber });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return refs;
|
|
347
|
+
}
|
|
92
348
|
//# sourceMappingURL=McpReferenceExtractor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpReferenceExtractor.js","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"McpReferenceExtractor.js","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -259,4 +259,8 @@ describe('McpReferenceExtractor', () => {
|
|
|
259
259
|
});
|
|
260
260
|
});
|
|
261
261
|
});
|
|
262
|
+
// SMI-5676 extraction-hardening tests (frontmatter parsing, mcpServers
|
|
263
|
+
// JSON-block detection, .mcp.json cross-check tagging) live in the sibling
|
|
264
|
+
// McpReferenceExtractor.hardening.test.ts, split out to stay under the
|
|
265
|
+
// 500-line file gate.
|
|
262
266
|
//# sourceMappingURL=McpReferenceExtractor.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpReferenceExtractor.test.js","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpReferenceExtractor.test.js","sourceRoot":"","sources":["../../../src/analysis/McpReferenceExtractor.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic config-file write primitives.
|
|
3
|
+
* @module @skillsmith/core/config/config-atomic-write
|
|
4
|
+
*
|
|
5
|
+
* SMI-5531: `saveConfig` (./index.ts) previously did an unguarded
|
|
6
|
+
* read-then-write (`loadConfig()` then a bare `writeFileSync()`), which has
|
|
7
|
+
* two distinct failure modes under concurrent writers (two CLI/MCP-server
|
|
8
|
+
* processes touching `~/.skillsmith/config.json` around the same time —
|
|
9
|
+
* e.g. one setting `apiKey`, another calling `getOrCreateInstallId()`):
|
|
10
|
+
*
|
|
11
|
+
* 1. Lost update / dropped sibling key: process A and process B both read
|
|
12
|
+
* the same pre-write snapshot, each merges its OWN change on top, then
|
|
13
|
+
* writes; whichever writes last wins, and its snapshot does not include
|
|
14
|
+
* the other's change — that key is silently dropped.
|
|
15
|
+
* 2. Torn write: a bare `writeFileSync` is not atomic with respect to a
|
|
16
|
+
* concurrent reader on every platform/filesystem; a reader could in
|
|
17
|
+
* principle observe a partially-written file.
|
|
18
|
+
*
|
|
19
|
+
* This module fixes both:
|
|
20
|
+
* - {@link acquireConfigLock} serializes ALL writers (cross-process, via a
|
|
21
|
+
* create-exclusive `.lock` sentinel file) so a caller's full
|
|
22
|
+
* read-modify-write sequence is one atomic critical section.
|
|
23
|
+
* - {@link atomicWriteFile} writes to a temp file in the same directory,
|
|
24
|
+
* then `renameSync`s it into place — POSIX guarantees `rename(2)` is
|
|
25
|
+
* atomic, so a concurrent reader (which never needs the lock — only
|
|
26
|
+
* writers do) always observes either the fully-old or fully-new file,
|
|
27
|
+
* never a torn write.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Acquire an exclusive, cross-process lock guarding `configPath`.
|
|
31
|
+
*
|
|
32
|
+
* Uses atomic create-exclusive (the `wx` flag — fails with `EEXIST` if the
|
|
33
|
+
* lock file already exists) as the mutual-exclusion primitive: portable
|
|
34
|
+
* (identical behavior on macOS/Linux/Windows) and needs no extra
|
|
35
|
+
* dependency. Retries with a short backoff until `LOCK_ACQUIRE_TIMEOUT_MS`
|
|
36
|
+
* elapses. If the held lock is older than `STALE_LOCK_AGE_MS` it is assumed
|
|
37
|
+
* abandoned by a crashed process and force-cleared once, then retried.
|
|
38
|
+
*
|
|
39
|
+
* @param configPath - Path to the config file being guarded (NOT the lock
|
|
40
|
+
* file itself — the lock file is `${configPath}.lock`).
|
|
41
|
+
* @param timeoutMs - Override the acquire timeout (test seam; production
|
|
42
|
+
* callers should omit this and use the default).
|
|
43
|
+
* @returns A release function. Callers MUST call it exactly once, in a
|
|
44
|
+
* `finally` block, so the lock is released even if the guarded work throws.
|
|
45
|
+
*/
|
|
46
|
+
export declare function acquireConfigLock(configPath: string, timeoutMs?: number): () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Write `content` to `filePath` atomically: write to a temp file in the same
|
|
49
|
+
* directory, then `renameSync` into place. A concurrent reader (which never
|
|
50
|
+
* needs to hold the config lock — only writers do) always observes either
|
|
51
|
+
* the fully-old or fully-new file content, never a torn write.
|
|
52
|
+
*
|
|
53
|
+
* The caller is responsible for holding {@link acquireConfigLock} for the
|
|
54
|
+
* duration of the read-modify-write this write is part of; this function
|
|
55
|
+
* alone only makes the WRITE step atomic, not the whole sequence around it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function atomicWriteFile(filePath: string, content: string, mode: number): void;
|
|
58
|
+
//# sourceMappingURL=config-atomic-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-atomic-write.d.ts","sourceRoot":"","sources":["../../../src/config/config-atomic-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA2CH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAAgC,GAC1C,MAAM,IAAI,CAmDZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAarF"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic config-file write primitives.
|
|
3
|
+
* @module @skillsmith/core/config/config-atomic-write
|
|
4
|
+
*
|
|
5
|
+
* SMI-5531: `saveConfig` (./index.ts) previously did an unguarded
|
|
6
|
+
* read-then-write (`loadConfig()` then a bare `writeFileSync()`), which has
|
|
7
|
+
* two distinct failure modes under concurrent writers (two CLI/MCP-server
|
|
8
|
+
* processes touching `~/.skillsmith/config.json` around the same time —
|
|
9
|
+
* e.g. one setting `apiKey`, another calling `getOrCreateInstallId()`):
|
|
10
|
+
*
|
|
11
|
+
* 1. Lost update / dropped sibling key: process A and process B both read
|
|
12
|
+
* the same pre-write snapshot, each merges its OWN change on top, then
|
|
13
|
+
* writes; whichever writes last wins, and its snapshot does not include
|
|
14
|
+
* the other's change — that key is silently dropped.
|
|
15
|
+
* 2. Torn write: a bare `writeFileSync` is not atomic with respect to a
|
|
16
|
+
* concurrent reader on every platform/filesystem; a reader could in
|
|
17
|
+
* principle observe a partially-written file.
|
|
18
|
+
*
|
|
19
|
+
* This module fixes both:
|
|
20
|
+
* - {@link acquireConfigLock} serializes ALL writers (cross-process, via a
|
|
21
|
+
* create-exclusive `.lock` sentinel file) so a caller's full
|
|
22
|
+
* read-modify-write sequence is one atomic critical section.
|
|
23
|
+
* - {@link atomicWriteFile} writes to a temp file in the same directory,
|
|
24
|
+
* then `renameSync`s it into place — POSIX guarantees `rename(2)` is
|
|
25
|
+
* atomic, so a concurrent reader (which never needs the lock — only
|
|
26
|
+
* writers do) always observes either the fully-old or fully-new file,
|
|
27
|
+
* never a torn write.
|
|
28
|
+
*/
|
|
29
|
+
import { randomBytes } from 'node:crypto';
|
|
30
|
+
import { openSync, closeSync, unlinkSync, writeFileSync, renameSync, statSync, chmodSync, } from 'node:fs';
|
|
31
|
+
import { dirname, join } from 'node:path';
|
|
32
|
+
/**
|
|
33
|
+
* Max time to wait for a lock before giving up (ms). This guards a
|
|
34
|
+
* user-facing CLI/MCP-server startup path, so it must fail loudly with a
|
|
35
|
+
* clear error rather than hang indefinitely.
|
|
36
|
+
*/
|
|
37
|
+
const LOCK_ACQUIRE_TIMEOUT_MS = 5_000;
|
|
38
|
+
/** Backoff between lock-acquisition retries (ms). */
|
|
39
|
+
const LOCK_RETRY_DELAY_MS = 20;
|
|
40
|
+
/**
|
|
41
|
+
* A lock file older than this is assumed to be left behind by a crashed
|
|
42
|
+
* process (stale) and is force-removed. A healthy holder releases the lock
|
|
43
|
+
* within milliseconds of a local JSON read+write, so this is a generous
|
|
44
|
+
* margin, not a tight race.
|
|
45
|
+
*/
|
|
46
|
+
const STALE_LOCK_AGE_MS = 10_000;
|
|
47
|
+
/**
|
|
48
|
+
* Synchronously block the calling thread for `ms` milliseconds without
|
|
49
|
+
* spinning the CPU. `Atomics.wait` on a throwaway `SharedArrayBuffer` is the
|
|
50
|
+
* standard synchronous-sleep primitive in Node (unlike browsers, Node does
|
|
51
|
+
* not forbid calling it on the main thread).
|
|
52
|
+
*/
|
|
53
|
+
function sleepSync(ms) {
|
|
54
|
+
const view = new Int32Array(new SharedArrayBuffer(4));
|
|
55
|
+
Atomics.wait(view, 0, 0, ms);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Acquire an exclusive, cross-process lock guarding `configPath`.
|
|
59
|
+
*
|
|
60
|
+
* Uses atomic create-exclusive (the `wx` flag — fails with `EEXIST` if the
|
|
61
|
+
* lock file already exists) as the mutual-exclusion primitive: portable
|
|
62
|
+
* (identical behavior on macOS/Linux/Windows) and needs no extra
|
|
63
|
+
* dependency. Retries with a short backoff until `LOCK_ACQUIRE_TIMEOUT_MS`
|
|
64
|
+
* elapses. If the held lock is older than `STALE_LOCK_AGE_MS` it is assumed
|
|
65
|
+
* abandoned by a crashed process and force-cleared once, then retried.
|
|
66
|
+
*
|
|
67
|
+
* @param configPath - Path to the config file being guarded (NOT the lock
|
|
68
|
+
* file itself — the lock file is `${configPath}.lock`).
|
|
69
|
+
* @param timeoutMs - Override the acquire timeout (test seam; production
|
|
70
|
+
* callers should omit this and use the default).
|
|
71
|
+
* @returns A release function. Callers MUST call it exactly once, in a
|
|
72
|
+
* `finally` block, so the lock is released even if the guarded work throws.
|
|
73
|
+
*/
|
|
74
|
+
export function acquireConfigLock(configPath, timeoutMs = LOCK_ACQUIRE_TIMEOUT_MS) {
|
|
75
|
+
const lockPath = `${configPath}.lock`;
|
|
76
|
+
const deadline = Date.now() + timeoutMs;
|
|
77
|
+
let staleClearAttempted = false;
|
|
78
|
+
for (;;) {
|
|
79
|
+
try {
|
|
80
|
+
const fd = openSync(lockPath, 'wx', 0o600);
|
|
81
|
+
try {
|
|
82
|
+
writeFileSync(fd, String(process.pid));
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
closeSync(fd);
|
|
86
|
+
}
|
|
87
|
+
return () => {
|
|
88
|
+
try {
|
|
89
|
+
unlinkSync(lockPath);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Already gone. Releasing an already-released lock is a no-op,
|
|
93
|
+
// not an error (defensive — should not happen in practice since
|
|
94
|
+
// each acquire pairs with exactly one release).
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err.code !== 'EEXIST')
|
|
100
|
+
throw err;
|
|
101
|
+
if (!staleClearAttempted) {
|
|
102
|
+
staleClearAttempted = true;
|
|
103
|
+
try {
|
|
104
|
+
const age = Date.now() - statSync(lockPath).mtimeMs;
|
|
105
|
+
if (age > STALE_LOCK_AGE_MS) {
|
|
106
|
+
unlinkSync(lockPath);
|
|
107
|
+
continue; // retry immediately — don't burn the backoff budget on this attempt
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Lock file vanished between the failed open and this stat (the
|
|
112
|
+
// holder released it concurrently, or another waiter already
|
|
113
|
+
// cleared it) — just retry the open below.
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (Date.now() >= deadline) {
|
|
118
|
+
throw new Error(`[skillsmith] Timed out waiting for config lock at ${lockPath} after ` +
|
|
119
|
+
`${timeoutMs}ms. If this persists, a crashed process may have left a ` +
|
|
120
|
+
`stale lock — verify no other skillsmith process is running, then remove ${lockPath} manually.`);
|
|
121
|
+
}
|
|
122
|
+
sleepSync(LOCK_RETRY_DELAY_MS);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Write `content` to `filePath` atomically: write to a temp file in the same
|
|
128
|
+
* directory, then `renameSync` into place. A concurrent reader (which never
|
|
129
|
+
* needs to hold the config lock — only writers do) always observes either
|
|
130
|
+
* the fully-old or fully-new file content, never a torn write.
|
|
131
|
+
*
|
|
132
|
+
* The caller is responsible for holding {@link acquireConfigLock} for the
|
|
133
|
+
* duration of the read-modify-write this write is part of; this function
|
|
134
|
+
* alone only makes the WRITE step atomic, not the whole sequence around it.
|
|
135
|
+
*/
|
|
136
|
+
export function atomicWriteFile(filePath, content, mode) {
|
|
137
|
+
const dir = dirname(filePath);
|
|
138
|
+
const tmpPath = join(dir, `.${randomBytes(6).toString('hex')}.tmp`);
|
|
139
|
+
writeFileSync(tmpPath, content, { encoding: 'utf-8', mode });
|
|
140
|
+
renameSync(tmpPath, filePath);
|
|
141
|
+
// rename() preserves the temp file's mode bits on POSIX, but re-assert
|
|
142
|
+
// explicitly — belt-and-braces, mirroring saveConfig's prior behavior, and
|
|
143
|
+
// a no-op cost on the common path.
|
|
144
|
+
try {
|
|
145
|
+
chmodSync(filePath, mode);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Ignore chmod errors on Windows.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=config-atomic-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-atomic-write.js","sourceRoot":"","sources":["../../../src/config/config-atomic-write.ts"],"names":[],"mappings":"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"}
|