vigiles 8.0.0 → 9.1.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.
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyDraftedRefs = verifyDraftedRefs;
4
+ exports.parseDraftJson = parseDraftJson;
5
+ exports.runAdoptabilityTier = runAdoptabilityTier;
6
+ exports.formatAdoptability = formatAdoptability;
7
+ /**
8
+ * Adoptability preview — "what would vigiles catch in YOUR repo?"
9
+ *
10
+ * The audit's adoption front door for a NON-adopter: instead of grading the hygiene
11
+ * of an already-adopted spec, it shows the concrete bugs a spec WOULD catch today.
12
+ *
13
+ * Architecture (research/adoption-gateway-preview.md): **LLM proposes, deterministic
14
+ * disposes.** A model DRAFTS the verifiable references in an instruction file (high
15
+ * recall, incl. prose intent a regex can't see — `draftRefs`); the deterministic
16
+ * cross-reference engine VERIFIES each one (`verifyDraftedRefs`, reusing
17
+ * `checkLinterRule` + the compile validators). The model never gets to assert a
18
+ * pass — only the verifier does — so the "M broken right now" number is trustworthy
19
+ * even though the extraction was probabilistic.
20
+ *
21
+ * The verifier + parser + formatter are pure and model-free (fully unit-tested); the
22
+ * single real model call (`defaultDraft`) is the only v8-ignored seam, injected so
23
+ * the orchestration is testable without a model.
24
+ */
25
+ const linters_js_1 = require("./core/linters.js");
26
+ const compile_js_1 = require("./core/compile.js");
27
+ const hash_js_1 = require("./core/hash.js");
28
+ const eval_js_1 = require("./eval.js");
29
+ /** Verify ONE drafted ref against the real repo; null = resolves, else the breakage. */
30
+ function verifyOne(r, basePath) {
31
+ switch (r.kind) {
32
+ case "enforce": {
33
+ const res = (0, linters_js_1.checkLinterRule)(r.ref, basePath);
34
+ if (!res.exists)
35
+ return {
36
+ ...r,
37
+ issue: res.error ?? `linter rule "${r.ref}" does not exist`,
38
+ };
39
+ if (res.enabled === "disabled")
40
+ return { ...r, issue: `rule "${r.ref}" exists but is not enabled` };
41
+ return null;
42
+ }
43
+ case "file": {
44
+ const e = (0, compile_js_1.validateFileRef)(r.ref, basePath);
45
+ return e ? { kind: r.kind, ref: r.ref, issue: e.message } : null;
46
+ }
47
+ case "cmd": {
48
+ const e = (0, compile_js_1.validateCommandRef)(r.ref, basePath);
49
+ return e ? { kind: r.kind, ref: r.ref, issue: e.message } : null;
50
+ }
51
+ case "dir": {
52
+ const e = (0, compile_js_1.validateDirRef)(r.ref, basePath);
53
+ return e ? { kind: r.kind, ref: r.ref, issue: e.message } : null;
54
+ }
55
+ default:
56
+ return (0, hash_js_1.assertNever)(r.kind);
57
+ }
58
+ }
59
+ /**
60
+ * Deterministic verdict over drafted refs — the "disposes" half. Dedupes
61
+ * (kind+ref), routes each to the real cross-ref/filesystem check, and counts the
62
+ * broken. Pure: a hallucinated rule resolves to broken, never trusted as a pass.
63
+ */
64
+ function verifyDraftedRefs(refs, basePath) {
65
+ const seen = new Set();
66
+ const unique = [];
67
+ for (const r of refs) {
68
+ const key = `${r.kind}:${r.ref}`;
69
+ if (seen.has(key))
70
+ continue;
71
+ seen.add(key);
72
+ unique.push(r);
73
+ }
74
+ const brokenRefs = unique
75
+ .map((r) => verifyOne(r, basePath))
76
+ .filter((b) => b !== null);
77
+ return { total: unique.length, broken: brokenRefs.length, brokenRefs };
78
+ }
79
+ const VALID_KINDS = new Set([
80
+ "enforce",
81
+ "file",
82
+ "cmd",
83
+ "dir",
84
+ ]);
85
+ /**
86
+ * Tolerant parse of the model's draft output into `DraftedRef[]`. The model is
87
+ * asked for a bare JSON array, but tolerate prose-wrapped / fenced output by
88
+ * extracting the outermost `[...]`. Drops any entry with an unknown kind or a
89
+ * non-string ref (the verifier is the guard, but a malformed shape is just noise).
90
+ */
91
+ function parseDraftJson(text) {
92
+ const raw = extractJsonArray(text);
93
+ if (raw === null)
94
+ return [];
95
+ let parsed;
96
+ try {
97
+ parsed = JSON.parse(raw);
98
+ }
99
+ catch {
100
+ return [];
101
+ }
102
+ if (!Array.isArray(parsed))
103
+ return [];
104
+ const out = [];
105
+ for (const item of parsed) {
106
+ if (typeof item !== "object" || item === null)
107
+ continue;
108
+ const rec = item;
109
+ const kind = rec.kind;
110
+ const ref = rec.ref;
111
+ if (typeof kind === "string" &&
112
+ VALID_KINDS.has(kind) &&
113
+ typeof ref === "string" &&
114
+ ref.trim()) {
115
+ out.push({ kind: kind, ref: ref.trim() });
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+ /** Pull the outermost `[...]` from a possibly prose/fence-wrapped string. */
121
+ function extractJsonArray(text) {
122
+ const start = text.indexOf("[");
123
+ const end = text.lastIndexOf("]");
124
+ if (start === -1 || end === -1 || end < start)
125
+ return null;
126
+ return text.slice(start, end + 1);
127
+ }
128
+ /** The drafting prompt — reuse the strengthen/adopt-spec mapping intent. */
129
+ function draftPrompt(content) {
130
+ return [
131
+ "You are evaluating whether a coding-agent instruction file's references can be",
132
+ "machine-verified. Read the instruction file and identify every reference to a",
133
+ "CONCRETE, VERIFIABLE artifact:",
134
+ '- a linter rule (kind "enforce", ref like "eslint/no-console" or',
135
+ ' "@typescript-eslint/no-floating-promises") — INCLUDING prose intent you can',
136
+ ' confidently map to a real rule (e.g. "always await promises" ->',
137
+ ' "@typescript-eslint/no-floating-promises", "no console.log" -> "eslint/no-console").',
138
+ '- a file path (kind "file", ref like "src/index.ts").',
139
+ '- an npm script (kind "cmd", ref like "npm run build" or "npm test").',
140
+ '- a directory (kind "dir", ref like "src/components").',
141
+ "",
142
+ 'Output ONLY a JSON array of {"kind","ref"} objects — no markdown, no prose. If',
143
+ "none, output []. Do not invent references that aren't grounded in the text.",
144
+ "",
145
+ "Instruction file:",
146
+ "---",
147
+ content,
148
+ "---",
149
+ ].join("\n");
150
+ }
151
+ /* v8 ignore start — the single real model call; the orchestration is tested with a fake draft. */
152
+ /** The "proposes" half: one model call drafting the verifiable refs from prose. */
153
+ async function defaultDraft(content, opts = {}) {
154
+ const runner = opts.runner ?? eval_js_1.spawnAgent;
155
+ const parse = opts.parse ?? eval_js_1.parseClaudeRun;
156
+ const out = await runner({
157
+ task: draftPrompt(content),
158
+ cwd: opts.cwd ?? process.cwd(),
159
+ model: opts.model ?? "sonnet",
160
+ tools: [], // the content is inline — no file tools needed (deterministic-ish)
161
+ hasSettings: false,
162
+ pluginDir: undefined,
163
+ timeoutMs: 120000,
164
+ env: process.env,
165
+ });
166
+ return parseDraftJson(parse(out).output);
167
+ }
168
+ /**
169
+ * Run the preview: draft refs from the instruction file (model), then verify them
170
+ * (deterministic). The composition root of "LLM proposes, deterministic disposes".
171
+ */
172
+ async function runAdoptabilityTier(opts) {
173
+ const draft = opts.draft ?? ((c) => defaultDraft(c));
174
+ const refs = await draft(opts.instructionContent);
175
+ return verifyDraftedRefs(refs, opts.basePath);
176
+ }
177
+ /** Terminal section — the adoption invitation, not a graded ring. */
178
+ function formatAdoptability(r, instructionFile) {
179
+ const lines = ["Adoptability — what vigiles would lock in"];
180
+ if (r.total === 0) {
181
+ lines.push(` no machine-verifiable references found in ${instructionFile}.`);
182
+ return lines.join("\n");
183
+ }
184
+ lines.push(` vigiles drafted a spec from ${instructionFile}: ${String(r.total)} verifiable reference(s) found`);
185
+ if (r.broken === 0) {
186
+ lines.push(" ✓ all resolve right now — adopt a spec to keep it that way.");
187
+ return lines.join("\n");
188
+ }
189
+ lines.push(` ${String(r.broken)} broken right now:`);
190
+ for (const b of r.brokenRefs) {
191
+ lines.push(` ✗ ${b.issue}`);
192
+ }
193
+ lines.push(" → run `vigiles init` to adopt the spec and catch these at edit time.");
194
+ return lines.join("\n");
195
+ }
196
+ //# sourceMappingURL=adoptability.js.map
@@ -0,0 +1,20 @@
1
+ import type { AuditReport } from "./audit-report.js";
2
+ /**
3
+ * Candidate locations for the built template, relative to this module (`__dirname`
4
+ * is the compiled `dist/` at runtime, or `src/` under vitest). CommonJS output, so
5
+ * we use `__dirname`, not `import.meta`.
6
+ */
7
+ export declare function templatePath(): string | null;
8
+ /**
9
+ * Inject the report JSON into a template by replacing the quoted placeholder
10
+ * string with the JSON object literal. Pure — the testable core. Throws if the
11
+ * template is missing the placeholder.
12
+ */
13
+ export declare function injectReportData(template: string, report: AuditReport): string;
14
+ /**
15
+ * Render the self-contained HTML report (React template + injected data). Throws
16
+ * if the template hasn't been built — the caller (writeAuditHtml) catches that and
17
+ * skips the HTML, since the JSON + terminal report don't depend on it.
18
+ */
19
+ export declare function renderAuditHtml(report: AuditReport): string;
20
+ //# sourceMappingURL=audit-html.d.ts.map
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.templatePath = templatePath;
4
+ exports.injectReportData = injectReportData;
5
+ exports.renderAuditHtml = renderAuditHtml;
6
+ /**
7
+ * The shareable HTML audit report — the prebuilt **Vite + React + shadcn** template
8
+ * (`report/`, built to one self-contained file at `dist/audit-report.template.html`)
9
+ * with the {@link AuditReport} JSON injected. The React app runs in the reader's
10
+ * browser, so the CLI stays runtime-dependency-light and the output is still a
11
+ * single offline file. There is ONE renderer (pure shadcn/Tailwind) — no inline-CSS
12
+ * fallback; the build guarantees the template exists, and if it somehow doesn't the
13
+ * caller skips the HTML (the JSON + terminal report still work).
14
+ *
15
+ * `<`/`>`/`&` are escaped on injection so report text can never break out of the
16
+ * `<script>`. `injectReportData` is the pure, testable core.
17
+ */
18
+ const node_fs_1 = require("node:fs");
19
+ const node_path_1 = require("node:path");
20
+ const PLACEHOLDER = "__VIGILES_DATA_PLACEHOLDER__";
21
+ /**
22
+ * Candidate locations for the built template, relative to this module (`__dirname`
23
+ * is the compiled `dist/` at runtime, or `src/` under vitest). CommonJS output, so
24
+ * we use `__dirname`, not `import.meta`.
25
+ */
26
+ function templatePath() {
27
+ const candidates = [
28
+ (0, node_path_1.resolve)(__dirname, "audit-report.template.html"), // dist/ (shipped)
29
+ (0, node_path_1.resolve)(__dirname, "..", "dist", "audit-report.template.html"), // src/ under vitest
30
+ ];
31
+ return candidates.find((p) => (0, node_fs_1.existsSync)(p)) ?? null;
32
+ }
33
+ /** Escape `<`, `>`, `&` so report text can never break out of the `<script>`. */
34
+ function escapeForScript(json) {
35
+ return json.replace(/[<>&]/g, (ch) => "\\u00" + ch.charCodeAt(0).toString(16).padStart(2, "0"));
36
+ }
37
+ /**
38
+ * Inject the report JSON into a template by replacing the quoted placeholder
39
+ * string with the JSON object literal. Pure — the testable core. Throws if the
40
+ * template is missing the placeholder.
41
+ */
42
+ function injectReportData(template, report) {
43
+ const re = new RegExp(`(["'])${PLACEHOLDER}\\1`);
44
+ if (!re.test(template)) {
45
+ throw new Error("audit report template is missing the data placeholder");
46
+ }
47
+ return template.replace(re, escapeForScript(JSON.stringify(report)));
48
+ }
49
+ /**
50
+ * Render the self-contained HTML report (React template + injected data). Throws
51
+ * if the template hasn't been built — the caller (writeAuditHtml) catches that and
52
+ * skips the HTML, since the JSON + terminal report don't depend on it.
53
+ */
54
+ function renderAuditHtml(report) {
55
+ const p = templatePath();
56
+ if (!p) {
57
+ throw new Error("audit report template not built — run `npm run build` (builds report/), or use --json / --no-html");
58
+ }
59
+ return injectReportData((0, node_fs_1.readFileSync)(p, "utf-8"), report);
60
+ }
61
+ //# sourceMappingURL=audit-html.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Auto-generated trigger probes for `vigiles audit`'s model trigger tier.
3
+ *
4
+ * The trigger-rate eval needs a per-skill prompt set (does the description FIRE?
5
+ * — recall + precision). Authoring that set by hand was the friction that made
6
+ * the eval un-wowable. The trigger tier removes it: derive a small, DIVERSE probe
7
+ * set from each skill's own description — zero setup. `--prompts=<file>` still
8
+ * overrides for a rigorous, curated benchmark.
9
+ *
10
+ * Deterministic by design (no model needed to AUTHOR the probes — the model is
11
+ * spent RUNNING them). The trick that clears the diversity gate: extract a SHORT
12
+ * topic from the description (so the shared text stays small relative to the
13
+ * frame) and wrap it in lexically-distant frames. Measured min pairwise NCD
14
+ * ~0.27 across short/long descriptions — comfortably above {@link AUTO_MIN_DISTANCE}.
15
+ */
16
+ import type { TriggerPromptSet } from "./scan-behavioral.js";
17
+ export interface PromptSkill {
18
+ readonly name: string;
19
+ readonly description: string;
20
+ }
21
+ /** How many recall probes we generate per skill (each a distinct frame). */
22
+ export declare const AUTO_RECALL_COUNT = 6;
23
+ /**
24
+ * The diversity floor for AUTO probes — relaxed from the default 0.3 because a
25
+ * templated-but-varied machine probe legitimately shares a topic phrase (the
26
+ * generator's measured min is ~0.27). Still well above 0 → genuine copy-paste
27
+ * is caught; the gate's "vary the phrasing" advice is for hand-authored sets.
28
+ */
29
+ export declare const AUTO_MIN_DISTANCE = 0.2;
30
+ /**
31
+ * Extract a short, action-shaped topic from a description: drop boilerplate
32
+ * lead-ins ("A skill that…", "Use this skill to…"), take the first clause, cap
33
+ * at 8 words. Capping is load-bearing — a long verbatim topic makes the frames
34
+ * too similar (NCD collapses below the gate).
35
+ */
36
+ export declare function topicOf(description: string): string;
37
+ /** Recall probes for one skill: distinct frames around its extracted topic. */
38
+ export declare function recallPrompts(description: string, count?: number): string[];
39
+ /**
40
+ * Build a {@link TriggerPromptSet} from skill descriptions — zero-setup trigger
41
+ * probes. Each skill gets `recallPrompts` derived from its description plus the
42
+ * shared irrelevant bank for precision. Skills with an empty description are
43
+ * skipped (nothing to derive a topic from).
44
+ */
45
+ export declare function autoTriggerPrompts(skills: readonly PromptSkill[], count?: number): TriggerPromptSet;
46
+ //# sourceMappingURL=audit-prompts.d.ts.map
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTO_MIN_DISTANCE = exports.AUTO_RECALL_COUNT = void 0;
4
+ exports.topicOf = topicOf;
5
+ exports.recallPrompts = recallPrompts;
6
+ exports.autoTriggerPrompts = autoTriggerPrompts;
7
+ /** How many recall probes we generate per skill (each a distinct frame). */
8
+ exports.AUTO_RECALL_COUNT = 6;
9
+ /**
10
+ * The diversity floor for AUTO probes — relaxed from the default 0.3 because a
11
+ * templated-but-varied machine probe legitimately shares a topic phrase (the
12
+ * generator's measured min is ~0.27). Still well above 0 → genuine copy-paste
13
+ * is caught; the gate's "vary the phrasing" advice is for hand-authored sets.
14
+ */
15
+ exports.AUTO_MIN_DISTANCE = 0.2;
16
+ // Lexically-distant frames around a short topic. Order matters: the first N are
17
+ // used, and they're arranged so any prefix stays diverse (verified in the test).
18
+ const RECALL_FRAMES = [
19
+ (t) => `I need help to ${t} in my project right now.`,
20
+ (t) => `How do I ${t}? Walk me through the steps.`,
21
+ (t) => `Please ${t} before I open this pull request.`,
22
+ (t) => `What's the recommended way to ${t} on a large team?`,
23
+ (t) => `Can you take a look and ${t} for me?`,
24
+ (t) => `My task today: ${t} across the whole repo.`,
25
+ (t) => `Is there a tool that will ${t} automatically?`,
26
+ (t) => `Give me a checklist to ${t} thoroughly.`,
27
+ ];
28
+ // Unrelated requests for the precision arm — varied, clearly off-topic, so a
29
+ // well-scoped skill should NOT fire on them (a too-broad description that hijacks
30
+ // these fails precision). Generic on purpose, distant from any one skill's topic.
31
+ // Count is load-bearing: the auto trigger tier applies the diversity gate's
32
+ // `minPrompts` floor (= AUTO_RECALL_COUNT) to BOTH arms, so the bank must hold at
33
+ // least AUTO_RECALL_COUNT entries or the precision arm fails preflight and every
34
+ // skill reports "unmeasured" instead of running.
35
+ const IRRELEVANT_BANK = [
36
+ "What's the weather forecast for Tokyo this weekend?",
37
+ "Summarize the plot of Hamlet in two sentences.",
38
+ "Convert 100 US dollars to euros at today's rate.",
39
+ "Recommend a good pasta recipe for dinner tonight.",
40
+ "Who won the most Olympic gold medals in swimming?",
41
+ "Explain how photosynthesis works in plants.",
42
+ "Suggest a weekend hiking trail near Seattle.",
43
+ ];
44
+ // Boilerplate lead-in words that carry no topical signal (skill descriptions
45
+ // open with a verb — "Reviews…", "Generate…" — so stripping these from the front
46
+ // never eats the real action). Applied iteratively until a content word remains.
47
+ const LEAD_WORD = /^(a|an|the|this|use|skill|agent|tool|command|helper|that|which|to|for|when|invoked?|invoke|used?|helps?|you|with)\b[\s,:-]*/i;
48
+ /**
49
+ * Extract a short, action-shaped topic from a description: drop boilerplate
50
+ * lead-ins ("A skill that…", "Use this skill to…"), take the first clause, cap
51
+ * at 8 words. Capping is load-bearing — a long verbatim topic makes the frames
52
+ * too similar (NCD collapses below the gate).
53
+ */
54
+ function topicOf(description) {
55
+ let t = description.trim().toLowerCase();
56
+ let prev = "";
57
+ while (t !== prev) {
58
+ prev = t;
59
+ t = t.replace(LEAD_WORD, "");
60
+ }
61
+ const firstClause = t.split(/[.,;:!?]/)[0].trim();
62
+ const words = firstClause.split(/\s+/).filter(Boolean).slice(0, 8);
63
+ const topic = words.join(" ");
64
+ // Fall back to the raw (capped, lowercased) description if stripping left nothing.
65
+ return (topic || description.trim().toLowerCase().split(/\s+/).slice(0, 8).join(" "));
66
+ }
67
+ /** Recall probes for one skill: distinct frames around its extracted topic. */
68
+ function recallPrompts(description, count = exports.AUTO_RECALL_COUNT) {
69
+ const topic = topicOf(description);
70
+ return RECALL_FRAMES.slice(0, count).map((frame) => frame(topic));
71
+ }
72
+ /**
73
+ * Build a {@link TriggerPromptSet} from skill descriptions — zero-setup trigger
74
+ * probes. Each skill gets `recallPrompts` derived from its description plus the
75
+ * shared irrelevant bank for precision. Skills with an empty description are
76
+ * skipped (nothing to derive a topic from).
77
+ */
78
+ function autoTriggerPrompts(skills, count = exports.AUTO_RECALL_COUNT) {
79
+ const set = {};
80
+ for (const s of skills) {
81
+ if (!s.description.trim())
82
+ continue;
83
+ set[s.name] = {
84
+ prompts: recallPrompts(s.description, count),
85
+ irrelevant: [...IRRELEVANT_BANK],
86
+ };
87
+ }
88
+ return set;
89
+ }
90
+ //# sourceMappingURL=audit-prompts.js.map
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The `AuditReport` — the versioned JSON contract that IS the audit's product
3
+ * boundary. Everything renders FROM it: the local self-contained HTML report,
4
+ * `audit --json` for CI, and (later) an upload to a hosted dashboard. Because it's
5
+ * the wire format between the CLI and anything downstream, it is VERSIONED
6
+ * (`meta.schemaVersion`) and stable — additive changes only within a version.
7
+ *
8
+ * Pure: `buildAuditReport` assembles the report from the same deterministic pieces
9
+ * the terminal output uses (`auditScore` + `optimize` + the scan inventory) — no
10
+ * re-detection (one-detector-no-drift), no model, no clock (a `generatedAt`
11
+ * timestamp is attached by the CLI at write time, never by this pure builder, so
12
+ * the embedded-in-HTML form stays deterministic).
13
+ */
14
+ import { type AuditScore } from "./audit-score.js";
15
+ import { type Recommendation } from "./optimize.js";
16
+ import type { AdoptabilityResult } from "./adoptability.js";
17
+ import type { ScanReport } from "./scan.js";
18
+ /** The current schema version. Bump only on a BREAKING change to the shape. */
19
+ export declare const AUDIT_SCHEMA_VERSION = 1;
20
+ export interface AuditReportMeta {
21
+ /** Wire-format version — consumers gate on this. */
22
+ readonly schemaVersion: typeof AUDIT_SCHEMA_VERSION;
23
+ readonly tool: "vigiles";
24
+ /** The vigiles version that produced the report. */
25
+ readonly vigilesVersion: string;
26
+ /** The detected/selected harness (`claude-code`, `codex`, …). */
27
+ readonly harness: string;
28
+ /** The audited directory. */
29
+ readonly dir: string;
30
+ /** ISO-8601 produced-at stamp — set by the CLI at write time (NOT the pure builder). */
31
+ readonly generatedAt?: string;
32
+ }
33
+ /** What the harness ships — the "inventory" surface counts. */
34
+ export interface AuditInventory {
35
+ readonly skills: number;
36
+ readonly agents: number;
37
+ readonly hooks: number;
38
+ readonly commands: number;
39
+ readonly mcp: boolean;
40
+ readonly untested: number;
41
+ }
42
+ /**
43
+ * A surface (skill / subagent / instruction file) that EXISTS but doesn't yet
44
+ * have a `.spec.ts` — so it can be adopted into a typed spec. The report can't
45
+ * write files (it's a browser app), so it EMITS the exact CLI command instead.
46
+ */
47
+ export interface AdoptableSurface {
48
+ /** The repo-relative path of the surface (e.g. `skills/foo/SKILL.md`). */
49
+ readonly path: string;
50
+ /** The exact command that adopts this one surface. */
51
+ readonly command: string;
52
+ }
53
+ /**
54
+ * The adoptable-surfaces list + the "create all" command — the data the report's
55
+ * "Create spec" / "Create all specs" affordances copy to the clipboard. Present
56
+ * only when there's at least one un-spec'd surface; the CLI computes the surface
57
+ * paths (the layout-aware `discoverAdoptableSurfaces`) and passes them in, so the
58
+ * pure builder stays adapter-agnostic.
59
+ */
60
+ export interface Adoptable {
61
+ readonly surfaces: readonly AdoptableSurface[];
62
+ /** The one command that adopts every surface at once. */
63
+ readonly createAllCommand: string;
64
+ }
65
+ /**
66
+ * The full audit, as the dashboard / CI / HTML all consume it. Self-describing
67
+ * and versioned; additive-only within a `schemaVersion`.
68
+ */
69
+ export interface AuditReport {
70
+ readonly meta: AuditReportMeta;
71
+ /** The four deterministic category rings + the weighted overall + grade. */
72
+ readonly score: AuditScore;
73
+ /** The deterministic, ranked fixes (the inline recommendations). */
74
+ readonly recommendations: readonly Recommendation[];
75
+ readonly inventory: AuditInventory;
76
+ /**
77
+ * The adoption preview — "what would vigiles catch in your repo?" Present only
78
+ * when the model-gated tier ran (behind consent); a deterministic read omits it.
79
+ * Additive/optional, so the schema version is unchanged.
80
+ */
81
+ readonly adoptability?: AdoptabilityResult;
82
+ /**
83
+ * The surfaces that exist but aren't spec-managed yet, each with the command
84
+ * that adopts it, plus a "create all" command. Drives the report's "Create
85
+ * spec" / "Create all specs" command-emit buttons. Present only when there's
86
+ * at least one adoptable surface. Additive/optional — schema version unchanged.
87
+ */
88
+ readonly adoptable?: Adoptable;
89
+ }
90
+ export interface BuildAuditReportOptions {
91
+ readonly harness: string;
92
+ readonly vigilesVersion: string;
93
+ /**
94
+ * The repo-relative paths of surfaces that exist but have no `.spec.ts` yet,
95
+ * computed by the CLI's layout-aware `discoverAdoptableSurfaces` (so the pure
96
+ * builder stays adapter-agnostic — it only formats the commands). Omit/empty
97
+ * when there's nothing to adopt.
98
+ */
99
+ readonly adoptableSurfaces?: readonly string[];
100
+ }
101
+ /**
102
+ * Assemble the versioned {@link AuditReport} from a scan report — pure, no clock.
103
+ * The CLI attaches `meta.generatedAt` when it writes the JSON artifact; the
104
+ * HTML-embedded form omits it so the rendered file stays deterministic.
105
+ */
106
+ export declare function buildAuditReport(report: ScanReport, opts: BuildAuditReportOptions): AuditReport;
107
+ //# sourceMappingURL=audit-report.d.ts.map
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUDIT_SCHEMA_VERSION = void 0;
4
+ exports.buildAuditReport = buildAuditReport;
5
+ /**
6
+ * The `AuditReport` — the versioned JSON contract that IS the audit's product
7
+ * boundary. Everything renders FROM it: the local self-contained HTML report,
8
+ * `audit --json` for CI, and (later) an upload to a hosted dashboard. Because it's
9
+ * the wire format between the CLI and anything downstream, it is VERSIONED
10
+ * (`meta.schemaVersion`) and stable — additive changes only within a version.
11
+ *
12
+ * Pure: `buildAuditReport` assembles the report from the same deterministic pieces
13
+ * the terminal output uses (`auditScore` + `optimize` + the scan inventory) — no
14
+ * re-detection (one-detector-no-drift), no model, no clock (a `generatedAt`
15
+ * timestamp is attached by the CLI at write time, never by this pure builder, so
16
+ * the embedded-in-HTML form stays deterministic).
17
+ */
18
+ const audit_score_js_1 = require("./audit-score.js");
19
+ const optimize_js_1 = require("./optimize.js");
20
+ /** The current schema version. Bump only on a BREAKING change to the shape. */
21
+ exports.AUDIT_SCHEMA_VERSION = 1;
22
+ /** The one command that adopts every un-spec'd surface (bare `init`). */
23
+ const CREATE_ALL_COMMAND = "npx vigiles init";
24
+ /** The command that adopts ONE surface at a given repo-relative path. */
25
+ function adoptCommand(path) {
26
+ return `npx vigiles init --target=${path}`;
27
+ }
28
+ /**
29
+ * Build the {@link Adoptable} payload from the layout-aware surface paths — pure,
30
+ * just formats the per-surface + create-all commands. Returns `undefined` when
31
+ * there's nothing to adopt (so the field stays absent).
32
+ */
33
+ function buildAdoptable(surfaces) {
34
+ if (!surfaces || surfaces.length === 0)
35
+ return undefined;
36
+ return {
37
+ surfaces: surfaces.map((path) => ({ path, command: adoptCommand(path) })),
38
+ createAllCommand: CREATE_ALL_COMMAND,
39
+ };
40
+ }
41
+ /**
42
+ * Assemble the versioned {@link AuditReport} from a scan report — pure, no clock.
43
+ * The CLI attaches `meta.generatedAt` when it writes the JSON artifact; the
44
+ * HTML-embedded form omits it so the rendered file stays deterministic.
45
+ */
46
+ function buildAuditReport(report, opts) {
47
+ const adoptable = buildAdoptable(opts.adoptableSurfaces);
48
+ return {
49
+ meta: {
50
+ schemaVersion: exports.AUDIT_SCHEMA_VERSION,
51
+ tool: "vigiles",
52
+ vigilesVersion: opts.vigilesVersion,
53
+ harness: opts.harness,
54
+ dir: report.dir,
55
+ },
56
+ score: (0, audit_score_js_1.auditScore)(report),
57
+ recommendations: (0, optimize_js_1.optimize)(report).recommendations,
58
+ inventory: {
59
+ skills: report.skills.length,
60
+ agents: report.agents.length,
61
+ // All hooks, file-backed + inline — matches formatScanReport and the
62
+ // emptiness/scoring count, so a JSON/HTML "What it ships" never reports 0
63
+ // hooks for an inline-hook-only harness.
64
+ hooks: report.hooks.length + report.inlineHooks,
65
+ commands: report.commands,
66
+ mcp: report.mcp,
67
+ untested: report.untested,
68
+ },
69
+ ...(adoptable ? { adoptable } : {}),
70
+ };
71
+ }
72
+ //# sourceMappingURL=audit-report.js.map