skill-checker 0.1.6 → 0.1.8
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/README.md +12 -22
- package/dist/cli.js +96 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.js +89 -31
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -160,14 +160,23 @@ function enumerateFiles(dirPath, warnings) {
|
|
|
160
160
|
content = readFileSync(fullPath, "utf-8");
|
|
161
161
|
} catch {
|
|
162
162
|
}
|
|
163
|
-
} else
|
|
163
|
+
} else {
|
|
164
164
|
let fd;
|
|
165
165
|
try {
|
|
166
166
|
fd = openSync(fullPath, "r");
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
const headBuf = Buffer.alloc(PARTIAL_READ_LIMIT);
|
|
168
|
+
const headBytesRead = readSync(fd, headBuf, 0, PARTIAL_READ_LIMIT, 0);
|
|
169
|
+
const headContent = headBuf.slice(0, headBytesRead).toString("utf-8");
|
|
170
|
+
const tailOffset = Math.max(0, lstats.size - PARTIAL_READ_LIMIT);
|
|
171
|
+
const tailBuf = Buffer.alloc(PARTIAL_READ_LIMIT);
|
|
172
|
+
const tailBytesRead = readSync(fd, tailBuf, 0, PARTIAL_READ_LIMIT, tailOffset);
|
|
173
|
+
const tailContent = tailBuf.slice(0, tailBytesRead).toString("utf-8");
|
|
174
|
+
content = tailOffset > 0 ? `${headContent}
|
|
175
|
+
/* ... window gap ... */
|
|
176
|
+
${tailContent}` : headContent;
|
|
177
|
+
warnings.push(
|
|
178
|
+
`Large file window-scanned (head+tail ${PARTIAL_READ_LIMIT} bytes each): ${relativePath} (${lstats.size} bytes total)`
|
|
179
|
+
);
|
|
171
180
|
} catch {
|
|
172
181
|
warnings.push(`Large file could not be read: ${relativePath} (${lstats.size} bytes)`);
|
|
173
182
|
} finally {
|
|
@@ -178,8 +187,6 @@ function enumerateFiles(dirPath, warnings) {
|
|
|
178
187
|
}
|
|
179
188
|
}
|
|
180
189
|
}
|
|
181
|
-
} else {
|
|
182
|
-
warnings.push(`File too large to scan: ${relativePath} (${lstats.size} bytes). Content not checked.`);
|
|
183
190
|
}
|
|
184
191
|
}
|
|
185
192
|
files.push({
|
|
@@ -199,15 +206,12 @@ function enumerateFiles(dirPath, warnings) {
|
|
|
199
206
|
// src/checks/structural.ts
|
|
200
207
|
var HYPHEN_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
201
208
|
var MAX_NAME_LENGTH = 64;
|
|
202
|
-
var
|
|
203
|
-
".exe",
|
|
204
|
-
".bat",
|
|
205
|
-
".cmd",
|
|
209
|
+
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
206
210
|
".sh",
|
|
207
211
|
".bash",
|
|
208
212
|
".ps1",
|
|
209
|
-
".
|
|
210
|
-
".
|
|
213
|
+
".bat",
|
|
214
|
+
".cmd"
|
|
211
215
|
]);
|
|
212
216
|
var BINARY_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
213
217
|
".exe",
|
|
@@ -219,6 +223,10 @@ var BINARY_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
|
219
223
|
".class",
|
|
220
224
|
".pyc"
|
|
221
225
|
]);
|
|
226
|
+
var INSTALLER_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
227
|
+
".com",
|
|
228
|
+
".msi"
|
|
229
|
+
]);
|
|
222
230
|
var structuralChecks = {
|
|
223
231
|
name: "Structural Validity",
|
|
224
232
|
category: "STRUCT",
|
|
@@ -272,7 +280,7 @@ var structuralChecks = {
|
|
|
272
280
|
}
|
|
273
281
|
for (const file of skill.files) {
|
|
274
282
|
const ext = file.extension.toLowerCase();
|
|
275
|
-
if (BINARY_EXTENSIONS2.has(ext) ||
|
|
283
|
+
if (BINARY_EXTENSIONS2.has(ext) || INSTALLER_EXTENSIONS.has(ext)) {
|
|
276
284
|
results.push({
|
|
277
285
|
id: "STRUCT-006",
|
|
278
286
|
category: "STRUCT",
|
|
@@ -281,6 +289,15 @@ var structuralChecks = {
|
|
|
281
289
|
message: `Found unexpected file: ${file.path} (${ext})`,
|
|
282
290
|
source: file.path
|
|
283
291
|
});
|
|
292
|
+
} else if (SCRIPT_EXTENSIONS.has(ext)) {
|
|
293
|
+
results.push({
|
|
294
|
+
id: "STRUCT-006",
|
|
295
|
+
category: "STRUCT",
|
|
296
|
+
severity: "LOW",
|
|
297
|
+
title: "Script file present",
|
|
298
|
+
message: `Found script file: ${file.path} (${ext}). Content is scanned separately.`,
|
|
299
|
+
source: file.path
|
|
300
|
+
});
|
|
284
301
|
}
|
|
285
302
|
}
|
|
286
303
|
const name = skill.frontmatter.name;
|
|
@@ -365,6 +382,15 @@ function isInDocumentationContext(lines, lineIndex) {
|
|
|
365
382
|
}
|
|
366
383
|
return false;
|
|
367
384
|
}
|
|
385
|
+
function isNearDocumentationHeader(lines, lineIndex) {
|
|
386
|
+
for (let i = lineIndex; i >= Math.max(0, lineIndex - 15); i--) {
|
|
387
|
+
const l = lines[i];
|
|
388
|
+
if (/^#{1,4}\s+.*(install|setup|prerequisite|requirement|depend|getting\s+started|quickstart)/i.test(l)) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
368
394
|
function isLicenseFile(filePath) {
|
|
369
395
|
const name = filePath.split("/").pop()?.toUpperCase() ?? "";
|
|
370
396
|
const base = name.replace(/\.[^.]+$/, "");
|
|
@@ -1708,14 +1734,58 @@ var supplyChainChecks = {
|
|
|
1708
1734
|
if (NPM_INSTALL_PATTERN.test(line) || PIP_INSTALL_PATTERN.test(line)) {
|
|
1709
1735
|
const allLines = getAllLines(skill);
|
|
1710
1736
|
const globalIdx = findGlobalLineIndex(allLines, source, lineNum);
|
|
1711
|
-
const isDoc = globalIdx >= 0 && isInDocumentationContext(
|
|
1737
|
+
const isDoc = source === "SKILL.md" && globalIdx >= 0 && isInDocumentationContext(
|
|
1712
1738
|
allLines.map((l) => l.line),
|
|
1713
1739
|
globalIdx
|
|
1714
1740
|
);
|
|
1715
|
-
|
|
1741
|
+
const srcLines = getLinesForSource(skill, source);
|
|
1742
|
+
const localIdx = getLocalIndex(source, lineNum, skill.bodyStartLine);
|
|
1743
|
+
const inCodeBlock = localIdx >= 0 && isInCodeBlock(srcLines, localIdx);
|
|
1744
|
+
if (isDoc && !inCodeBlock) {
|
|
1745
|
+
} else {
|
|
1716
1746
|
let severity = "HIGH";
|
|
1717
1747
|
let reducedFrom;
|
|
1718
1748
|
let msgSuffix = "";
|
|
1749
|
+
if (inCodeBlock) {
|
|
1750
|
+
const isNearDoc = source === "SKILL.md" && globalIdx >= 0 && isNearDocumentationHeader(
|
|
1751
|
+
allLines.map((l) => l.line),
|
|
1752
|
+
globalIdx
|
|
1753
|
+
);
|
|
1754
|
+
if (isNearDoc) {
|
|
1755
|
+
severity = "LOW";
|
|
1756
|
+
reducedFrom = "HIGH";
|
|
1757
|
+
msgSuffix = " [reduced: in code block within documentation]";
|
|
1758
|
+
} else {
|
|
1759
|
+
const r = reduceSeverity(severity, "in code block");
|
|
1760
|
+
severity = r.severity;
|
|
1761
|
+
reducedFrom = r.reducedFrom;
|
|
1762
|
+
msgSuffix = ` ${r.annotation}`;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
results.push({
|
|
1766
|
+
id: "SUPPLY-003",
|
|
1767
|
+
category: "SUPPLY",
|
|
1768
|
+
severity,
|
|
1769
|
+
title: "Package installation command",
|
|
1770
|
+
message: `${source}:${lineNum}: Installs packages. Verify package names are legitimate.${msgSuffix}`,
|
|
1771
|
+
line: lineNum,
|
|
1772
|
+
snippet: line.trim().slice(0, 120),
|
|
1773
|
+
source,
|
|
1774
|
+
reducedFrom
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (GIT_CLONE_PATTERN.test(line)) {
|
|
1779
|
+
const allLines = getAllLines(skill);
|
|
1780
|
+
const globalIdx = findGlobalLineIndex(allLines, source, lineNum);
|
|
1781
|
+
const isDoc = source === "SKILL.md" && globalIdx >= 0 && isInDocumentationContext(
|
|
1782
|
+
allLines.map((l) => l.line),
|
|
1783
|
+
globalIdx
|
|
1784
|
+
);
|
|
1785
|
+
if (!isDoc) {
|
|
1786
|
+
let severity = "MEDIUM";
|
|
1787
|
+
let reducedFrom;
|
|
1788
|
+
let msgSuffix = "";
|
|
1719
1789
|
const srcLines = getLinesForSource(skill, source);
|
|
1720
1790
|
const localIdx = getLocalIndex(source, lineNum, skill.bodyStartLine);
|
|
1721
1791
|
if (localIdx >= 0 && isInCodeBlock(srcLines, localIdx)) {
|
|
@@ -1725,11 +1795,11 @@ var supplyChainChecks = {
|
|
|
1725
1795
|
msgSuffix = ` ${r.annotation}`;
|
|
1726
1796
|
}
|
|
1727
1797
|
results.push({
|
|
1728
|
-
id: "SUPPLY-
|
|
1798
|
+
id: "SUPPLY-006",
|
|
1729
1799
|
category: "SUPPLY",
|
|
1730
1800
|
severity,
|
|
1731
|
-
title: "
|
|
1732
|
-
message: `${source}:${lineNum}:
|
|
1801
|
+
title: "git clone command",
|
|
1802
|
+
message: `${source}:${lineNum}: Clones a git repository. Verify the source.${msgSuffix}`,
|
|
1733
1803
|
line: lineNum,
|
|
1734
1804
|
snippet: line.trim().slice(0, 120),
|
|
1735
1805
|
source,
|
|
@@ -1737,18 +1807,6 @@ var supplyChainChecks = {
|
|
|
1737
1807
|
});
|
|
1738
1808
|
}
|
|
1739
1809
|
}
|
|
1740
|
-
if (GIT_CLONE_PATTERN.test(line)) {
|
|
1741
|
-
results.push({
|
|
1742
|
-
id: "SUPPLY-006",
|
|
1743
|
-
category: "SUPPLY",
|
|
1744
|
-
severity: "MEDIUM",
|
|
1745
|
-
title: "git clone command",
|
|
1746
|
-
message: `${source}:${lineNum}: Clones a git repository. Verify the source.`,
|
|
1747
|
-
line: lineNum,
|
|
1748
|
-
snippet: line.trim().slice(0, 120),
|
|
1749
|
-
source
|
|
1750
|
-
});
|
|
1751
|
-
}
|
|
1752
1810
|
const urls = line.match(URL_PATTERN) || [];
|
|
1753
1811
|
for (const url of urls) {
|
|
1754
1812
|
if (url.startsWith("http://")) {
|