vigiles 14.6.7 → 14.8.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/adapters/claude-code/agent-runtime.d.ts +2 -19
- package/dist/adapters/claude-code/agent-runtime.js +5 -30
- package/dist/adapters/claude-code/agent-tools.d.ts +20 -0
- package/dist/adapters/claude-code/agent-tools.js +40 -0
- package/dist/audit-report.d.ts +2 -2
- package/dist/audit-report.template.html +15 -15
- package/dist/audit-score.d.ts +1 -1
- package/dist/audit-score.js +20 -20
- package/dist/audit-verdict.d.ts +2 -2
- package/dist/audit-verdict.js +5 -5
- package/dist/core/assert-never.d.ts +9 -0
- package/dist/core/assert-never.js +14 -0
- package/dist/core/description-overlap.js +2 -2
- package/dist/core/edit-distance.d.ts +12 -0
- package/dist/core/edit-distance.js +36 -0
- package/dist/core/effects.js +3 -3
- package/dist/core/hash.d.ts +1 -2
- package/dist/core/hash.js +6 -4
- package/dist/core/hook-block-ineffective.d.ts +55 -6
- package/dist/core/hook-block-ineffective.js +9 -14
- package/dist/core/hook-events.js +2 -2
- package/dist/core/linters.d.ts +2 -6
- package/dist/core/linters.js +4 -29
- package/dist/core/mcp-contract-message.d.ts +22 -0
- package/dist/core/mcp-contract-message.js +29 -0
- package/dist/core/mcp.d.ts +4 -12
- package/dist/core/mcp.js +3 -14
- package/dist/core/ncd.d.ts +12 -0
- package/dist/core/ncd.js +50 -0
- package/dist/core/plugin-dir-layout.d.ts +5 -5
- package/dist/core/plugin-dir-layout.js +10 -22
- package/dist/core/proofs.d.ts +2 -11
- package/dist/core/proofs.js +4 -39
- package/dist/core/skill-resources.d.ts +3 -3
- package/dist/core/skill-resources.js +9 -8
- package/dist/core/tool-contract.js +2 -2
- package/dist/leaderboard.d.ts +2 -51
- package/dist/leaderboard.js +20 -225
- package/dist/optimize.d.ts +1 -1
- package/dist/optimize.js +3 -3
- package/dist/posix-path.d.ts +40 -0
- package/dist/posix-path.js +293 -0
- package/dist/scan-core.d.ts +154 -0
- package/dist/scan-core.js +690 -0
- package/dist/scan-files.d.ts +28 -0
- package/dist/scan-files.js +489 -0
- package/dist/scan.d.ts +11 -34
- package/dist/scan.js +55 -668
- package/dist/score-core.d.ts +73 -0
- package/dist/score-core.js +226 -0
- package/dist/test-coverage-files.d.ts +11 -0
- package/dist/test-coverage-files.js +208 -0
- package/package.json +1 -1
package/dist/leaderboard.js
CHANGED
|
@@ -12,18 +12,30 @@
|
|
|
12
12
|
* model and stack on top later; this part runs anywhere in CI for free.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.W_TRIFECTA = exports.W_NO_CONTRACT = exports.W_OVERLAP = exports.W_DANGLING_REF = exports.W_NO_DESCRIPTION = exports.W_MISSING_HOOK = void 0;
|
|
16
|
-
exports.gradeFor = gradeFor;
|
|
17
|
-
exports.reportDeductions = reportDeductions;
|
|
18
|
-
exports.isEmptyMachine = isEmptyMachine;
|
|
19
|
-
exports.computeIntegrityScore = computeIntegrityScore;
|
|
20
|
-
exports.scoreReport = scoreReport;
|
|
15
|
+
exports.scoreReport = exports.computeIntegrityScore = exports.isEmptyMachine = exports.reportDeductions = exports.gradeFor = exports.W_TRIFECTA = exports.W_NO_CONTRACT = exports.W_OVERLAP = exports.W_DANGLING_REF = exports.W_NO_DESCRIPTION = exports.W_MISSING_HOOK = void 0;
|
|
21
16
|
exports.rankPlugins = rankPlugins;
|
|
22
17
|
exports.formatLeaderboard = formatLeaderboard;
|
|
23
18
|
exports.formatLeaderboardMarkdown = formatLeaderboardMarkdown;
|
|
24
19
|
const node_fs_1 = require("node:fs");
|
|
25
20
|
const node_path_1 = require("node:path");
|
|
26
21
|
const scan_js_1 = require("./scan.js");
|
|
22
|
+
// The pure, node-free scoring core lives in ./score-core.js (extracted so the
|
|
23
|
+
// in-browser audit's report builder can import scoring WITHOUT this module's
|
|
24
|
+
// node-only scanPlugin/pluginLabel → scan.ts → @ast-grep/napi chain). Re-exported
|
|
25
|
+
// here so every existing `from "./leaderboard.js"` consumer keeps working.
|
|
26
|
+
const score_core_js_1 = require("./score-core.js");
|
|
27
|
+
var score_core_js_2 = require("./score-core.js");
|
|
28
|
+
Object.defineProperty(exports, "W_MISSING_HOOK", { enumerable: true, get: function () { return score_core_js_2.W_MISSING_HOOK; } });
|
|
29
|
+
Object.defineProperty(exports, "W_NO_DESCRIPTION", { enumerable: true, get: function () { return score_core_js_2.W_NO_DESCRIPTION; } });
|
|
30
|
+
Object.defineProperty(exports, "W_DANGLING_REF", { enumerable: true, get: function () { return score_core_js_2.W_DANGLING_REF; } });
|
|
31
|
+
Object.defineProperty(exports, "W_OVERLAP", { enumerable: true, get: function () { return score_core_js_2.W_OVERLAP; } });
|
|
32
|
+
Object.defineProperty(exports, "W_NO_CONTRACT", { enumerable: true, get: function () { return score_core_js_2.W_NO_CONTRACT; } });
|
|
33
|
+
Object.defineProperty(exports, "W_TRIFECTA", { enumerable: true, get: function () { return score_core_js_2.W_TRIFECTA; } });
|
|
34
|
+
Object.defineProperty(exports, "gradeFor", { enumerable: true, get: function () { return score_core_js_2.gradeFor; } });
|
|
35
|
+
Object.defineProperty(exports, "reportDeductions", { enumerable: true, get: function () { return score_core_js_2.reportDeductions; } });
|
|
36
|
+
Object.defineProperty(exports, "isEmptyMachine", { enumerable: true, get: function () { return score_core_js_2.isEmptyMachine; } });
|
|
37
|
+
Object.defineProperty(exports, "computeIntegrityScore", { enumerable: true, get: function () { return score_core_js_2.computeIntegrityScore; } });
|
|
38
|
+
Object.defineProperty(exports, "scoreReport", { enumerable: true, get: function () { return score_core_js_2.scoreReport; } });
|
|
27
39
|
/** The plugin's declared name (`.claude-plugin/plugin.json`), for a real label in
|
|
28
40
|
* the ranking instead of a SHA-pinned dir basename. Falls back to the basename. */
|
|
29
41
|
function pluginLabel(dir) {
|
|
@@ -41,233 +53,16 @@ function pluginLabel(dir) {
|
|
|
41
53
|
}
|
|
42
54
|
return (0, node_path_1.basename)(dir) || dir;
|
|
43
55
|
}
|
|
44
|
-
// Penalty weights — broken-at-runtime costs most, footguns less, nudges least.
|
|
45
|
-
// Exported so the category view (audit-score.ts) reuses the SAME weights and the
|
|
46
|
-
// two surfaces can never drift on a per-item cost.
|
|
47
|
-
exports.W_MISSING_HOOK = 15; // a hook script that doesn't exist → never runs
|
|
48
|
-
exports.W_NO_DESCRIPTION = 10; // a skill with no usable description → can't trigger
|
|
49
|
-
exports.W_DANGLING_REF = 8; // a referenced intra-plugin file that's missing → broken path
|
|
50
|
-
exports.W_OVERLAP = 8; // a description collision → the wrong skill fires
|
|
51
|
-
exports.W_NO_CONTRACT = 5; // generic small-footgun weight (disallowedTools typo, invalid model/color)
|
|
52
|
-
exports.W_TRIFECTA = 10; // a HARD lethal-trifecta contract (all three legs, explicit) → a prompt-injection exfil path. HALF the old 20: a DING, not a fail — a trifecta is a real risk worth surfacing in the grade, but official plugins ship the pattern by design, so it dents the score (e.g. feature-dev's 3 hard units → −30 → C) without a catastrophic F.
|
|
53
|
-
// Two things are advisory, NOT graded penalties (shown, never scored — see scoreReport):
|
|
54
|
-
// - untested surfaces — a hardening gap, not breakage.
|
|
55
|
-
// - an agent that inherits all tools (no `tools:` line) — see reportDeductions for why.
|
|
56
|
-
// - an inherits-all (severity "advisory") trifecta finding — shown by the Safety
|
|
57
|
-
// ring but never scored; only the HARD, explicit all-three-legs finding grades.
|
|
58
|
-
/** Map a 0–100 structural-health score to its letter grade (A ≥90 … F <60). */
|
|
59
|
-
function gradeFor(score) {
|
|
60
|
-
if (score >= 90)
|
|
61
|
-
return "A";
|
|
62
|
-
if (score >= 80)
|
|
63
|
-
return "B";
|
|
64
|
-
if (score >= 70)
|
|
65
|
-
return "C";
|
|
66
|
-
if (score >= 60)
|
|
67
|
-
return "D";
|
|
68
|
-
return "F";
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The COMPLETE graded-penalty list a report incurs — the single source of truth
|
|
72
|
-
* BOTH the leaderboard's single health number and the audit's category rings
|
|
73
|
-
* read, so the overall can never drift between the two surfaces. Each entry is a
|
|
74
|
-
* graded penalty; untested surfaces are deliberately ABSENT (they're advisory,
|
|
75
|
-
* surfaced separately, never scored).
|
|
76
|
-
*/
|
|
77
|
-
function reportDeductions(r) {
|
|
78
|
-
const missingHooks = r.hooks.filter((h) => h.status === "missing").length;
|
|
79
|
-
const noDesc = r.skills.filter((s) => !s.hasDescription).length;
|
|
80
|
-
const deadTools = r.agents.reduce((n, a) => n + a.toolIssues.length, 0);
|
|
81
|
-
const deadMcpTools = r.agents.reduce((n, a) => n + a.mcpToolIssues.length, 0);
|
|
82
|
-
const deadDisallowed = r.agents.reduce((n, a) => n + a.disallowedToolIssues.length, 0);
|
|
83
|
-
// HARD lethal-trifecta findings only — an EXPLICIT contract naming all three
|
|
84
|
-
// legs (a prompt-injection exfil path). Graded at W_TRIFECTA=10 (HALF the old
|
|
85
|
-
// 20): a DING that surfaces a real risk in the grade without a catastrophic F
|
|
86
|
-
// for an accepted design pattern official plugins ship. Advisory (inherits-all)
|
|
87
|
-
// trifecta findings are surfaced but NEVER graded (aligned with the inherits-all
|
|
88
|
-
// stance), so they're excluded here.
|
|
89
|
-
const hardTrifecta = r.trifectaFindings.filter((f) => f.finding.severity === "hard").length;
|
|
90
|
-
return [
|
|
91
|
-
{
|
|
92
|
-
n: hardTrifecta,
|
|
93
|
-
weight: exports.W_TRIFECTA,
|
|
94
|
-
label: "unit(s) holding all three lethal-trifecta legs (prompt-injection exfil path)",
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
n: missingHooks,
|
|
98
|
-
weight: exports.W_MISSING_HOOK,
|
|
99
|
-
label: "hook script(s) MISSING",
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
n: r.hookEventIssues.length,
|
|
103
|
-
weight: exports.W_MISSING_HOOK,
|
|
104
|
-
label: "hook(s) on an unknown event (never fire)",
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
n: noDesc,
|
|
108
|
-
weight: exports.W_NO_DESCRIPTION,
|
|
109
|
-
label: "skill(s) with no usable description",
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
n: r.descriptionOverlaps.length,
|
|
113
|
-
weight: exports.W_OVERLAP,
|
|
114
|
-
label: "near-identical skill description(s) (wrong one fires)",
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
n: r.danglingRefs.length,
|
|
118
|
-
weight: exports.W_DANGLING_REF,
|
|
119
|
-
label: "broken intra-plugin reference(s)",
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
n: deadTools,
|
|
123
|
-
weight: exports.W_DANGLING_REF,
|
|
124
|
-
label: "agent tool(s) that don't exist (typo / never-available)",
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
n: deadMcpTools,
|
|
128
|
-
weight: exports.W_DANGLING_REF,
|
|
129
|
-
label: "agent MCP tool(s) whose server isn't declared (can't resolve)",
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
n: deadDisallowed,
|
|
133
|
-
weight: exports.W_NO_CONTRACT,
|
|
134
|
-
label: "agent disallowedTools typo(s) that block nothing",
|
|
135
|
-
},
|
|
136
|
-
// NB: an agent that inherits all tools (no `tools:` line) is ADVISORY, not a
|
|
137
|
-
// graded penalty — it's surfaced by scoreReport / the Structure ring but never
|
|
138
|
-
// drags the score. WHY: omitting the `tools:` line is a near-universal,
|
|
139
|
-
// legitimate authoring style (a measured OSS sweep of 122 real plugins found
|
|
140
|
-
// 109 whose ONLY finding was this), so penalizing it makes the grade cry wolf
|
|
141
|
-
// on idiomatic subagents. A health score should mean "something is BROKEN", and
|
|
142
|
-
// a broad-by-default tool surface is a hardening/least-privilege NUDGE, not
|
|
143
|
-
// breakage. The count is re-derived where the advisory note is built.
|
|
144
|
-
{
|
|
145
|
-
n: r.frontmatterIssues.length,
|
|
146
|
-
weight: exports.W_NO_DESCRIPTION,
|
|
147
|
-
label: "surface(s) missing required frontmatter (name/description)",
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
n: r.frontmatterValueIssues.length,
|
|
151
|
-
weight: exports.W_NO_CONTRACT,
|
|
152
|
-
label: "agent(s) with an invalid model/color (typo → silent fallback)",
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
n: r.mcpIssues.length,
|
|
156
|
-
weight: exports.W_DANGLING_REF,
|
|
157
|
-
label: "MCP server(s) that can't start (no command/url)",
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
n: r.mcpHookIssues.length,
|
|
161
|
-
weight: exports.W_DANGLING_REF,
|
|
162
|
-
label: "mcp_tool hook(s) incomplete / targeting an undeclared server",
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
n: r.skillResourceIssues.length,
|
|
166
|
-
weight: exports.W_DANGLING_REF,
|
|
167
|
-
label: "skill bundled-resource ref(s) that don't resolve on disk",
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
n: r.skillFenceIssues.length,
|
|
171
|
-
weight: exports.W_NO_DESCRIPTION,
|
|
172
|
-
label: "invisible skill(s) (frontmatter with no opening `---` fence)",
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
n: r.pluginLayoutIssues.length,
|
|
176
|
-
weight: exports.W_NO_DESCRIPTION,
|
|
177
|
-
label: "functional dir(s) misplaced inside `.claude-plugin/` (invisible)",
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
n: r.hookBlockFindings.length,
|
|
181
|
-
weight: exports.W_MISSING_HOOK,
|
|
182
|
-
label: "hook(s) that look like they block but silently don't",
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
n: r.hookMatcherFindings.length,
|
|
186
|
-
weight: exports.W_MISSING_HOOK,
|
|
187
|
-
label: "hook matcher(s) that never fire (typo / wrong MCP form)",
|
|
188
|
-
},
|
|
189
|
-
// NB: delegationTrifecta (like the advisory per-unit/inherits-all trifecta) is a
|
|
190
|
-
// ⚠ RISK, surfaced but NOT graded — only the HARD per-unit trifecta above scores.
|
|
191
|
-
// NB: untested surfaces are NOT a penalty — an untested surface is a hardening
|
|
192
|
-
// gap, not breakage, so it never drags the health score (it's appended as an
|
|
193
|
-
// advisory note below). The score ranks what's BROKEN.
|
|
194
|
-
];
|
|
195
|
-
}
|
|
196
|
-
/** True when a report has no loadable plugin surface at all (the empty machine). */
|
|
197
|
-
function isEmptyMachine(r) {
|
|
198
|
-
const surfaces = r.skills.length +
|
|
199
|
-
r.agents.length +
|
|
200
|
-
r.hooks.length +
|
|
201
|
-
r.inlineHooks +
|
|
202
|
-
r.commands;
|
|
203
|
-
return surfaces === 0 && !r.mcp;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* THE shared integrity score — `100 − Σ(all graded penalties)`, clamped to
|
|
207
|
-
* [0,100]. Both the leaderboard's single health number AND the audit's headline
|
|
208
|
-
* overall read this, so the two can never disagree (the summed model is the
|
|
209
|
-
* honest one — averaging rings would dilute a real problem). Returns the score
|
|
210
|
-
* plus the per-item deductions so callers render their own issue/finding lists.
|
|
211
|
-
*/
|
|
212
|
-
function computeIntegrityScore(deductions) {
|
|
213
|
-
let penalty = 0;
|
|
214
|
-
for (const d of deductions) {
|
|
215
|
-
if (d.n <= 0)
|
|
216
|
-
continue;
|
|
217
|
-
penalty += d.n * d.weight;
|
|
218
|
-
}
|
|
219
|
-
return { score: Math.max(0, 100 - penalty), penalty };
|
|
220
|
-
}
|
|
221
|
-
/** Resolve the terse "thing(s)" plural placeholder against a count:
|
|
222
|
-
* n===1 drops the "(s)" ("1 tool"); otherwise it becomes "s" ("3 tools").
|
|
223
|
-
* (Kept local — audit-score.ts has its own copy to avoid a circular import.) */
|
|
224
|
-
function pluralizeLabel(n, label) {
|
|
225
|
-
return label.replace(/\(s\)/g, n === 1 ? "" : "s");
|
|
226
|
-
}
|
|
227
|
-
/** Deterministic structural-health score for one scanned plugin. */
|
|
228
|
-
function scoreReport(r) {
|
|
229
|
-
// An empty/unloadable machine isn't healthy — it's a non-plugin or a broken
|
|
230
|
-
// load. A command-only or MCP-only plugin (commands/*.md or .mcp.json with no
|
|
231
|
-
// skills/agents/hooks) IS a legitimate plugin, though — Anthropic ships
|
|
232
|
-
// command-only plugins in its own marketplace — so it must NOT score 0.
|
|
233
|
-
const surfaces = r.skills.length + r.agents.length + r.hooks.length + r.commands;
|
|
234
|
-
if (surfaces === 0 && !r.mcp) {
|
|
235
|
-
return { score: 0, issues: ["no loadable plugin surface"] };
|
|
236
|
-
}
|
|
237
|
-
const deductions = reportDeductions(r);
|
|
238
|
-
const { score } = computeIntegrityScore(deductions);
|
|
239
|
-
const issues = [];
|
|
240
|
-
for (const d of deductions) {
|
|
241
|
-
if (d.n === 0)
|
|
242
|
-
continue;
|
|
243
|
-
issues.push(`${String(d.n)} ${pluralizeLabel(d.n, d.label)}`);
|
|
244
|
-
}
|
|
245
|
-
// Sort issues by cost (worst first) so the report leads with what matters.
|
|
246
|
-
issues.sort((a, b) => Number(b.split(" ")[0]) - Number(a.split(" ")[0]));
|
|
247
|
-
// Advisory notes are surfaced for visibility but DON'T affect the score, so they
|
|
248
|
-
// come AFTER the real (score-affecting) issues:
|
|
249
|
-
// - inherit-all (no tool contract): a least-privilege NUDGE, not breakage —
|
|
250
|
-
// see reportDeductions for the full rationale.
|
|
251
|
-
// - untested surfaces: a hardening gap, not breakage.
|
|
252
|
-
const noContract = r.agents.filter((a) => a.tools === null).length;
|
|
253
|
-
if (noContract > 0) {
|
|
254
|
-
issues.push(`${String(noContract)} ${pluralizeLabel(noContract, "agent(s) inherit all tools (no contract) (advisory)")}`);
|
|
255
|
-
}
|
|
256
|
-
if (r.untested > 0) {
|
|
257
|
-
issues.push(`${String(r.untested)} ${pluralizeLabel(r.untested, "untested surface(s) (advisory)")}`);
|
|
258
|
-
}
|
|
259
|
-
return { score, issues };
|
|
260
|
-
}
|
|
261
56
|
/** Scan + score each directory, ranked best-first (ties broken by name). */
|
|
262
57
|
function rankPlugins(dirs) {
|
|
263
58
|
const scored = dirs.map((dir) => {
|
|
264
59
|
const report = (0, scan_js_1.scanPlugin)(dir);
|
|
265
|
-
const { score, issues } = scoreReport(report);
|
|
60
|
+
const { score, issues } = (0, score_core_js_1.scoreReport)(report);
|
|
266
61
|
return {
|
|
267
62
|
dir,
|
|
268
63
|
name: pluginLabel(dir),
|
|
269
64
|
score,
|
|
270
|
-
grade: gradeFor(score),
|
|
65
|
+
grade: (0, score_core_js_1.gradeFor)(score),
|
|
271
66
|
issues,
|
|
272
67
|
report,
|
|
273
68
|
};
|
package/dist/optimize.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* the ranked fix list + the hand-off to the measured layer. Same findings, the
|
|
27
27
|
* optimization framing. See research/measurement-authority.md (A2) + roadmap §P1.
|
|
28
28
|
*/
|
|
29
|
-
import { type PluginScore } from "./
|
|
29
|
+
import { type PluginScore } from "./score-core.js";
|
|
30
30
|
import { type ExplanationConfidence } from "./score-explainer.js";
|
|
31
31
|
import type { ScanReport } from "./scan.js";
|
|
32
32
|
/**
|
package/dist/optimize.js
CHANGED
|
@@ -31,7 +31,7 @@ exports.formatOptimize = formatOptimize;
|
|
|
31
31
|
* the ranked fix list + the hand-off to the measured layer. Same findings, the
|
|
32
32
|
* optimization framing. See research/measurement-authority.md (A2) + roadmap §P1.
|
|
33
33
|
*/
|
|
34
|
-
const
|
|
34
|
+
const score_core_js_1 = require("./score-core.js");
|
|
35
35
|
const score_explainer_js_1 = require("./score-explainer.js");
|
|
36
36
|
function actionFor(e) {
|
|
37
37
|
return e.symptom === "wrong-skill-fires" ? "differentiate" : "fix";
|
|
@@ -46,7 +46,7 @@ function isEmptyMachine(r) {
|
|
|
46
46
|
* before `possible` proxies, via explainScore's own ordering). Pure over the report.
|
|
47
47
|
*/
|
|
48
48
|
function optimize(report) {
|
|
49
|
-
const { score } = (0,
|
|
49
|
+
const { score } = (0, score_core_js_1.scoreReport)(report);
|
|
50
50
|
const recommendations = (0, score_explainer_js_1.explainScore)(report).map((e) => ({
|
|
51
51
|
surface: e.surface,
|
|
52
52
|
action: actionFor(e),
|
|
@@ -58,7 +58,7 @@ function optimize(report) {
|
|
|
58
58
|
return {
|
|
59
59
|
dir: report.dir,
|
|
60
60
|
score,
|
|
61
|
-
grade: (0,
|
|
61
|
+
grade: (0, score_core_js_1.gradeFor)(score),
|
|
62
62
|
recommendations,
|
|
63
63
|
empty: isEmptyMachine(report),
|
|
64
64
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny, node-free POSIX `path` — the browser-safe substitute for `node:path`
|
|
3
|
+
* inside the in-browser audit engine (`scan-core.ts` / `scan-files.ts` /
|
|
4
|
+
* `test-coverage-files.ts` and the core detectors they reach). A Vite bundle of
|
|
5
|
+
* that engine must not pull `node:path`, so these are pure string ops.
|
|
6
|
+
*
|
|
7
|
+
* The audit engine's file-map keys are always POSIX (`/`-separated), and the
|
|
8
|
+
* disk-side `scanPlugin` feeds it absolute POSIX roots (`resolve(dir)` on Linux),
|
|
9
|
+
* so a faithful port of Node's `path.posix` algorithm is byte-identical to
|
|
10
|
+
* `node:path` for every input the engine passes — which is exactly what the
|
|
11
|
+
* parity firewall (`scan-files.test.ts`) proves. `resolve` deliberately falls
|
|
12
|
+
* back to `/` (never `process.cwd()`) so it stays pure and process-free; every
|
|
13
|
+
* call site passes an absolute first segment, so the fallback is never reached.
|
|
14
|
+
*
|
|
15
|
+
* NOTE — the functions below are VERBATIM ports of Node's `lib/path.js` POSIX
|
|
16
|
+
* implementations (charCode scan, `normalizeString`, `basename`, `relative`).
|
|
17
|
+
* Their branch depth / cyclomatic complexity is inherent to that battle-tested
|
|
18
|
+
* algorithm; rewriting it to satisfy the complexity linters would risk a subtle
|
|
19
|
+
* behavioural divergence from `node:path` (which the disk-vs-browser parity gate
|
|
20
|
+
* relies on), so the metric rules are disabled for this file only.
|
|
21
|
+
*/
|
|
22
|
+
/** POSIX `path.isAbsolute`. */
|
|
23
|
+
export declare function isAbsolute(path: string): boolean;
|
|
24
|
+
/** POSIX `path.normalize`. */
|
|
25
|
+
export declare function normalize(path: string): string;
|
|
26
|
+
/** POSIX `path.join`. */
|
|
27
|
+
export declare function join(...parts: string[]): string;
|
|
28
|
+
/**
|
|
29
|
+
* POSIX `path.resolve`. Right-to-left until an absolute segment is found, then
|
|
30
|
+
* normalize. The `i === -1` fallback is `/` (not `process.cwd()`) so this stays
|
|
31
|
+
* pure; every engine call site passes an absolute first segment.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolve(...parts: string[]): string;
|
|
34
|
+
/** POSIX `path.dirname`. */
|
|
35
|
+
export declare function dirname(path: string): string;
|
|
36
|
+
/** POSIX `path.basename` (with an optional `suffix` to strip, like `node:path`). */
|
|
37
|
+
export declare function basename(path: string, suffix?: string): string;
|
|
38
|
+
/** POSIX `path.relative`. */
|
|
39
|
+
export declare function relative(from: string, to: string): string;
|
|
40
|
+
//# sourceMappingURL=posix-path.d.ts.map
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A tiny, node-free POSIX `path` — the browser-safe substitute for `node:path`
|
|
4
|
+
* inside the in-browser audit engine (`scan-core.ts` / `scan-files.ts` /
|
|
5
|
+
* `test-coverage-files.ts` and the core detectors they reach). A Vite bundle of
|
|
6
|
+
* that engine must not pull `node:path`, so these are pure string ops.
|
|
7
|
+
*
|
|
8
|
+
* The audit engine's file-map keys are always POSIX (`/`-separated), and the
|
|
9
|
+
* disk-side `scanPlugin` feeds it absolute POSIX roots (`resolve(dir)` on Linux),
|
|
10
|
+
* so a faithful port of Node's `path.posix` algorithm is byte-identical to
|
|
11
|
+
* `node:path` for every input the engine passes — which is exactly what the
|
|
12
|
+
* parity firewall (`scan-files.test.ts`) proves. `resolve` deliberately falls
|
|
13
|
+
* back to `/` (never `process.cwd()`) so it stays pure and process-free; every
|
|
14
|
+
* call site passes an absolute first segment, so the fallback is never reached.
|
|
15
|
+
*
|
|
16
|
+
* NOTE — the functions below are VERBATIM ports of Node's `lib/path.js` POSIX
|
|
17
|
+
* implementations (charCode scan, `normalizeString`, `basename`, `relative`).
|
|
18
|
+
* Their branch depth / cyclomatic complexity is inherent to that battle-tested
|
|
19
|
+
* algorithm; rewriting it to satisfy the complexity linters would risk a subtle
|
|
20
|
+
* behavioural divergence from `node:path` (which the disk-vs-browser parity gate
|
|
21
|
+
* relies on), so the metric rules are disabled for this file only.
|
|
22
|
+
*/
|
|
23
|
+
/* eslint-disable complexity, max-depth, sonarjs/cognitive-complexity, sonarjs/nested-control-flow */
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.isAbsolute = isAbsolute;
|
|
26
|
+
exports.normalize = normalize;
|
|
27
|
+
exports.join = join;
|
|
28
|
+
exports.resolve = resolve;
|
|
29
|
+
exports.dirname = dirname;
|
|
30
|
+
exports.basename = basename;
|
|
31
|
+
exports.relative = relative;
|
|
32
|
+
const SLASH = 47; // '/'
|
|
33
|
+
const DOT = 46; // '.'
|
|
34
|
+
/**
|
|
35
|
+
* The core POSIX normalize pass (ported from Node's `normalizeString`): collapse
|
|
36
|
+
* `.`/`..`/`//` in a slash-separated path with no leading root. `allowAboveRoot`
|
|
37
|
+
* keeps leading `..` segments (used for a relative path).
|
|
38
|
+
*/
|
|
39
|
+
function normalizeString(path, allowAboveRoot) {
|
|
40
|
+
let res = "";
|
|
41
|
+
let lastSegmentLength = 0;
|
|
42
|
+
let lastSlash = -1;
|
|
43
|
+
let dots = 0;
|
|
44
|
+
let code = 0;
|
|
45
|
+
for (let i = 0; i <= path.length; ++i) {
|
|
46
|
+
if (i < path.length)
|
|
47
|
+
code = path.charCodeAt(i);
|
|
48
|
+
else if (code === SLASH)
|
|
49
|
+
break;
|
|
50
|
+
else
|
|
51
|
+
code = SLASH;
|
|
52
|
+
if (code === SLASH) {
|
|
53
|
+
if (lastSlash === i - 1 || dots === 1) {
|
|
54
|
+
// no-op: a `//` or `.` segment
|
|
55
|
+
}
|
|
56
|
+
else if (dots === 2) {
|
|
57
|
+
if (res.length < 2 ||
|
|
58
|
+
lastSegmentLength !== 2 ||
|
|
59
|
+
res.charCodeAt(res.length - 1) !== DOT ||
|
|
60
|
+
res.charCodeAt(res.length - 2) !== DOT) {
|
|
61
|
+
if (res.length > 2) {
|
|
62
|
+
const lastSlashIndex = res.lastIndexOf("/");
|
|
63
|
+
if (lastSlashIndex === -1) {
|
|
64
|
+
res = "";
|
|
65
|
+
lastSegmentLength = 0;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
res = res.slice(0, lastSlashIndex);
|
|
69
|
+
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
|
70
|
+
}
|
|
71
|
+
lastSlash = i;
|
|
72
|
+
dots = 0;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
else if (res.length !== 0) {
|
|
76
|
+
res = "";
|
|
77
|
+
lastSegmentLength = 0;
|
|
78
|
+
lastSlash = i;
|
|
79
|
+
dots = 0;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (allowAboveRoot) {
|
|
84
|
+
res += res.length > 0 ? "/.." : "..";
|
|
85
|
+
lastSegmentLength = 2;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (res.length > 0)
|
|
90
|
+
res += "/" + path.slice(lastSlash + 1, i);
|
|
91
|
+
else
|
|
92
|
+
res = path.slice(lastSlash + 1, i);
|
|
93
|
+
lastSegmentLength = i - lastSlash - 1;
|
|
94
|
+
}
|
|
95
|
+
lastSlash = i;
|
|
96
|
+
dots = 0;
|
|
97
|
+
}
|
|
98
|
+
else if (code === DOT && dots !== -1) {
|
|
99
|
+
++dots;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
dots = -1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return res;
|
|
106
|
+
}
|
|
107
|
+
/** POSIX `path.isAbsolute`. */
|
|
108
|
+
function isAbsolute(path) {
|
|
109
|
+
return path.length > 0 && path.charCodeAt(0) === SLASH;
|
|
110
|
+
}
|
|
111
|
+
/** POSIX `path.normalize`. */
|
|
112
|
+
function normalize(path) {
|
|
113
|
+
if (path.length === 0)
|
|
114
|
+
return ".";
|
|
115
|
+
const isAbs = path.charCodeAt(0) === SLASH;
|
|
116
|
+
const trailingSep = path.charCodeAt(path.length - 1) === SLASH;
|
|
117
|
+
path = normalizeString(path, !isAbs);
|
|
118
|
+
if (path.length === 0) {
|
|
119
|
+
if (isAbs)
|
|
120
|
+
return "/";
|
|
121
|
+
return trailingSep ? "./" : ".";
|
|
122
|
+
}
|
|
123
|
+
if (trailingSep)
|
|
124
|
+
path += "/";
|
|
125
|
+
return isAbs ? `/${path}` : path;
|
|
126
|
+
}
|
|
127
|
+
/** POSIX `path.join`. */
|
|
128
|
+
function join(...parts) {
|
|
129
|
+
if (parts.length === 0)
|
|
130
|
+
return ".";
|
|
131
|
+
let joined;
|
|
132
|
+
for (const part of parts) {
|
|
133
|
+
if (part.length > 0) {
|
|
134
|
+
if (joined === undefined)
|
|
135
|
+
joined = part;
|
|
136
|
+
else
|
|
137
|
+
joined += `/${part}`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (joined === undefined)
|
|
141
|
+
return ".";
|
|
142
|
+
return normalize(joined);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* POSIX `path.resolve`. Right-to-left until an absolute segment is found, then
|
|
146
|
+
* normalize. The `i === -1` fallback is `/` (not `process.cwd()`) so this stays
|
|
147
|
+
* pure; every engine call site passes an absolute first segment.
|
|
148
|
+
*/
|
|
149
|
+
function resolve(...parts) {
|
|
150
|
+
let resolvedPath = "";
|
|
151
|
+
let resolvedAbsolute = false;
|
|
152
|
+
for (let i = parts.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
|
153
|
+
const path = i >= 0 ? parts[i] : "/";
|
|
154
|
+
if (path.length === 0)
|
|
155
|
+
continue;
|
|
156
|
+
resolvedPath = `${path}/${resolvedPath}`;
|
|
157
|
+
resolvedAbsolute = path.charCodeAt(0) === SLASH;
|
|
158
|
+
}
|
|
159
|
+
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
|
|
160
|
+
if (resolvedAbsolute)
|
|
161
|
+
return `/${resolvedPath}`;
|
|
162
|
+
return resolvedPath.length > 0 ? resolvedPath : ".";
|
|
163
|
+
}
|
|
164
|
+
/** POSIX `path.dirname`. */
|
|
165
|
+
function dirname(path) {
|
|
166
|
+
if (path.length === 0)
|
|
167
|
+
return ".";
|
|
168
|
+
const hasRoot = path.charCodeAt(0) === SLASH;
|
|
169
|
+
let end = -1;
|
|
170
|
+
let matchedSlash = true;
|
|
171
|
+
for (let i = path.length - 1; i >= 1; --i) {
|
|
172
|
+
if (path.charCodeAt(i) === SLASH) {
|
|
173
|
+
if (!matchedSlash) {
|
|
174
|
+
end = i;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
matchedSlash = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (end === -1)
|
|
183
|
+
return hasRoot ? "/" : ".";
|
|
184
|
+
if (hasRoot && end === 1)
|
|
185
|
+
return "//";
|
|
186
|
+
return path.slice(0, end);
|
|
187
|
+
}
|
|
188
|
+
/** POSIX `path.basename` (with an optional `suffix` to strip, like `node:path`). */
|
|
189
|
+
function basename(path, suffix) {
|
|
190
|
+
let start = 0;
|
|
191
|
+
let end = -1;
|
|
192
|
+
let matchedSlash = true;
|
|
193
|
+
if (suffix !== undefined &&
|
|
194
|
+
suffix.length > 0 &&
|
|
195
|
+
suffix.length <= path.length) {
|
|
196
|
+
if (suffix === path)
|
|
197
|
+
return "";
|
|
198
|
+
let extIdx = suffix.length - 1;
|
|
199
|
+
let firstNonSlashEnd = -1;
|
|
200
|
+
for (let i = path.length - 1; i >= 0; --i) {
|
|
201
|
+
const code = path.charCodeAt(i);
|
|
202
|
+
if (code === SLASH) {
|
|
203
|
+
if (!matchedSlash) {
|
|
204
|
+
start = i + 1;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
if (firstNonSlashEnd === -1) {
|
|
210
|
+
matchedSlash = false;
|
|
211
|
+
firstNonSlashEnd = i + 1;
|
|
212
|
+
}
|
|
213
|
+
if (extIdx >= 0) {
|
|
214
|
+
if (code === suffix.charCodeAt(extIdx)) {
|
|
215
|
+
if (--extIdx === -1) {
|
|
216
|
+
end = i;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
extIdx = -1;
|
|
221
|
+
end = firstNonSlashEnd;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (start === end)
|
|
227
|
+
end = firstNonSlashEnd;
|
|
228
|
+
else if (end === -1)
|
|
229
|
+
end = path.length;
|
|
230
|
+
return path.slice(start, end);
|
|
231
|
+
}
|
|
232
|
+
for (let i = path.length - 1; i >= 0; --i) {
|
|
233
|
+
if (path.charCodeAt(i) === SLASH) {
|
|
234
|
+
if (!matchedSlash) {
|
|
235
|
+
start = i + 1;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else if (end === -1) {
|
|
240
|
+
matchedSlash = false;
|
|
241
|
+
end = i + 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (end === -1)
|
|
245
|
+
return "";
|
|
246
|
+
return path.slice(start, end);
|
|
247
|
+
}
|
|
248
|
+
/** POSIX `path.relative`. */
|
|
249
|
+
function relative(from, to) {
|
|
250
|
+
if (from === to)
|
|
251
|
+
return "";
|
|
252
|
+
from = resolve(from);
|
|
253
|
+
to = resolve(to);
|
|
254
|
+
if (from === to)
|
|
255
|
+
return "";
|
|
256
|
+
const fromStart = 1;
|
|
257
|
+
const fromEnd = from.length;
|
|
258
|
+
const fromLen = fromEnd - fromStart;
|
|
259
|
+
const toStart = 1;
|
|
260
|
+
const toLen = to.length - toStart;
|
|
261
|
+
const length = fromLen < toLen ? fromLen : toLen;
|
|
262
|
+
let lastCommonSep = -1;
|
|
263
|
+
let i = 0;
|
|
264
|
+
for (; i < length; i++) {
|
|
265
|
+
const fromCode = from.charCodeAt(fromStart + i);
|
|
266
|
+
if (fromCode !== to.charCodeAt(toStart + i))
|
|
267
|
+
break;
|
|
268
|
+
else if (fromCode === SLASH)
|
|
269
|
+
lastCommonSep = i;
|
|
270
|
+
}
|
|
271
|
+
if (i === length) {
|
|
272
|
+
if (toLen > length) {
|
|
273
|
+
if (to.charCodeAt(toStart + i) === SLASH)
|
|
274
|
+
return to.slice(toStart + i + 1);
|
|
275
|
+
if (i === 0)
|
|
276
|
+
return to.slice(toStart + i);
|
|
277
|
+
}
|
|
278
|
+
else if (fromLen > length) {
|
|
279
|
+
if (from.charCodeAt(fromStart + i) === SLASH)
|
|
280
|
+
lastCommonSep = i;
|
|
281
|
+
else if (i === 0)
|
|
282
|
+
lastCommonSep = 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
let out = "";
|
|
286
|
+
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
|
|
287
|
+
if (i === fromEnd || from.charCodeAt(i) === SLASH) {
|
|
288
|
+
out += out.length === 0 ? ".." : "/..";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return `${out}${to.slice(toStart + lastCommonSep)}`;
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=posix-path.js.map
|