vigiles 14.7.0 → 14.9.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 +1 -1
- package/dist/audit-report.template.html +37 -32
- package/dist/audit-score.d.ts +1 -1
- package/dist/audit-score.js +20 -20
- package/dist/audit-verdict.d.ts +1 -1
- package/dist/audit-verdict.js +3 -3
- 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/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/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/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 +83 -0
- package/dist/score-core.js +236 -0
- package/dist/test-coverage-files.d.ts +11 -0
- package/dist/test-coverage-files.js +208 -0
- package/package.json +1 -1
|
@@ -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
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { type DescriptionOverlap } from "./core/description-overlap.js";
|
|
2
|
+
import { type DescriptionBudgetIssue } from "./core/skill-description-budget.js";
|
|
3
|
+
import type { PluginLayout } from "./core/layout.js";
|
|
4
|
+
import type { HarnessDialect } from "./core/dialect.js";
|
|
5
|
+
import type { HookRegistration } from "./core/hook-normalize.js";
|
|
6
|
+
import type { HookScriptEntry } from "./core/hook-block-ineffective.js";
|
|
7
|
+
import type { HookMatcherEntry } from "./core/hook-matcher.js";
|
|
8
|
+
import type { ScanSkill, ScanAgent, ScanHook, FrontmatterIssue, FrontmatterValueIssue, FrontmatterParseIssue, ScanTrifectaFinding, ScanSkillResourceFinding, ScanSkillFenceFinding, ScanDelegationFinding } from "./scan.js";
|
|
9
|
+
/**
|
|
10
|
+
* Per-kind surface classifiers, built from the harness `PluginLayout`'s
|
|
11
|
+
* `skillDir`/`agentDir`/`commandDir` — so adding a harness whose subagents live
|
|
12
|
+
* somewhere other than `agents/` (OpenCode's `.opencode/agent`) needs no change
|
|
13
|
+
* here. Each anchors on a real path boundary (start-of-path or a `/`), so a
|
|
14
|
+
* directory whose NAME merely ends in the keyword isn't misclassified — e.g. the
|
|
15
|
+
* skill `skills/dispatching-parallel-agents/SKILL.md` must NOT register as an
|
|
16
|
+
* agent named "SKILL" (the `-agents/` substring), which real plugins like
|
|
17
|
+
* obra/superpowers ship. See scan.test.ts for the regression cases.
|
|
18
|
+
*/
|
|
19
|
+
export interface SurfaceClassifier {
|
|
20
|
+
readonly isSkill: (f: string) => boolean;
|
|
21
|
+
readonly isAgent: (f: string) => boolean;
|
|
22
|
+
readonly isCommand: (f: string) => boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function makeClassifier(layout: PluginLayout): SurfaceClassifier;
|
|
25
|
+
/** The plugin-root + materialize-root + dialect context skill scanning needs. */
|
|
26
|
+
export interface SkillScanContext {
|
|
27
|
+
readonly root: string;
|
|
28
|
+
readonly materializeRoot: string;
|
|
29
|
+
readonly dialect: HarnessDialect;
|
|
30
|
+
/**
|
|
31
|
+
* Materialized-key → real on-disk path (from `loadPlugin`). A surface can be
|
|
32
|
+
* materialized under a canonical key while living at a different root (repo-root
|
|
33
|
+
* `skills/` vs project-level `.claude/skills/`), so bundled-resource resolution
|
|
34
|
+
* uses the true dir from here rather than reverse-guessing from the key.
|
|
35
|
+
*/
|
|
36
|
+
readonly sources?: Record<string, string>;
|
|
37
|
+
/** OPT-IN top-level shared-resource dirs (`.vigilesrc.json` `sharedDirs`). */
|
|
38
|
+
readonly sharedDirs?: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* The REPO root the `sharedDirs` are declared relative to (where `.vigilesrc.json`
|
|
41
|
+
* lives) — distinct from the scan `root`, which may be a scoped SUBDIR (e.g.
|
|
42
|
+
* `lint packages/foo`). A shared-dir ref resolves against THIS, not the subdir,
|
|
43
|
+
* so a scoped scan doesn't false-flag the top-level shared tree. Defaults to
|
|
44
|
+
* `root` (unchanged when scanning the repo root itself).
|
|
45
|
+
*/
|
|
46
|
+
readonly sharedDirsRoot?: string;
|
|
47
|
+
/**
|
|
48
|
+
* REQUIRED existence check for bundled-resource resolution. The disk scan
|
|
49
|
+
* (`scanPlugin`) injects `node:fs` `existsSync`; the browser file-map engine
|
|
50
|
+
* (`scanFiles`) a map-backed impl — so the same `scanSkills` runs on disk OR
|
|
51
|
+
* in the browser with no `node:` import in this node-free module.
|
|
52
|
+
*/
|
|
53
|
+
readonly existsSync: (p: string) => boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare function scanSkills(files: Record<string, string>, cls: SurfaceClassifier, ctx: SkillScanContext): ScanSkill[];
|
|
56
|
+
export declare function descriptionOverlapsFor(files: Record<string, string>, cls: SurfaceClassifier): DescriptionOverlap[];
|
|
57
|
+
/**
|
|
58
|
+
* Model-invocable skills whose description is so long the trigger signal is
|
|
59
|
+
* buried (heuristic proxy; degrades recall + precision). Same surfaces as the
|
|
60
|
+
* overlap check. See skill-description-budget.ts.
|
|
61
|
+
*/
|
|
62
|
+
export declare function descriptionBudgetFor(files: Record<string, string>, cls: SurfaceClassifier): DescriptionBudgetIssue[];
|
|
63
|
+
export declare function scanAgents(files: Record<string, string>, dialect: HarnessDialect, declaredServers: readonly string[], cls: SurfaceClassifier): ScanAgent[];
|
|
64
|
+
/**
|
|
65
|
+
* A compiled `vigiles/hook` artifact runs through the `hook-runtime run-program`
|
|
66
|
+
* runtime entrypoint; any other hook command is hand-written (a shell script or
|
|
67
|
+
* an inline one-liner) the author maintains directly. The basis for the
|
|
68
|
+
* `prefer-compiled-hooks` nudge.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isManagedHookCommand(command: string): boolean;
|
|
71
|
+
/** The `prefer-compiled-hooks` recommendation message (shared by `lint` + `scan`). */
|
|
72
|
+
export declare function preferCompiledHooksMessage(count: number): string;
|
|
73
|
+
export declare function scanHooks(regs: readonly HookRegistration[], root: string, pluginRootToken: string, exists: (p: string) => boolean): {
|
|
74
|
+
hooks: ScanHook[];
|
|
75
|
+
inline: number;
|
|
76
|
+
manual: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Frontmatter-schema check — **subagents only**. Per the Claude Code docs, a
|
|
80
|
+
* subagent (`agents/*.md`) REQUIRES `name` + `description` (no fallback) or it
|
|
81
|
+
* won't register. A SKILL.md requires NOTHING: `name` falls back to the directory
|
|
82
|
+
* name and `description` to the first body paragraph, so a frontmatter-less skill
|
|
83
|
+
* still loads — flagging it would be a false positive (skill description QUALITY
|
|
84
|
+
* is a separate, behavioral concern). See https://code.claude.com/docs/en/skills
|
|
85
|
+
* and …/sub-agents.
|
|
86
|
+
*/
|
|
87
|
+
export declare function frontmatterIssuesFor(files: Record<string, string>, cls: SurfaceClassifier): FrontmatterIssue[];
|
|
88
|
+
/**
|
|
89
|
+
* Agent frontmatter VALUE validity — a `model:` or `color:` that's a close typo
|
|
90
|
+
* of a real one. A bad `model:` silently falls back; a bad `color:` is ignored.
|
|
91
|
+
* High-precision (close-typo only); a full/dated model id is left alone. Folded
|
|
92
|
+
* into the `subagent-frontmatter` rule. Agents only (skills have no model/color).
|
|
93
|
+
*/
|
|
94
|
+
export declare function frontmatterValueIssuesFor(files: Record<string, string>, cls: SurfaceClassifier): FrontmatterValueIssue[];
|
|
95
|
+
/**
|
|
96
|
+
* Frontmatter that EXISTS but isn't valid YAML — the `frontmatter-valid` signal.
|
|
97
|
+
* Reported for skills + agents via the shared reader's `malformed` flag. Honest
|
|
98
|
+
* caveat (see docs/rules/frontmatter-valid.md): js-yaml is stricter than some
|
|
99
|
+
* loaders, so a one-line `description:` containing a `: ` colon or an `<example>`
|
|
100
|
+
* block is flagged even though it may still load — which is why scan surfaces it
|
|
101
|
+
* as an informational note (NOT a structural defect) and the lint rule is a
|
|
102
|
+
* warn, not an error. The file's other fields are still salvaged.
|
|
103
|
+
*/
|
|
104
|
+
export declare function malformedFrontmatterFor(files: Record<string, string>, cls: SurfaceClassifier): FrontmatterParseIssue[];
|
|
105
|
+
/**
|
|
106
|
+
* Skill-metadata RECOMMENDATION (not a correctness check): a `SKILL.md` loads
|
|
107
|
+
* fine without frontmatter (`name` ← dir, `description` ← first body paragraph),
|
|
108
|
+
* but relying on those fallbacks is fragile — the dir name may be unclear and the
|
|
109
|
+
* first paragraph is often a heading or boilerplate, making a weak trigger
|
|
110
|
+
* surface. Best practice is an EXPLICIT `name` + `description`. Flags skills
|
|
111
|
+
* missing either; surfaced as a soft note in scan (NOT a structural defect, NOT
|
|
112
|
+
* scored) and gated by the `skill-frontmatter` lint rule (warn by default).
|
|
113
|
+
*/
|
|
114
|
+
export declare function skillMetaIssuesFor(files: Record<string, string>, cls: SurfaceClassifier): FrontmatterIssue[];
|
|
115
|
+
/**
|
|
116
|
+
* Flatten the per-surface lethal-trifecta + skill-resource findings into the
|
|
117
|
+
* path-tagged report lists the `audit` report AND the `lethal-trifecta` /
|
|
118
|
+
* `skill-resource-resolves` lint rules both consume (one detector, no drift).
|
|
119
|
+
*/
|
|
120
|
+
export declare function collectSurfaceFindings(agents: readonly ScanAgent[], skills: readonly ScanSkill[]): {
|
|
121
|
+
trifectaFindings: ScanTrifectaFinding[];
|
|
122
|
+
skillResourceFindings: ScanSkillResourceFinding[];
|
|
123
|
+
skillFenceFindings: ScanSkillFenceFinding[];
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Build the subagent delegation graph and flag a lethal trifecta that EMERGES
|
|
127
|
+
* across an edge (own ∪ delegated-to capability) though no single unit trips it.
|
|
128
|
+
*
|
|
129
|
+
* Edge source (deterministic, audit-available): a subagent that lists the `Task`
|
|
130
|
+
* tool can dispatch any sibling subagent, so it `delegatesTo` every OTHER agent.
|
|
131
|
+
* An inherits-all agent (`tools === null`) carries the wildcard, which the
|
|
132
|
+
* detector's FP-safe guard skips (that maximal-blast case is the per-unit
|
|
133
|
+
* advisory's job). One detector, no drift. (Richer edge sources — a typed
|
|
134
|
+
* railway's `delegate()` chain, a Flue subagent inheritance tree — plug in here.)
|
|
135
|
+
*/
|
|
136
|
+
export declare function collectDelegationTrifecta(agents: readonly ScanAgent[], dialect: HarnessDialect): ScanDelegationFinding[];
|
|
137
|
+
/**
|
|
138
|
+
* Build `hookBlockIssues` entries by walking the canonical object-keyed-by-event
|
|
139
|
+
* settings shape PER REGISTRATION — so a script registered under several events
|
|
140
|
+
* is inspected under EACH (no de-dup by script path), inline one-liners are
|
|
141
|
+
* included (script token → null, inspect the command), and a script token is
|
|
142
|
+
* resolved to its ABSOLUTE on-disk path against the plugin root (not the caller's
|
|
143
|
+
* cwd). Addresses the gaps a de-duplicated `ScanHook[]` would miss.
|
|
144
|
+
*/
|
|
145
|
+
export declare function collectHookBlockEntries(regs: readonly HookRegistration[], root: string, pluginRootToken: string, exists: (p: string) => boolean): HookScriptEntry[];
|
|
146
|
+
/** Extract (event, matcher) pairs from the normalized hook registrations. */
|
|
147
|
+
export declare function collectHookMatchers(regs: readonly HookRegistration[]): HookMatcherEntry[];
|
|
148
|
+
/** Tally how many scanned agents fall into each purity rung (effectSurface). */
|
|
149
|
+
export declare function summarizePurity(agents: readonly ScanAgent[]): {
|
|
150
|
+
pure: number;
|
|
151
|
+
bounded: number;
|
|
152
|
+
unrestricted: number;
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=scan-core.d.ts.map
|