vigiles 4.0.0 → 4.0.2

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.
Files changed (79) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +29 -18
  3. package/action.yml +1 -1
  4. package/dist/adapter-conformance.js +1 -1
  5. package/dist/adapters/claude-code/adapter.js +1 -1
  6. package/dist/check.d.ts +132 -0
  7. package/dist/check.js +318 -0
  8. package/dist/claude-code.d.ts +1 -1
  9. package/dist/claude-code.js +1 -1
  10. package/dist/cli.js +133 -55
  11. package/dist/core/compile.d.ts +1 -1
  12. package/dist/core/compile.js +1 -1
  13. package/dist/core/compose.d.ts +1 -1
  14. package/dist/core/compose.js +1 -1
  15. package/dist/core/generate-schema.d.ts +1 -1
  16. package/dist/core/generate-schema.js +4 -4
  17. package/dist/core/linters.js +2 -2
  18. package/dist/core/orphans.js +57 -14
  19. package/dist/core/proofs.js +1 -1
  20. package/dist/core/refs.d.ts +1 -1
  21. package/dist/core/refs.js +2 -2
  22. package/dist/core/sidecar.d.ts +1 -1
  23. package/dist/core/sidecar.js +1 -1
  24. package/dist/core/spec.d.ts +1 -1
  25. package/dist/core/spec.js +1 -1
  26. package/dist/core/types.d.ts +1 -1
  27. package/dist/core/validate.js +2 -2
  28. package/dist/e2e.d.ts +10 -13
  29. package/dist/e2e.js +10 -17
  30. package/dist/{adapters/claude-code/eval-cache.d.ts → eval-cache.d.ts} +1 -1
  31. package/dist/{adapters/claude-code/eval-cache.js → eval-cache.js} +1 -1
  32. package/dist/{adapters/claude-code/eval.d.ts → eval.d.ts} +217 -2
  33. package/dist/{adapters/claude-code/eval.js → eval.js} +430 -20
  34. package/dist/harness-assert.d.ts +11 -8
  35. package/dist/harness-assert.js +21 -5
  36. package/dist/{adapters/claude-code/harness-test.d.ts → harness-test.d.ts} +50 -4
  37. package/dist/{adapters/claude-code/harness-test.js → harness-test.js} +106 -4
  38. package/dist/integration.d.ts +11 -3
  39. package/dist/integration.js +12 -3
  40. package/dist/jest.d.ts +3 -1
  41. package/dist/jest.js +3 -2
  42. package/dist/{adapters/claude-code/mock-model.d.ts → mock-model.d.ts} +2 -2
  43. package/dist/{adapters/claude-code/run-hook.d.ts → run-hook.d.ts} +23 -1
  44. package/dist/{adapters/claude-code/run-hook.js → run-hook.js} +29 -1
  45. package/dist/{adapters/claude-code/sandbox.js → sandbox.js} +1 -1
  46. package/dist/scan.d.ts +1 -1
  47. package/dist/scan.js +1 -1
  48. package/dist/setup-plan.d.ts +5 -1
  49. package/dist/setup-plan.js +11 -1
  50. package/dist/test-coverage.js +8 -1
  51. package/dist/testing.d.ts +10 -3
  52. package/dist/testing.js +15 -3
  53. package/dist/unit.d.ts +4 -2
  54. package/dist/unit.js +8 -2
  55. package/dist/vitest.d.mts +3 -1
  56. package/hooks/refs-nudge.sh +1 -1
  57. package/hooks/session-start.sh +3 -3
  58. package/package.json +8 -7
  59. package/skills/edit-spec/SKILL.md +21 -10
  60. package/skills/linter-docs/SKILL.md +23 -0
  61. package/skills/migrate-to-spec/SKILL.md +1 -1
  62. package/skills/strengthen/SKILL.md +1 -2
  63. package/skills/generate-rule/SKILL.md +0 -64
  64. /package/dist/{adapters/claude-code/egress-entry.d.ts → egress-entry.d.ts} +0 -0
  65. /package/dist/{adapters/claude-code/egress-entry.js → egress-entry.js} +0 -0
  66. /package/dist/{adapters/claude-code/egress-proxy.d.ts → egress-proxy.d.ts} +0 -0
  67. /package/dist/{adapters/claude-code/egress-proxy.js → egress-proxy.js} +0 -0
  68. /package/dist/{adapters/claude-code/egress.d.ts → egress.d.ts} +0 -0
  69. /package/dist/{adapters/claude-code/egress.js → egress.js} +0 -0
  70. /package/dist/{adapters/claude-code/eval-baseline.d.ts → eval-baseline.d.ts} +0 -0
  71. /package/dist/{adapters/claude-code/eval-baseline.js → eval-baseline.js} +0 -0
  72. /package/dist/{adapters/claude-code/judge.d.ts → judge.d.ts} +0 -0
  73. /package/dist/{adapters/claude-code/judge.js → judge.js} +0 -0
  74. /package/dist/{adapters/claude-code/mock-entry.d.ts → mock-entry.d.ts} +0 -0
  75. /package/dist/{adapters/claude-code/mock-entry.js → mock-entry.js} +0 -0
  76. /package/dist/{adapters/claude-code/mock-model.js → mock-model.js} +0 -0
  77. /package/dist/{adapters/claude-code/sandbox.d.ts → sandbox.d.ts} +0 -0
  78. /package/dist/{adapters/claude-code/stats.d.ts → stats.d.ts} +0 -0
  79. /package/dist/{adapters/claude-code/stats.js → stats.js} +0 -0
package/dist/core/spec.js CHANGED
@@ -93,7 +93,7 @@ function cmd(command) {
93
93
  * Reference a symbol defined in a file — verified at compile time that the
94
94
  * named file exists AND defines the named symbol (via ast-grep, cross-language).
95
95
  * Compiles to the file-qualified inline form `` `file#symbol` `` so the markdown
96
- * `audit` / `refs-hook` re-verify the same reference.
96
+ * `lint` / `refs-hook` re-verify the same reference.
97
97
  */
98
98
  function symbol(file, name) {
99
99
  return { _ref: "symbol", file: file, symbol: name };
@@ -97,7 +97,7 @@ export interface RulesConfig {
97
97
  "untested-surface"?: RuleWithOptions<TestCoverageConfig>;
98
98
  /**
99
99
  * Nudge (or block) when an instruction file has code-shaped references that
100
- * aren't expressed as vigiles marks (so the audit can't verify them), or a
100
+ * aren't expressed as vigiles marks (so the lint can't verify them), or a
101
101
  * `vigiles:symbol` mark that points at a missing symbol. Drives the
102
102
  * PostToolUse refs-hook: "warn" (default) → a non-blocking nudge, "error" →
103
103
  * block the edit, false → off.
@@ -157,9 +157,9 @@ function validate(content, { ruleMarkers, rules: rulesConfig, filePath, dialect
157
157
  const specPath = filePath + ".spec.ts";
158
158
  // Inline mode counts as a spec — any parseable
159
159
  // `<!-- vigiles:enforce ... -->` comment means the file is
160
- // verified on `vigiles audit` even without a .spec.ts sibling.
160
+ // verified on `vigiles lint` even without a .spec.ts sibling.
161
161
  // Delegate to the real parser so a malformed marker can't
162
- // satisfy require-spec with a rule that audit can't verify.
162
+ // satisfy require-spec with a rule that lint can't verify.
163
163
  const hasInline = (0, inline_js_1.hasInlineRules)(content) || (0, frontmatter_js_1.hasFrontmatterRules)(content);
164
164
  if (!(0, node_fs_1.existsSync)(specPath) && !hasInline) {
165
165
  const msg = {
package/dist/e2e.d.ts CHANGED
@@ -1,19 +1,16 @@
1
1
  /**
2
- * `vigiles/e2e` — the **deterministic** end-to-end tier (real sandbox + real
3
- * network, but a definite pass/fail).
2
+ * `vigiles/e2e` — DEPRECATED back-compat alias for [`vigiles/integration`](./integration.ts).
4
3
  *
5
- * Re-exports everything in [`vigiles/integration`](./integration.ts) and adds the
6
- * real-egress capability: `egressRoutes()` (probe whether allowlisted egress can
7
- * actually route) and by re-export — `runHook` used with `egress: { allow }`
8
- * (allowlisted real outbound). Capability contract: needs a **routable rootless
9
- * sandbox + real network**, and each test self-skips via `egressRoutes()` where
10
- * that's unavailable. Still a **verification** tier you assert pass/fail.
4
+ * There is no separate "e2e" tier: real **egress** is a *capability* of the
5
+ * harness/integration scope (`egressRoutes()` + `runHook`'s `egress: { allow }`),
6
+ * not a different kind of testthe old `e2e` barrel added exactly one symbol
7
+ * over `integration`, which is the definition of a non-tier. It now lives on
8
+ * `vigiles/integration`; this entry re-exports it unchanged so existing imports
9
+ * keep working. See `research/testing-api-design.md` Part 4 (two scopes, not
10
+ * four tiers). Prefer `vigiles/integration`.
11
11
  *
12
- * NOT here: **evals** (`runEval` / `measureTriggerRate` / `judge`). Those are a
13
- * different axis — **non-deterministic measurement** (real model, mean ± se), run
14
- * via `vigiles eval` on `*.eval.mjs`. Import them from
15
- * [`vigiles/eval`](./eval.ts) + [`vigiles/judge`](./judge.ts), not from here.
12
+ * NOT here: **evals** (`runEval` / `measure` / `measureTriggerRate` / `judge`)
13
+ * those are non-deterministic measurement (`vigiles/eval`), a different axis.
16
14
  */
17
15
  export * from "./integration.js";
18
- export { egressRoutes } from "./adapters/claude-code/run-hook.js";
19
16
  //# sourceMappingURL=e2e.d.ts.map
package/dist/e2e.js CHANGED
@@ -14,26 +14,19 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.egressRoutes = void 0;
18
17
  /**
19
- * `vigiles/e2e` — the **deterministic** end-to-end tier (real sandbox + real
20
- * network, but a definite pass/fail).
18
+ * `vigiles/e2e` — DEPRECATED back-compat alias for [`vigiles/integration`](./integration.ts).
21
19
  *
22
- * Re-exports everything in [`vigiles/integration`](./integration.ts) and adds the
23
- * real-egress capability: `egressRoutes()` (probe whether allowlisted egress can
24
- * actually route) and by re-export — `runHook` used with `egress: { allow }`
25
- * (allowlisted real outbound). Capability contract: needs a **routable rootless
26
- * sandbox + real network**, and each test self-skips via `egressRoutes()` where
27
- * that's unavailable. Still a **verification** tier you assert pass/fail.
20
+ * There is no separate "e2e" tier: real **egress** is a *capability* of the
21
+ * harness/integration scope (`egressRoutes()` + `runHook`'s `egress: { allow }`),
22
+ * not a different kind of testthe old `e2e` barrel added exactly one symbol
23
+ * over `integration`, which is the definition of a non-tier. It now lives on
24
+ * `vigiles/integration`; this entry re-exports it unchanged so existing imports
25
+ * keep working. See `research/testing-api-design.md` Part 4 (two scopes, not
26
+ * four tiers). Prefer `vigiles/integration`.
28
27
  *
29
- * NOT here: **evals** (`runEval` / `measureTriggerRate` / `judge`). Those are a
30
- * different axis — **non-deterministic measurement** (real model, mean ± se), run
31
- * via `vigiles eval` on `*.eval.mjs`. Import them from
32
- * [`vigiles/eval`](./eval.ts) + [`vigiles/judge`](./judge.ts), not from here.
28
+ * NOT here: **evals** (`runEval` / `measure` / `measureTriggerRate` / `judge`)
29
+ * those are non-deterministic measurement (`vigiles/eval`), a different axis.
33
30
  */
34
31
  __exportStar(require("./integration.js"), exports);
35
- // The real-egress capability probe (the egress-using runHook is already re-exported
36
- // via the integration→unit chain).
37
- var run_hook_js_1 = require("./adapters/claude-code/run-hook.js");
38
- Object.defineProperty(exports, "egressRoutes", { enumerable: true, get: function () { return run_hook_js_1.egressRoutes; } });
39
32
  //# sourceMappingURL=e2e.js.map
@@ -1,4 +1,4 @@
1
- import { type SHA256Hash } from "../../core/hash.js";
1
+ import { type SHA256Hash } from "./core/hash.js";
2
2
  import type { RunOut } from "./eval.js";
3
3
  /** Cache behaviour: never touch the cache / read-only / read-and-write. */
4
4
  export type CacheMode = "off" | "read" | "readwrite";
@@ -24,7 +24,7 @@ exports.restoreDir = restoreDir;
24
24
  */
25
25
  const node_fs_1 = require("node:fs");
26
26
  const node_path_1 = require("node:path");
27
- const hash_js_1 = require("../../core/hash.js");
27
+ const hash_js_1 = require("./core/hash.js");
28
28
  const MAX_SNAPSHOT_FILE_BYTES = 1024 * 1024;
29
29
  const SKIP_DIRS = new Set(["node_modules", ".git"]);
30
30
  /**
@@ -1,5 +1,7 @@
1
1
  import { type ToolCall, type Trace } from "./harness-test.js";
2
2
  import { type CacheMode } from "./eval-cache.js";
3
+ import type { Check, CheckJSON } from "./check.js";
4
+ import { type Comparison } from "./stats.js";
3
5
  /** One arm of the comparison: fixture overrides + settings (hooks) for this arm. */
4
6
  export interface EvalArm {
5
7
  /** Files written on top of the base fixture for this arm. */
@@ -172,6 +174,127 @@ export type AgentRunner = (args: AgentRunArgs) => Promise<RunOut>;
172
174
  * {@link runEvalWith} with the real agent runner.
173
175
  */
174
176
  export declare function runEval<M extends Metrics>(spec: EvalSpec<M>): Promise<EvalReport>;
177
+ /** A task run N times, scored against a `Trace` check vocabulary. */
178
+ export interface MeasureSpec {
179
+ /** Base fixture files written for every run (path → contents). */
180
+ readonly fixture?: Record<string, string>;
181
+ /** `.claude/settings.json` (hooks/permissions) for the run. */
182
+ readonly settings?: unknown;
183
+ /** A real plugin/repo to load (materialized) — see `EvalArm.plugin`. */
184
+ readonly plugin?: string;
185
+ /** A complete plugin dir to install natively (`--plugin-dir`) so skills activate. */
186
+ readonly pluginDir?: string;
187
+ /**
188
+ * Stub each skill BODY in `pluginDir` (frontmatter/trigger surface kept) before
189
+ * the run — for checks about whether a skill FIRES (`skill()`), not what it
190
+ * produces. A selected skill stops at selection instead of running its (often
191
+ * expensive) procedure, so a description/firing run costs a fraction of the
192
+ * tokens. Do NOT combine with `judged`/quality checks: the body is gone, so
193
+ * there's nothing to grade. Requires `pluginDir`. See {@link stubSkillBody}.
194
+ */
195
+ readonly stubSkillBodies?: boolean;
196
+ /** The task prompt given to the agent. */
197
+ readonly task: string;
198
+ /**
199
+ * The checks to score across the trials. Any check over the run is accepted:
200
+ * `Trace` checks (`tool`/`skill`/`output`/`mcp`/`judged`) and resource checks
201
+ * (`cost`/`latency`/`tokens`, which read the eval-only `usage`) — all fit
202
+ * `Check<RunContext>`.
203
+ */
204
+ readonly checks: readonly Check<RunContext>[];
205
+ /** Trials. Default 5. */
206
+ readonly trials?: number;
207
+ /** Model alias. Default "sonnet" — measure on the model your users run. */
208
+ readonly model?: string;
209
+ /** Tools the agent may use. */
210
+ readonly allowedTools?: readonly string[];
211
+ /** Per-run timeout ms. */
212
+ readonly timeoutMs?: number;
213
+ /** Seconds between runs. */
214
+ readonly spacingSec?: number;
215
+ }
216
+ /** One check's measured rate across the trials. */
217
+ export interface CheckRate {
218
+ readonly check: CheckJSON;
219
+ /** Fraction of trials the check passed (0..1). */
220
+ readonly rate: number;
221
+ /** Standard error of the rate. */
222
+ readonly se: number;
223
+ /** pass^k — 1 iff the check passed on EVERY trial. */
224
+ readonly passK: number;
225
+ /** Trials observed. */
226
+ readonly n: number;
227
+ }
228
+ export interface CheckReport {
229
+ readonly n: number;
230
+ readonly perCheck: readonly CheckRate[];
231
+ }
232
+ /**
233
+ * Score a check vocabulary across trials — the scored counterpart to
234
+ * `assertChecks` (strict). Each check yields a `rate ± se` and `pass^k` over `n`
235
+ * runs. Reuses the tested `runEvalWith` aggregation (one arm), so the loop,
236
+ * cache, concurrency, and stats come for free. Exported with an injectable
237
+ * `runner` so the orchestration is unit-testable without a model.
238
+ */
239
+ export declare function measureWith(spec: MeasureSpec, runner: AgentRunner): Promise<CheckReport>;
240
+ /** Score a check vocabulary across trials against the real `claude` CLI. */
241
+ export declare function measure(spec: MeasureSpec): Promise<CheckReport>;
242
+ /** A task scored against checks across NAMED arms (the harness variable on/off). */
243
+ export interface ArmsMeasureSpec {
244
+ readonly fixture?: Record<string, string>;
245
+ /** The arms to compare (settings / plugin / pluginDir per arm). */
246
+ readonly arms: Record<string, EvalArm>;
247
+ readonly task: string;
248
+ readonly checks: readonly Check<RunContext>[];
249
+ /**
250
+ * Stub each arm's skill BODIES (frontmatter kept) before the run — the A/B
251
+ * counterpart to {@link MeasureSpec.stubSkillBodies}. For firing comparisons
252
+ * (does description variant A fire more than B?), every arm that sets
253
+ * `pluginDir` is repackaged with bodies stripped so each run stops at
254
+ * selection — a fraction of the tokens. Arms without a `pluginDir` are left
255
+ * untouched. Don't combine with `judged`/quality checks. See {@link stubSkillBody}.
256
+ */
257
+ readonly stubSkillBodies?: boolean;
258
+ readonly trials?: number;
259
+ readonly model?: string;
260
+ readonly allowedTools?: readonly string[];
261
+ readonly timeoutMs?: number;
262
+ readonly spacingSec?: number;
263
+ }
264
+ /** Per-arm {@link CheckReport}s — `arms[name].perCheck[i]` aligns across arms. */
265
+ export interface ArmsCheckReport {
266
+ readonly arms: Record<string, CheckReport>;
267
+ }
268
+ /** Score checks across arms (injectable runner). Reuses `runEvalWith`. */
269
+ export declare function measureArmsWith(spec: ArmsMeasureSpec, runner: AgentRunner): Promise<ArmsCheckReport>;
270
+ /** Score checks across arms against the real `claude` CLI. */
271
+ export declare function measureArms(spec: ArmsMeasureSpec): Promise<ArmsCheckReport>;
272
+ /**
273
+ * Welch significance on one check's rate between two arms (`arm` vs `baseline`),
274
+ * by index in `perCheck`. So "the gated arm resolves the skill significantly more
275
+ * than vanilla" is a p-value, not a vibe. Reuses `welchTTest` from stats.ts.
276
+ */
277
+ export declare function compareCheck(report: ArmsCheckReport, baseline: string, arm: string, checkIndex: number): Comparison;
278
+ /** Format a {@link CheckReport}: one line per check with its rate ± se and pass^k. */
279
+ export declare function formatCheckReport(report: CheckReport): string;
280
+ /**
281
+ * The scored gate (Phase 4): throw if any check's measured rate is below `min` —
282
+ * the `measure` counterpart to `assertChecks` (strict). Reads the rate, not a
283
+ * single run, so it never trips on one noisy trial.
284
+ */
285
+ export declare function assertRates(report: CheckReport, opts: {
286
+ min: number;
287
+ }): void;
288
+ /**
289
+ * Serialize a {@link CheckReport} to JUnit XML (Phase 4) — each check a
290
+ * `<testcase>`, failing when its rate is below `min`. Because a check is *data*,
291
+ * this falls out for free: CI test reporters, regression baselines, and a
292
+ * promptfoo bridge all consume the same shape.
293
+ */
294
+ export declare function checkReportToJUnit(report: CheckReport, opts?: {
295
+ min?: number;
296
+ name?: string;
297
+ }): string;
175
298
  /** Parse per-run cost/latency/tokens from a stream — pure, model-free. */
176
299
  export declare function parseUsage(stdout: string): EvalUsage;
177
300
  /** Aggregate per-run metrics: mean for numbers, fraction-true (0..1) for booleans. */
@@ -209,8 +332,19 @@ export declare function formatEvalReport(report: EvalReport): string;
209
332
  * (reuse the bare predicates, e.g. `(t) => skillResolved(t, "x:y")`).
210
333
  */
211
334
  export interface TriggerRateSpec {
212
- /** Plugin dir installed natively (`--plugin-dir`) so its skills/commands activate. */
213
- readonly pluginDir: string;
335
+ /**
336
+ * Plugin dir installed natively (`--plugin-dir`) so its skills/commands
337
+ * activate. Provide this OR {@link skillsDir}, not both.
338
+ */
339
+ readonly pluginDir?: string;
340
+ /**
341
+ * A directory of LOOSE skills (`<skillsDir>/<name>/SKILL.md`, e.g. a repo's
342
+ * `.claude/skills`) to trigger-test directly. vigiles packages them into a
343
+ * throwaway `--plugin-dir` for you and removes it afterward — the one-liner
344
+ * for repo-local skills that aren't a published plugin. Provide this OR
345
+ * {@link pluginDir}, not both.
346
+ */
347
+ readonly skillsDir?: string;
214
348
  /** The varied prompts to test the trigger against. */
215
349
  readonly prompts: readonly string[];
216
350
  /**
@@ -223,6 +357,30 @@ export interface TriggerRateSpec {
223
357
  readonly irrelevantPrompts?: readonly string[];
224
358
  /** Did the behaviour fire on this run? e.g. `(t) => skillResolved(t, "x:y")`. */
225
359
  readonly fired: (trace: Trace) => boolean;
360
+ /**
361
+ * Replace each skill's BODY with a no-op stub (keeping its frontmatter — name +
362
+ * description) before running. Trigger-rate is decided by the frontmatter alone
363
+ * (the model selects a skill before its body loads), so stubbing the body can't
364
+ * change what's measured but stops the run from executing an expensive
365
+ * procedure once the skill fires — far cheaper, faster, side-effect-free. All
366
+ * skills' descriptions stay present, so the selection competition is faithful.
367
+ * Default false (off) for now; recommended `true` for trigger evals. See
368
+ * {@link stubSkillBody}.
369
+ */
370
+ readonly stubSkillBodies?: boolean;
371
+ /**
372
+ * Minimum number of prompts each set (relevant + irrelevant) must have. A
373
+ * handful of prompts can't tell a real recall/precision rate from noise, so
374
+ * the run is rejected before it spends a token. Default 10; lower it
375
+ * deliberately for a genuinely narrow skill.
376
+ */
377
+ readonly minPrompts?: number;
378
+ /**
379
+ * Reject the run when two prompts in a set are closer than this in NCD
380
+ * (gzip-based distance, 0..1; 0 = identical) — near-duplicate prompts inflate
381
+ * a rate without testing varied phrasings. Default 0.3 (the rule-dup threshold).
382
+ */
383
+ readonly minDistance?: number;
226
384
  /** Trials per prompt. Default 1. */
227
385
  readonly trials?: number;
228
386
  /** Model alias. Default "haiku". */
@@ -263,6 +421,63 @@ export interface TriggerRateReport {
263
421
  /** Per-prompt stats for the irrelevant set. Present with irrelevant prompts. */
264
422
  readonly perIrrelevant?: readonly PromptTriggerStat[];
265
423
  }
424
+ /**
425
+ * Package loose `<skillsDir>/<name>/SKILL.md` skills into a throwaway plugin dir
426
+ * that `claude --plugin-dir` accepts — so repo-local skills (e.g. `.claude/skills`)
427
+ * can be trigger-tested without hand-rolling a `plugin.json`. Writes a minimal
428
+ * `.claude-plugin/plugin.json` and copies each `<name>/` (recursively, so
429
+ * `references/` etc. come along) under `skills/<name>/`. Returns the temp plugin
430
+ * dir; the caller removes it (`measureTriggerRate` does). Throws if the directory
431
+ * is missing or holds no `<name>/SKILL.md`.
432
+ */
433
+ export declare function packageSkillsDir(skillsDir: string, opts?: {
434
+ name?: string;
435
+ stub?: boolean;
436
+ }): string;
437
+ /**
438
+ * Rewrite a SKILL.md to keep its YAML frontmatter (the trigger surface — name +
439
+ * description) but replace the body with a no-op stub. Trigger-rate is a property
440
+ * of the frontmatter ONLY: the model picks a skill from its name + description
441
+ * before the body is ever loaded, so the body is causally downstream of selection
442
+ * and irrelevant to whether the skill fires. Stubbing it lets a trigger run stop
443
+ * AT selection instead of executing an expensive multi-step procedure — cheaper,
444
+ * faster, and side-effect-free, without changing what's measured. Pure.
445
+ */
446
+ export declare function stubSkillBody(skillMd: string): string;
447
+ /**
448
+ * Build a throwaway plugin dir mirroring `pluginDir`'s skills with their BODIES
449
+ * stripped (frontmatter kept) — the trigger surface a description/firing check
450
+ * needs, without paying to run each skill's procedure. Keeps the original plugin
451
+ * NAME so `<name>:<skill>` ids still match. The caller removes the returned dir.
452
+ * See {@link stubSkillBody} for why the body is irrelevant to selection.
453
+ */
454
+ export declare function stubbedPluginDir(pluginDir: string): string;
455
+ /**
456
+ * Distance between two prompts in ~0..1 (0 = identical, higher = more
457
+ * different) via Normalized Compression Distance over the normalized text.
458
+ * Reuses {@link ncd} from the proof engine.
459
+ */
460
+ export declare function promptDistance(a: string, b: string): number;
461
+ export interface PromptDiversityIssue {
462
+ readonly kind: "too-few" | "too-similar";
463
+ readonly message: string;
464
+ }
465
+ /**
466
+ * Deterministically check a prompt set is big and varied enough to measure a
467
+ * trigger rate: at least `minPrompts` entries, and no two closer than
468
+ * `minDistance` in NCD. Pure — no model. `label` names the set in messages.
469
+ */
470
+ export declare function checkPromptDiversity(prompts: readonly string[], opts?: {
471
+ minPrompts?: number;
472
+ minDistance?: number;
473
+ label?: string;
474
+ }): PromptDiversityIssue[];
475
+ /** Throw if a prompt set isn't big/varied enough. See {@link checkPromptDiversity}. */
476
+ export declare function assertPromptDiversity(prompts: readonly string[], opts?: {
477
+ minPrompts?: number;
478
+ minDistance?: number;
479
+ label?: string;
480
+ }): void;
266
481
  /**
267
482
  * Trigger-rate orchestration — every prompt × trial via `runner`, the `fired`
268
483
  * predicate evaluated per run and aggregated into an overall + per-prompt rate.