vigiles 33.1.0 → 33.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 (50) hide show
  1. package/dist/adapter-conformance.js +14 -10
  2. package/dist/adapter.d.ts +2 -0
  3. package/dist/adapters/claude-code/layout.js +3 -0
  4. package/dist/adapters/claude-code/output-style.d.ts +3 -0
  5. package/dist/adapters/claude-code/output-style.js +81 -0
  6. package/dist/adapters/claude-code/replies.d.ts +14 -0
  7. package/dist/adapters/claude-code/replies.js +78 -0
  8. package/dist/audit-report.d.ts +5 -0
  9. package/dist/audit-report.js +29 -20
  10. package/dist/audit-report.template.html +25 -25
  11. package/dist/audit-score.js +2 -2
  12. package/dist/check.d.ts +20 -0
  13. package/dist/check.js +87 -2
  14. package/dist/cli-main.js +25 -13
  15. package/dist/core/config-schema.d.ts +9 -0
  16. package/dist/core/config-schema.js +1 -0
  17. package/dist/core/eval-driver.d.ts +10 -0
  18. package/dist/core/frontmatter-read.d.ts +31 -0
  19. package/dist/core/frontmatter-read.js +4 -2
  20. package/dist/core/harness-driver.d.ts +2 -0
  21. package/dist/core/layout.d.ts +7 -0
  22. package/dist/core/output-style.d.ts +84 -0
  23. package/dist/core/output-style.js +90 -0
  24. package/dist/core/rule-meta.d.ts +1 -1
  25. package/dist/core/rule-meta.js +8 -0
  26. package/dist/core/types.d.ts +2 -0
  27. package/dist/coverage-evidence.d.ts +42 -0
  28. package/dist/coverage-evidence.js +39 -0
  29. package/dist/eval.d.ts +26 -3
  30. package/dist/eval.js +54 -5
  31. package/dist/harness-test.d.ts +30 -1
  32. package/dist/harness-test.js +65 -15
  33. package/dist/optimize.js +1 -5
  34. package/dist/output-style-arms.d.ts +27 -0
  35. package/dist/output-style-arms.js +46 -0
  36. package/dist/scan-files.js +1 -1
  37. package/dist/scan.d.ts +6 -0
  38. package/dist/scan.js +14 -4
  39. package/dist/score-core.d.ts +3 -0
  40. package/dist/score-core.js +5 -1
  41. package/dist/setup-plan.d.ts +1 -1
  42. package/dist/setup-plan.js +1 -0
  43. package/dist/test-coverage-files.d.ts +3 -1
  44. package/dist/test-coverage-files.js +20 -6
  45. package/dist/test-coverage.d.ts +18 -1
  46. package/dist/test-coverage.js +64 -14
  47. package/dist/test.d.ts +3 -1
  48. package/dist/test.js +8 -2
  49. package/dist/vigilesrc.schema.json +134 -0
  50. package/package.json +1 -1
@@ -21,6 +21,19 @@ const event_capability_js_1 = require("./core/event-capability.js");
21
21
  const dialect_js_1 = require("./core/dialect.js");
22
22
  const layout_js_1 = require("./core/layout.js");
23
23
  const tmp_root_js_1 = require("./core/tmp-root.js");
24
+ /** Every optional path a layout names, with the field it sits in. */
25
+ function optionalPaths(layout) {
26
+ return [
27
+ ["rulesDir", layout.rulesDir],
28
+ ["outputStyles.dir", layout.outputStyles?.dir],
29
+ ["hookScriptsDir", layout.hookScriptsDir],
30
+ ["hooksConventionPath", layout.hooksConventionPath],
31
+ ["userSurfaceRoot", layout.userSurfaceRoot],
32
+ ["surfaces.skill", layout.surfaces.skill],
33
+ ["surfaces.agent", layout.surfaces.agent],
34
+ ["surfaces.command", layout.surfaces.command],
35
+ ];
36
+ }
24
37
  /** Check an adapter against the port contracts; returns the (possibly empty) failure list. */
25
38
  function checkAdapterConformance(adapter) {
26
39
  const failures = [];
@@ -76,16 +89,7 @@ function checkAdapterConformance(adapter) {
76
89
  // second spelling of absent" rule is a test for every optional path. An
77
90
  // absent key already means "this harness has no such thing"; `""` would be a
78
91
  // second one, and every reader would have to remember to test for both.
79
- const optionalPaths = [
80
- ["rulesDir", adapter.layout.rulesDir],
81
- ["hookScriptsDir", adapter.layout.hookScriptsDir],
82
- ["hooksConventionPath", adapter.layout.hooksConventionPath],
83
- ["userSurfaceRoot", adapter.layout.userSurfaceRoot],
84
- ["surfaces.skill", adapter.layout.surfaces.skill],
85
- ["surfaces.agent", adapter.layout.surfaces.agent],
86
- ["surfaces.command", adapter.layout.surfaces.command],
87
- ];
88
- for (const [field, value] of optionalPaths)
92
+ for (const [field, value] of optionalPaths(adapter.layout))
89
93
  need(value === undefined || value.length > 0, `layout.${field} is "" — absence is spelled by omitting the key, never by an empty string`);
90
94
  // A7: `hooksConventionPath` names a FILE, and every reader treats it as one
91
95
  // (dirname it, parse it, round-trip it). `opencodeLayout` used to name the
package/dist/adapter.d.ts CHANGED
@@ -18,6 +18,8 @@
18
18
  export type { HarnessAdapter, AdapterCapabilities } from "./core/adapter.js";
19
19
  export type { HarnessDialect } from "./core/dialect.js";
20
20
  export type { PluginLayout, SurfaceDirs, SurfaceKind } from "./core/layout.js";
21
+ export type { OutputStyle, OutputStyleRules } from "./core/output-style.js";
22
+ export type { ModelRequest } from "./core/harness-driver.js";
21
23
  export { executableSourceDirs, materializePrefix, surfaceDirs, } from "./core/layout.js";
22
24
  /**
23
25
  * The instruction-chain vocabulary `PluginLayout.instructionChain` speaks.
@@ -15,6 +15,7 @@ const layout_js_1 = require("../../core/layout.js");
15
15
  const instruction_chain_js_1 = require("../../core/instruction-chain.js");
16
16
  const settings_codec_js_1 = require("../../core/settings-codec.js");
17
17
  const instruction_chain_js_2 = require("./instruction-chain.js");
18
+ const output_style_js_1 = require("./output-style.js");
18
19
  exports.claudeCodeLayout = {
19
20
  name: "claude-code",
20
21
  manifestPath: ".claude-plugin/plugin.json",
@@ -30,6 +31,8 @@ exports.claudeCodeLayout = {
30
31
  userSurfaceRoot: ".claude",
31
32
  // `.claude/rules/*.md` — path-scoped project instructions (see PluginLayout).
32
33
  rulesDir: "rules",
34
+ // `.claude/output-styles/**/*.md` — see output-style.ts for how a style is named and selected.
35
+ outputStyles: output_style_js_1.claudeCodeOutputStyles,
33
36
  // `.claude/settings.local.json` — the per-machine layer, gitignored by
34
37
  // `init`. Declared because only this adapter knows the word; Codex and
35
38
  // OpenCode name none, so none is synthesized for them.
@@ -0,0 +1,3 @@
1
+ import type { OutputStyleRules } from "../../core/output-style.js";
2
+ export declare const claudeCodeOutputStyles: OutputStyleRules;
3
+ //# sourceMappingURL=output-style.d.ts.map
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.claudeCodeOutputStyles = void 0;
4
+ /**
5
+ * How Claude Code keeps output styles and switches one on. Every rule here was
6
+ * measured on Claude Code 2.1.291; the tests beside this file hold the cases.
7
+ */
8
+ const node_path_1 = require("node:path");
9
+ // `yaml` is the lazy loader: this module hangs off the layout, which is on the
10
+ // hook path, so a top-level `js-yaml` import would load into every hook decision.
11
+ const frontmatter_read_js_1 = require("../../core/frontmatter-read.js");
12
+ /** Claude Code puts this line, then the body, into the first user message. */
13
+ const HEADER = "# Output Style: ";
14
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
15
+ /** YAML as Claude Code reads it: the core schema, and a repeated key keeps the last value. */
16
+ const parse = (block) => {
17
+ try {
18
+ const { CORE_SCHEMA, load } = (0, frontmatter_read_js_1.yaml)();
19
+ const value = load(block, { schema: CORE_SCHEMA, json: true });
20
+ if (value === null || value === undefined)
21
+ return {};
22
+ return isRecord(value) ? value : null;
23
+ }
24
+ catch (e) {
25
+ if (e instanceof (0, frontmatter_read_js_1.yaml)().YAMLException)
26
+ return null;
27
+ throw e;
28
+ }
29
+ };
30
+ /** `name: Status: Block` is not YAML, but Claude Code takes the rest of the line. */
31
+ const quoteColonValues = (block) => block
32
+ .split("\n")
33
+ .map((line) => {
34
+ const m = /^([A-Za-z0-9_-]+): (.*)$/.exec(line.replace(/\r$/, ""));
35
+ if (m === null)
36
+ return line;
37
+ const [, key = "", value = ""] = m;
38
+ const needsQuotes = /: |:$/.test(value) && !/^["'[{|>]/.test(value);
39
+ return needsQuotes ? `${key}: ${JSON.stringify(value)}` : line;
40
+ })
41
+ .join("\n");
42
+ const readBlock = (block) => parse(block) ?? parse(quoteColonValues(block));
43
+ /** A declared name if there is one, else the file name; null when unknowable. */
44
+ const nameFrom = (text, stem) => {
45
+ const { block } = (0, frontmatter_read_js_1.readFrontmatter)(text);
46
+ if (block === null)
47
+ return stem;
48
+ const data = readBlock(block);
49
+ if (data === null)
50
+ return null;
51
+ const name = data["name"];
52
+ if (typeof name === "string")
53
+ return name === "" ? stem : name;
54
+ if (typeof name === "number" || typeof name === "boolean")
55
+ return String(name);
56
+ return name === null || name === undefined ? stem : null;
57
+ };
58
+ const squash = (s) => s.replace(/\s+/g, " ").trim();
59
+ const read = (path, text) => ({
60
+ path,
61
+ name: nameFrom(text, (0, node_path_1.basename)(path).replace(/\.md$/, "")),
62
+ body: (0, frontmatter_read_js_1.frontmatterBody)(text).trim(),
63
+ });
64
+ /** The header and the whole body, in the system prompt or in any message. */
65
+ const reached = (style, request) => {
66
+ if (style.name === null)
67
+ return false;
68
+ const probe = squash(`${HEADER}${style.name}\n${style.body}`);
69
+ return [request.system, ...request.messages.map((m) => m.text)].some((text) => squash(text).includes(probe));
70
+ };
71
+ exports.claudeCodeOutputStyles = {
72
+ dir: "output-styles",
73
+ isStyleFile: (pathInDir) => pathInDir.endsWith(".md"),
74
+ read,
75
+ select: (name) => ({ outputStyle: name }),
76
+ // A user-wide `outputStyle` in `~/.claude/settings.json` loads in every
77
+ // project; the project's own `default` wins over it (measured on 2.1.292).
78
+ selectNone: { outputStyle: "default" },
79
+ reached,
80
+ };
81
+ //# sourceMappingURL=output-style.js.map
@@ -0,0 +1,14 @@
1
+ /** Whether the model answered at all: any `assistant` event, subagents included. */
2
+ export declare function reachedModel(streamJson: string): boolean;
3
+ /**
4
+ * The main agent's replies, one per message that ended a turn. A message
5
+ * streamed as several events (one per content block) is joined under its
6
+ * `message.id`. Left out: a subagent's messages (a non-null
7
+ * `parent_tool_use_id`); a message that also calls a tool, whose text is
8
+ * narration on the way to the call (measured on Claude Code 2.1.292: the
9
+ * narration and the `tool_use` share one id); a message with no text.
10
+ * `undefined` when the output is not a stream, so a check can say the run
11
+ * was not streamed instead of reading "no replies".
12
+ */
13
+ export declare function parseReplies(streamJson: string): readonly string[] | undefined;
14
+ //# sourceMappingURL=replies.d.ts.map
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reachedModel = reachedModel;
4
+ exports.parseReplies = parseReplies;
5
+ /**
6
+ * Every reply the agent ended a turn with, in order — read off Claude Code's
7
+ * `--output-format stream-json`. The `result` event carries only the LAST one,
8
+ * so a reply a Stop hook made the agent follow up on was invisible to a test,
9
+ * and with it anything that reply printed (a second status footer, measured on
10
+ * Claude Code 2.1.291: two `assistant` events, one `result` holding the second).
11
+ */
12
+ const zod_1 = require("zod");
13
+ /** The parts of an `assistant` stream line this reads; the rest is ignored. */
14
+ const AssistantLineSchema = zod_1.z.object({
15
+ type: zod_1.z.literal("assistant"),
16
+ parent_tool_use_id: zod_1.z.string().nullish(),
17
+ message: zod_1.z.object({
18
+ id: zod_1.z.string().optional(),
19
+ content: zod_1.z.array(zod_1.z.unknown()),
20
+ }),
21
+ });
22
+ const TextBlock = zod_1.z.object({ type: zod_1.z.literal("text"), text: zod_1.z.string() });
23
+ const textOf = (content) => content
24
+ .map((block) => TextBlock.safeParse(block))
25
+ .flatMap((r) => (r.success ? [r.data.text] : []))
26
+ .join("");
27
+ const parseJson = (line) => {
28
+ try {
29
+ return JSON.parse(line);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ };
35
+ const parseLine = (line) => {
36
+ const r = AssistantLineSchema.safeParse(parseJson(line));
37
+ return r.success ? r.data : null;
38
+ };
39
+ /** A stream-json run opens with a `system` event; plain `json` output has none. */
40
+ const StreamStartSchema = zod_1.z.object({ type: zod_1.z.literal("system") });
41
+ const isStream = (lines) => lines.some((l) => StreamStartSchema.safeParse(parseJson(l)).success ||
42
+ parseLine(l) !== null);
43
+ const ToolUseBlock = zod_1.z.object({ type: zod_1.z.literal("tool_use") });
44
+ /** Whether the model answered at all: any `assistant` event, subagents included. */
45
+ function reachedModel(streamJson) {
46
+ return streamJson.split("\n").some((l) => parseLine(l) !== null);
47
+ }
48
+ /**
49
+ * The main agent's replies, one per message that ended a turn. A message
50
+ * streamed as several events (one per content block) is joined under its
51
+ * `message.id`. Left out: a subagent's messages (a non-null
52
+ * `parent_tool_use_id`); a message that also calls a tool, whose text is
53
+ * narration on the way to the call (measured on Claude Code 2.1.292: the
54
+ * narration and the `tool_use` share one id); a message with no text.
55
+ * `undefined` when the output is not a stream, so a check can say the run
56
+ * was not streamed instead of reading "no replies".
57
+ */
58
+ function parseReplies(streamJson) {
59
+ const lines = streamJson.split("\n");
60
+ if (!isStream(lines))
61
+ return undefined;
62
+ const events = lines.map(parseLine).flatMap((e, i) => e !== null && (e.parent_tool_use_id ?? null) === null
63
+ ? [
64
+ {
65
+ id: e.message.id ?? `#${String(i)}`,
66
+ text: textOf(e.message.content),
67
+ callsTool: e.message.content.some((b) => ToolUseBlock.safeParse(b).success),
68
+ },
69
+ ]
70
+ : []);
71
+ const ids = [...new Set(events.map((e) => e.id))];
72
+ return ids
73
+ .map((id) => events.filter((e) => e.id === id))
74
+ .filter((parts) => !parts.some((e) => e.callsTool))
75
+ .map((parts) => parts.map((e) => e.text).join(""))
76
+ .filter((text) => text !== "");
77
+ }
78
+ //# sourceMappingURL=replies.js.map
@@ -55,6 +55,11 @@ export interface AuditInventory {
55
55
  readonly agents: number;
56
56
  readonly hooks: number;
57
57
  readonly commands: number;
58
+ /**
59
+ * Output styles found, or `"not-supported"` when the harness has none.
60
+ * Omitted when there are zero, so a report without styles is unchanged.
61
+ */
62
+ readonly outputStyles?: number | "not-supported";
58
63
  readonly mcp: boolean;
59
64
  /** Surfaces covered by NEITHER tier — the union count (unchanged). */
60
65
  readonly untested: number;
@@ -46,6 +46,34 @@ function buildAdoptable(surfaces) {
46
46
  createAllCommand: CREATE_ALL_COMMAND,
47
47
  };
48
48
  }
49
+ /** What the scanned harness ships, as the report's `inventory`. */
50
+ function inventoryOf(report) {
51
+ return {
52
+ skills: report.skills.length,
53
+ agents: report.agents.length,
54
+ // All hooks, file-backed + inline — matches formatScanReport and the
55
+ // emptiness/scoring count, so a JSON/HTML "What it ships" never reports 0
56
+ // hooks for an inline-hook-only harness.
57
+ hooks: report.hooks.length + report.inlineHooks,
58
+ commands: report.commands,
59
+ ...outputStylesField(report.outputStyles),
60
+ mcp: report.mcp,
61
+ untested: report.untested,
62
+ ...(report.untestedHarness !== undefined
63
+ ? { untestedHarness: report.untestedHarness }
64
+ : {}),
65
+ ...(report.unevaluated !== undefined
66
+ ? { unevaluated: report.unevaluated }
67
+ : {}),
68
+ ...(report.coverageEvidence
69
+ ? { coverageEvidence: report.coverageEvidence }
70
+ : {}),
71
+ };
72
+ }
73
+ /** The inventory's `outputStyles` entry: present unless there are none to report. */
74
+ function outputStylesField(n) {
75
+ return n === undefined || n === 0 ? {} : { outputStyles: n };
76
+ }
49
77
  /**
50
78
  * Assemble the versioned {@link AuditReport} from a scan report — pure, no clock.
51
79
  * The CLI attaches `meta.generatedAt` when it writes the JSON artifact; the
@@ -68,26 +96,7 @@ function buildAuditReport(report, opts) {
68
96
  score,
69
97
  verdict,
70
98
  recommendations,
71
- inventory: {
72
- skills: report.skills.length,
73
- agents: report.agents.length,
74
- // All hooks, file-backed + inline — matches formatScanReport and the
75
- // emptiness/scoring count, so a JSON/HTML "What it ships" never reports 0
76
- // hooks for an inline-hook-only harness.
77
- hooks: report.hooks.length + report.inlineHooks,
78
- commands: report.commands,
79
- mcp: report.mcp,
80
- untested: report.untested,
81
- ...(report.untestedHarness !== undefined
82
- ? { untestedHarness: report.untestedHarness }
83
- : {}),
84
- ...(report.unevaluated !== undefined
85
- ? { unevaluated: report.unevaluated }
86
- : {}),
87
- ...(report.coverageEvidence
88
- ? { coverageEvidence: report.coverageEvidence }
89
- : {}),
90
- },
99
+ inventory: inventoryOf(report),
91
100
  ...(report.danglingRefs.length
92
101
  ? { brokenReferences: report.danglingRefs }
93
102
  : {}),