vigiles 5.0.1 → 5.2.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.
Files changed (72) hide show
  1. package/README.md +15 -9
  2. package/dist/adapters/claude-code/adapter.js +1 -0
  3. package/dist/adapters/claude-code/agent-runtime.d.ts +30 -6
  4. package/dist/adapters/claude-code/agent-runtime.js +66 -37
  5. package/dist/adapters/claude-code/dialect.js +37 -2
  6. package/dist/adapters/claude-code/effect-region.d.ts +9 -0
  7. package/dist/adapters/claude-code/effect-region.js +45 -0
  8. package/dist/adapters/claude-code/layout.js +3 -0
  9. package/dist/adapters/claude-code/skill-runtime.d.ts +25 -0
  10. package/dist/adapters/claude-code/skill-runtime.js +48 -0
  11. package/dist/adapters/codex/adapter.js +3 -0
  12. package/dist/adapters/codex/eval.d.ts +94 -0
  13. package/dist/adapters/codex/eval.js +227 -0
  14. package/dist/adapters/codex/layout.js +3 -0
  15. package/dist/adapters/opencode/adapter.js +1 -0
  16. package/dist/adapters/opencode/layout.js +3 -0
  17. package/dist/check.d.ts +8 -0
  18. package/dist/check.js +27 -3
  19. package/dist/cli.js +712 -21
  20. package/dist/codex.d.ts +1 -0
  21. package/dist/codex.js +3 -0
  22. package/dist/core/adapter.d.ts +10 -0
  23. package/dist/core/bash-effects.d.ts +41 -0
  24. package/dist/core/bash-effects.js +405 -0
  25. package/dist/core/compile.d.ts +3 -1
  26. package/dist/core/compile.js +169 -74
  27. package/dist/core/description-overlap.d.ts +27 -0
  28. package/dist/core/description-overlap.js +53 -0
  29. package/dist/core/dialect.d.ts +18 -0
  30. package/dist/core/effects.d.ts +172 -0
  31. package/dist/core/effects.js +245 -0
  32. package/dist/core/frontmatter-read.d.ts +25 -0
  33. package/dist/core/frontmatter-read.js +138 -0
  34. package/dist/core/hook-events.d.ts +34 -0
  35. package/dist/core/hook-events.js +48 -0
  36. package/dist/core/layout.d.ts +6 -0
  37. package/dist/core/mcp-config.d.ts +20 -0
  38. package/dist/core/mcp-config.js +40 -0
  39. package/dist/core/mcp-hook.d.ts +35 -0
  40. package/dist/core/mcp-hook.js +70 -0
  41. package/dist/core/mcp-tool.d.ts +50 -0
  42. package/dist/core/mcp-tool.js +61 -0
  43. package/dist/core/orphans.js +21 -0
  44. package/dist/core/spec.d.ts +142 -3
  45. package/dist/core/spec.js +48 -0
  46. package/dist/core/tool-contract.d.ts +68 -0
  47. package/dist/core/tool-contract.js +113 -0
  48. package/dist/core/types.d.ts +91 -2
  49. package/dist/core/validate.js +23 -1
  50. package/dist/eval.d.ts +69 -13
  51. package/dist/eval.js +106 -51
  52. package/dist/harness-test.d.ts +7 -0
  53. package/dist/harness-test.js +19 -7
  54. package/dist/leaderboard.d.ts +2 -0
  55. package/dist/leaderboard.js +63 -3
  56. package/dist/optimize.d.ts +74 -0
  57. package/dist/optimize.js +94 -0
  58. package/dist/plugin-loader.d.ts +1 -0
  59. package/dist/plugin-loader.js +71 -18
  60. package/dist/scaffold-test.d.ts +30 -0
  61. package/dist/scaffold-test.js +158 -0
  62. package/dist/scan-behavioral.d.ts +73 -0
  63. package/dist/scan-behavioral.js +150 -0
  64. package/dist/scan.d.ts +166 -1
  65. package/dist/scan.js +622 -55
  66. package/dist/score-explainer.d.ts +69 -0
  67. package/dist/score-explainer.js +169 -0
  68. package/dist/test-coverage.d.ts +7 -0
  69. package/dist/test-coverage.js +39 -24
  70. package/package.json +2 -1
  71. package/skills/{migrate-to-spec → adopt-spec}/SKILL.md +4 -6
  72. package/skills/edit-spec/SKILL.md +1 -1
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `vigiles scan --trigger` — the BEHAVIORAL column of the scan report.
3
+ *
4
+ * Structural `scan`/`scanPlugin` is deterministic, no-model, CI-free — and stays
5
+ * that way. This is the opt-in, model-gated column that stacks on top: for each
6
+ * model-invocable skill in a plugin, it measures how reliably the description
7
+ * actually FIRES (recall, + precision when irrelevant prompts are supplied),
8
+ * reusing `measureTriggerRate`. It degrades honestly when the `claude` CLI / auth
9
+ * is absent rather than faking a pass — exactly like the egress column.
10
+ *
11
+ * Prompts are AUTHOR-SUPPLIED (a per-skill JSON map), not model-generated — a
12
+ * path in prose is undecidable, and the deterministic-input discipline is what
13
+ * makes the column trustworthy. See `research/plugin-behavioral-findings.md`.
14
+ */
15
+ import { type EvalDriver } from "./eval.js";
16
+ import { type Trace } from "./harness-test.js";
17
+ /** Which harness drives the behavioral column (default Claude Code). */
18
+ export type ProbeHarness = "claude-code" | "codex";
19
+ /** Author-supplied prompt sets for one skill (bare skill name → these). */
20
+ export interface SkillPrompts {
21
+ readonly prompts: readonly string[];
22
+ readonly irrelevant?: readonly string[];
23
+ }
24
+ /** The `--prompts <file>` shape: bare skill name → its prompt sets. */
25
+ export type TriggerPromptSet = Record<string, SkillPrompts>;
26
+ export interface SkillTriggerResult {
27
+ readonly skill: string;
28
+ /** Whether a model probe actually ran (false = skipped, see `note`). */
29
+ readonly measured: boolean;
30
+ readonly recall?: number;
31
+ readonly precision?: number;
32
+ readonly falsePositiveRate?: number;
33
+ readonly n?: number;
34
+ /** Why it was skipped, or a measurement error. */
35
+ readonly note?: string;
36
+ }
37
+ export interface BehavioralReport {
38
+ /** False when the `claude` CLI / auth is absent — the column couldn't run. */
39
+ readonly available: boolean;
40
+ readonly results: readonly SkillTriggerResult[];
41
+ }
42
+ export interface ProbeOptions {
43
+ readonly concurrency?: number;
44
+ readonly model?: string;
45
+ readonly minPrompts?: number;
46
+ readonly minDistance?: number;
47
+ /** Which harness to drive (default `"claude-code"`). */
48
+ readonly harness?: ProbeHarness;
49
+ }
50
+ /**
51
+ * Per-harness probe wiring: the eval driver (runner+parse), how to build the
52
+ * `fired` predicate for a skill, whether to stub bodies, and an availability
53
+ * gate. Claude detects firing via the `Skill` tool_use (namespaced by the
54
+ * plugin name); Codex has no skill event, so firing is the SKILL.md read
55
+ * (`codexSkillFired`, bare name) — see `research/codex-prototype-findings.md`.
56
+ */
57
+ export interface HarnessProbe {
58
+ readonly evalDriver: EvalDriver;
59
+ readonly firedFor: (name: string) => (t: Trace) => boolean;
60
+ readonly stub: boolean;
61
+ readonly available: () => boolean;
62
+ }
63
+ /** The injectable core (for tests): probe every model-invocable skill that has prompts. */
64
+ export declare function probePluginTriggersWith(dir: string, promptSet: TriggerPromptSet, probe: HarnessProbe, opts?: ProbeOptions): Promise<BehavioralReport>;
65
+ /**
66
+ * Probe a plugin's skills against the real harness (default Claude Code; Codex via
67
+ * `opts.harness`). Needs that harness's binary + auth; degrades to
68
+ * `available: false` otherwise.
69
+ */
70
+ export declare function probePluginTriggers(dir: string, promptSet: TriggerPromptSet, opts?: ProbeOptions): Promise<BehavioralReport>;
71
+ /** Format the behavioral column as a scan-report section. */
72
+ export declare function formatBehavioralReport(b: BehavioralReport): string;
73
+ //# sourceMappingURL=scan-behavioral.d.ts.map
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * `vigiles scan --trigger` — the BEHAVIORAL column of the scan report.
4
+ *
5
+ * Structural `scan`/`scanPlugin` is deterministic, no-model, CI-free — and stays
6
+ * that way. This is the opt-in, model-gated column that stacks on top: for each
7
+ * model-invocable skill in a plugin, it measures how reliably the description
8
+ * actually FIRES (recall, + precision when irrelevant prompts are supplied),
9
+ * reusing `measureTriggerRate`. It degrades honestly when the `claude` CLI / auth
10
+ * is absent rather than faking a pass — exactly like the egress column.
11
+ *
12
+ * Prompts are AUTHOR-SUPPLIED (a per-skill JSON map), not model-generated — a
13
+ * path in prose is undecidable, and the deterministic-input discipline is what
14
+ * makes the column trustworthy. See `research/plugin-behavioral-findings.md`.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.probePluginTriggersWith = probePluginTriggersWith;
18
+ exports.probePluginTriggers = probePluginTriggers;
19
+ exports.formatBehavioralReport = formatBehavioralReport;
20
+ const node_fs_1 = require("node:fs");
21
+ const node_path_1 = require("node:path");
22
+ const scan_js_1 = require("./scan.js");
23
+ const eval_js_1 = require("./eval.js");
24
+ const harness_assert_js_1 = require("./harness-assert.js");
25
+ const harness_test_js_1 = require("./harness-test.js");
26
+ const eval_js_2 = require("./adapters/codex/eval.js");
27
+ const driver_js_1 = require("./adapters/codex/driver.js");
28
+ function buildProbe(dir, harness) {
29
+ if (harness === "codex") {
30
+ return {
31
+ evalDriver: eval_js_2.codexEvalDriver,
32
+ firedFor: (name) => (t) => (0, eval_js_2.codexSkillFired)(t, name),
33
+ // Codex stubbing of a non-Claude plugin isn't validated; install the real
34
+ // skills (firing is the SKILL.md read, detected regardless of body).
35
+ stub: false,
36
+ available: () => driver_js_1.codexDriver.available(),
37
+ };
38
+ }
39
+ const ns = pluginName(dir);
40
+ return {
41
+ evalDriver: eval_js_1.claudeEvalDriver,
42
+ firedFor: (name) => (t) => (0, harness_assert_js_1.skillResolved)(t, ns ? `${ns}:${name}` : name),
43
+ stub: true,
44
+ available: harness_test_js_1.claudeAvailable,
45
+ };
46
+ }
47
+ /** Plugin name from `.claude-plugin/plugin.json` (the skill id's namespace). */
48
+ function pluginName(dir) {
49
+ const p = (0, node_path_1.join)(dir, ".claude-plugin", "plugin.json");
50
+ if (!(0, node_fs_1.existsSync)(p))
51
+ return null;
52
+ try {
53
+ return (JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8")).name ?? null);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /** Probe one skill via the harness probe's eval driver → result, never throwing. */
60
+ async function probeSkill(ctx, name, ps) {
61
+ try {
62
+ const r = await (0, eval_js_1.measureTriggerRate)({
63
+ pluginDir: ctx.dir,
64
+ stubSkillBodies: ctx.probe.stub,
65
+ prompts: ps.prompts,
66
+ irrelevantPrompts: ps.irrelevant,
67
+ minPrompts: ctx.opts.minPrompts,
68
+ minDistance: ctx.opts.minDistance,
69
+ model: ctx.opts.model,
70
+ concurrency: ctx.opts.concurrency,
71
+ fired: ctx.probe.firedFor(name),
72
+ }, { evalDriver: ctx.probe.evalDriver });
73
+ return {
74
+ skill: name,
75
+ measured: true,
76
+ recall: r.rate,
77
+ precision: r.precision,
78
+ falsePositiveRate: r.falsePositiveRate,
79
+ n: r.n,
80
+ };
81
+ }
82
+ catch (e) {
83
+ // A thin/near-duplicate prompt set (diversity gate) or a model-floor reject
84
+ // shouldn't crash the whole scan — surface it per skill.
85
+ return {
86
+ skill: name,
87
+ measured: false,
88
+ note: e instanceof Error ? e.message : String(e),
89
+ };
90
+ }
91
+ }
92
+ /** The injectable core (for tests): probe every model-invocable skill that has prompts. */
93
+ async function probePluginTriggersWith(dir, promptSet, probe, opts = {}) {
94
+ const ctx = { dir, opts, probe };
95
+ // Only model-invocable, describable skills can auto-trigger; user-invoked and
96
+ // description-less ones can't, so they're not behavioral candidates.
97
+ const candidates = (0, scan_js_1.scanPlugin)(dir).skills.filter((s) => !s.userInvoked && s.hasDescription);
98
+ const results = [];
99
+ for (const s of candidates) {
100
+ const ps = promptSet[s.name];
101
+ if (!ps || ps.prompts.length === 0) {
102
+ results.push({
103
+ skill: s.name,
104
+ measured: false,
105
+ note: "no prompts supplied",
106
+ });
107
+ continue;
108
+ }
109
+ results.push(await probeSkill(ctx, s.name, ps));
110
+ }
111
+ return { available: true, results };
112
+ }
113
+ /**
114
+ * Probe a plugin's skills against the real harness (default Claude Code; Codex via
115
+ * `opts.harness`). Needs that harness's binary + auth; degrades to
116
+ * `available: false` otherwise.
117
+ */
118
+ async function probePluginTriggers(dir, promptSet, opts = {}) {
119
+ const probe = buildProbe(dir, opts.harness ?? "claude-code");
120
+ if (!probe.available())
121
+ return { available: false, results: [] };
122
+ return probePluginTriggersWith(dir, promptSet, probe, opts);
123
+ }
124
+ const pct = (x) => `${(x * 100).toFixed(0)}%`;
125
+ /** Format the behavioral column as a scan-report section. */
126
+ function formatBehavioralReport(b) {
127
+ if (!b.available) {
128
+ return "Behavioral (trigger-rate): unavailable — needs the harness CLI + model auth";
129
+ }
130
+ if (b.results.length === 0) {
131
+ return "Behavioral (trigger-rate): no model-invocable skills to probe";
132
+ }
133
+ const lines = ["Behavioral (trigger-rate):"];
134
+ for (const r of b.results) {
135
+ if (!r.measured) {
136
+ lines.push(` · ${r.skill} — unmeasured (${r.note ?? "skipped"})`);
137
+ continue;
138
+ }
139
+ const extra = [];
140
+ if (r.precision !== undefined)
141
+ extra.push(`precision ${pct(r.precision)}`);
142
+ if (r.falsePositiveRate !== undefined)
143
+ extra.push(`fp ${pct(r.falsePositiveRate)}`);
144
+ extra.push(`${String(r.n ?? 0)} runs`);
145
+ const mark = (r.recall ?? 0) >= 0.6 ? "✓" : "⚠";
146
+ lines.push(` ${mark} ${r.skill} — recall ${pct(r.recall ?? 0)} (${extra.join(", ")})`);
147
+ }
148
+ return lines.join("\n");
149
+ }
150
+ //# sourceMappingURL=scan-behavioral.js.map
package/dist/scan.d.ts CHANGED
@@ -12,17 +12,75 @@
12
12
  * stack on top later; this core stays pure so it runs anywhere in CI for free.
13
13
  */
14
14
  import type { PluginLayout } from "./core/layout.js";
15
+ import type { HarnessDialect } from "./core/dialect.js";
16
+ import { type ToolIssue } from "./core/tool-contract.js";
17
+ import { type HookEventIssue } from "./core/hook-events.js";
18
+ import { type McpIssue } from "./core/mcp-config.js";
19
+ import { type DescriptionOverlap } from "./core/description-overlap.js";
20
+ import { type McpToolIssue } from "./core/mcp-tool.js";
21
+ import { type McpHookIssue } from "./core/mcp-hook.js";
22
+ import { type PurityLevel, type EffectSurface } from "./core/effects.js";
23
+ /** A named writing system. The label `unexpectedScript` reports + the config's expectation parse into this. */
24
+ export type Script = "Latin" | "Cyrillic" | "Han" | "Japanese" | "Korean" | "Arabic" | "Hebrew" | "Greek" | "Devanagari" | "Thai";
15
25
  export interface ScanSkill {
16
26
  readonly name: string;
17
27
  readonly path: string;
18
28
  readonly hasDescription: boolean;
19
29
  readonly userInvoked: boolean;
30
+ /**
31
+ * The description's dominant script when it DIFFERS from the expected one
32
+ * (default `"Latin"`), else null. The model's skill-selection context is
33
+ * English-centric, so a description in another script carries a cross-language
34
+ * trigger risk — it may under-fire on English prompts. A RISK flag, not a
35
+ * defect (a language-matched audience is fine); measure the real gap with
36
+ * `scan --trigger`.
37
+ */
38
+ readonly descriptionScript: Script | null;
20
39
  }
21
40
  export interface ScanAgent {
22
41
  readonly name: string;
23
42
  readonly path: string;
24
43
  /** Declared tool contract, or null when the agent ships no `tools:` (inherits all). */
25
44
  readonly tools: readonly string[] | null;
45
+ /** Contract entries that don't resolve to a real built-in / MCP tool (typo, never-available). */
46
+ readonly toolIssues: readonly ToolIssue[];
47
+ /** MCP tool entries naming a server the plugin doesn't declare (can't resolve). */
48
+ readonly mcpToolIssues: readonly McpToolIssue[];
49
+ /** `disallowedTools:` block-list entries that are typos of a real tool (block nothing). */
50
+ readonly disallowedToolIssues: readonly ToolIssue[];
51
+ /**
52
+ * Static effect-surface purity of the agent's declared tool contract.
53
+ * - `"pure"` — no side-effecting tools (read-only, deterministically testable).
54
+ * - `"bounded"` — has side-effecting tools (Edit/Write/…) but no Bash or unknown.
55
+ * - `"unrestricted"` — has Bash, any MCP/unknown tool, or inherits-all (no contract).
56
+ * Computed by `effectSurface()` from `src/core/effects.ts` — one detector, no drift.
57
+ */
58
+ readonly purity: PurityLevel;
59
+ /**
60
+ * The three tool buckets from `effectSurface()`: read-only, side-effecting, and
61
+ * unknown-effect (MCP or unrecognized) tool names in the declared contract.
62
+ */
63
+ readonly effectBuckets: Pick<EffectSurface, "readOnly" | "sideEffecting" | "unknown">;
64
+ }
65
+ /** A skill/agent whose frontmatter is missing a required field (name / description). */
66
+ export interface FrontmatterIssue {
67
+ readonly path: string;
68
+ readonly kind: "skill" | "agent";
69
+ readonly missing: readonly ("name" | "description")[];
70
+ readonly message: string;
71
+ }
72
+ /** A skill/agent whose `---` block exists but isn't valid YAML (may not parse as intended). */
73
+ export interface FrontmatterParseIssue {
74
+ readonly path: string;
75
+ readonly message: string;
76
+ }
77
+ /** An agent frontmatter field whose VALUE is invalid (a typo of a real model/color). */
78
+ export interface FrontmatterValueIssue {
79
+ readonly path: string;
80
+ readonly field: "model" | "color";
81
+ readonly value: string;
82
+ readonly suggestion: string;
83
+ readonly message: string;
26
84
  }
27
85
  /** ok = file present; missing = referenced but absent; unresolved = path still has an unexpanded var, can't check. */
28
86
  export type HookStatus = "ok" | "missing" | "unresolved";
@@ -30,8 +88,21 @@ export interface ScanHook {
30
88
  readonly script: string;
31
89
  readonly status: HookStatus;
32
90
  }
91
+ /**
92
+ * The repo's top-level instruction file (`CLAUDE.md` / `AGENTS.md`), if present.
93
+ * Every cc/codex repo has one even when it ships no plugin surface, so `scan`
94
+ * reports it — otherwise a plain instruction-only repo looks empty. `hasSpec` is
95
+ * the deterministic fact that a `<file>.spec.ts` sits beside it (spec-managed vs
96
+ * hand-written); it is informational, NOT the `require-spec` gate (that's lint).
97
+ */
98
+ export interface ScanInstructions {
99
+ readonly file: string;
100
+ readonly hasSpec: boolean;
101
+ }
33
102
  export interface ScanReport {
34
103
  readonly dir: string;
104
+ /** The detected instruction file (CLAUDE.md/AGENTS.md), or null if none. */
105
+ readonly instructions: ScanInstructions | null;
35
106
  readonly skills: readonly ScanSkill[];
36
107
  readonly agents: readonly ScanAgent[];
37
108
  readonly hooks: readonly ScanHook[];
@@ -39,11 +110,105 @@ export interface ScanReport {
39
110
  readonly inlineHooks: number;
40
111
  readonly commands: number;
41
112
  readonly mcp: boolean;
113
+ /**
114
+ * Intra-plugin file references (hook scripts, skill bodies) pointing at files
115
+ * that don't exist on disk — the broken-path / partial-vendor class. A
116
+ * first-class structural finding, not just a free-text warning, so the verdict
117
+ * and the leaderboard can count it.
118
+ */
119
+ readonly danglingRefs: readonly string[];
120
+ /** Hooks registered under an event name the harness doesn't define (typo / dead). */
121
+ readonly hookEventIssues: readonly HookEventIssue[];
122
+ /** Skills/agents missing a required frontmatter field (name; agents also description). */
123
+ readonly frontmatterIssues: readonly FrontmatterIssue[];
124
+ /** Agent frontmatter fields with an invalid value (a typo of a real model/color). */
125
+ readonly frontmatterValueIssues: readonly FrontmatterValueIssue[];
126
+ /** Skills lacking an EXPLICIT name/description — a best-practice recommendation, not a defect. */
127
+ readonly skillMetaIssues: readonly FrontmatterIssue[];
128
+ /** Declared MCP servers that can't start (no command/url). */
129
+ readonly mcpIssues: readonly McpIssue[];
130
+ /** `type: mcp_tool` hook actions that are incomplete or target an undeclared server. */
131
+ readonly mcpHookIssues: readonly McpHookIssue[];
132
+ /** Pairs of model-invocable skills whose descriptions are near-identical (precision collision). */
133
+ readonly descriptionOverlaps: readonly DescriptionOverlap[];
134
+ /** Skills/agents whose `---` block isn't valid YAML — informational (may still load via salvage). */
135
+ readonly malformedFrontmatter: readonly FrontmatterParseIssue[];
42
136
  readonly warnings: readonly string[];
43
137
  readonly untested: number;
138
+ /**
139
+ * Harness-level purity summary: how many scanned agents fall into each purity
140
+ * rung. A high `pure` count means more of the harness is statically testable
141
+ * (deterministic, no mocks); `unrestricted` is the blind-spot count.
142
+ * Computed by `effectSurface()` (one detector, no drift).
143
+ */
144
+ readonly puritySummary: {
145
+ pure: number;
146
+ bounded: number;
147
+ unrestricted: number;
148
+ };
149
+ }
150
+ /**
151
+ * Per-kind surface classifiers, built from the harness `PluginLayout`'s
152
+ * `skillDir`/`agentDir`/`commandDir` — so adding a harness whose subagents live
153
+ * somewhere other than `agents/` (OpenCode's `.opencode/agent`) needs no change
154
+ * here. Each anchors on a real path boundary (start-of-path or a `/`), so a
155
+ * directory whose NAME merely ends in the keyword isn't misclassified — e.g. the
156
+ * skill `skills/dispatching-parallel-agents/SKILL.md` must NOT register as an
157
+ * agent named "SKILL" (the `-agents/` substring), which real plugins like
158
+ * obra/superpowers ship. See scan.test.ts for the regression cases.
159
+ */
160
+ export interface SurfaceClassifier {
161
+ readonly isSkill: (f: string) => boolean;
162
+ readonly isAgent: (f: string) => boolean;
163
+ readonly isCommand: (f: string) => boolean;
44
164
  }
165
+ /**
166
+ * The description's dominant alphabetic script when it DIFFERS from `expected`
167
+ * (default `"Latin"`) — the cross-language trigger-risk signal. The model's
168
+ * skill-selection context is English-centric, so a description written mostly in
169
+ * another script may under-fire on English prompts. `expected` is a configurable
170
+ * default, not a value judgement: a Russian-targeted pack sets it to `"Cyrillic"`
171
+ * so its Cyrillic descriptions pass and an English one is flagged instead.
172
+ * Returns null when the dominant script IS the expected one (or there's no
173
+ * alphabetic content). Shared by `scan` and the future lint rule (one detector,
174
+ * no drift). The ≥20% guard avoids a near-empty string tripping on one letter.
175
+ */
176
+ export declare function unexpectedScript(text: string, expected?: Script): Script | null;
45
177
  /** Scan a plugin/repo directory and report its surfaces + structural issues. */
46
- export declare function scanPlugin(dir: string, layout?: PluginLayout): ScanReport;
178
+ export declare function scanPlugin(dir: string, layout?: PluginLayout, dialect?: HarnessDialect): ScanReport;
179
+ /**
180
+ * A plugin MARKETPLACE (`.claude-plugin/marketplace.json`) decomposed into its
181
+ * members. A marketplace either VENDORS its plugins in-tree (string `source`
182
+ * paths, e.g. wshobson/agents — `onDisk` populated) or CURATES external ones
183
+ * (object `source` with a git/url, e.g. obra/superpowers-marketplace,
184
+ * anthropics/claude-plugins-community — `external` populated, nothing on disk).
185
+ * Distinguishing the two lets `scan` report a curated marketplace honestly
186
+ * instead of mistaking it for an empty repo.
187
+ */
188
+ export interface MarketplaceInfo {
189
+ readonly name: string;
190
+ /** Member plugin dirs that exist on disk (string `source` paths). */
191
+ readonly onDisk: readonly string[];
192
+ /** Members referencing an off-disk source (url/git/github) — can't be scanned here. */
193
+ readonly external: number;
194
+ readonly total: number;
195
+ }
196
+ /**
197
+ * Read a `marketplace.json` beside the layout's plugin manifest and classify its
198
+ * members into on-disk vs external. Returns `null` when `dir` is not a
199
+ * marketplace. The source of truth behind {@link expandMarketplace} and the
200
+ * curated-marketplace report in `vigiles scan`.
201
+ */
202
+ export declare function inspectMarketplace(dir: string, layout?: PluginLayout): MarketplaceInfo | null;
203
+ /**
204
+ * If `dir` is a plugin MARKETPLACE (a `marketplace.json` beside the layout's
205
+ * plugin manifest, e.g. `.claude-plugin/marketplace.json`), expand it into the
206
+ * absolute dirs of its member plugins. Returns `null` when there's no
207
+ * marketplace, `[]` when it's a marketplace whose members are all external (not
208
+ * on disk). Used by `vigiles scan` to rank a whole marketplace — wshobson/agents
209
+ * alone ships 80+ plugins under one `marketplace.json`. See {@link inspectMarketplace}.
210
+ */
211
+ export declare function expandMarketplace(dir: string, layout?: PluginLayout): string[] | null;
47
212
  /** Format a scan report as human-readable text. */
48
213
  export declare function formatScanReport(r: ScanReport): string;
49
214
  //# sourceMappingURL=scan.d.ts.map