vigiles 29.1.0 → 30.0.1

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 (122) hide show
  1. package/dist/adapter-conformance.d.ts +1 -1
  2. package/dist/adapter-conformance.js +106 -25
  3. package/dist/adapter-registry.d.ts +61 -14
  4. package/dist/adapter-registry.js +78 -10
  5. package/dist/adapter.d.ts +23 -2
  6. package/dist/adapter.js +13 -1
  7. package/dist/adapters/claude-code/adapter.d.ts +32 -2
  8. package/dist/adapters/claude-code/adapter.js +44 -23
  9. package/dist/adapters/claude-code/agent-runtime.js +3 -1
  10. package/dist/adapters/claude-code/dialect.js +87 -21
  11. package/dist/adapters/claude-code/effect-region.js +3 -1
  12. package/dist/adapters/claude-code/hook-protocol.js +16 -0
  13. package/dist/adapters/claude-code/instruction-chain.d.ts +25 -0
  14. package/dist/adapters/claude-code/instruction-chain.js +626 -0
  15. package/dist/adapters/claude-code/layout.d.ts +2 -2
  16. package/dist/adapters/claude-code/layout.js +42 -8
  17. package/dist/adapters/claude-code/model-access.d.ts +41 -0
  18. package/dist/adapters/claude-code/model-access.js +46 -0
  19. package/dist/adapters/claude-code/skill-reachability.d.ts +125 -0
  20. package/dist/adapters/claude-code/skill-reachability.js +111 -0
  21. package/dist/adapters/claude-code/skill-runtime.js +3 -1
  22. package/dist/adapters/codex/adapter.d.ts +39 -2
  23. package/dist/adapters/codex/adapter.js +29 -29
  24. package/dist/adapters/codex/dialect.js +11 -6
  25. package/dist/adapters/codex/eval.d.ts +10 -0
  26. package/dist/adapters/codex/eval.js +48 -1
  27. package/dist/adapters/codex/hook-protocol.d.ts +2 -1
  28. package/dist/adapters/codex/hook-protocol.js +10 -0
  29. package/dist/adapters/codex/instruction-chain.d.ts +40 -0
  30. package/dist/adapters/codex/instruction-chain.js +105 -0
  31. package/dist/adapters/codex/layout.d.ts +1 -1
  32. package/dist/adapters/codex/layout.js +41 -14
  33. package/dist/adapters/opencode/adapter.d.ts +33 -2
  34. package/dist/adapters/opencode/adapter.js +36 -36
  35. package/dist/adapters/opencode/dialect.js +2 -2
  36. package/dist/adapters/opencode/instruction-chain.d.ts +37 -0
  37. package/dist/adapters/opencode/instruction-chain.js +70 -0
  38. package/dist/adapters/opencode/layout.d.ts +19 -0
  39. package/dist/adapters/opencode/layout.js +34 -15
  40. package/dist/adoptability.d.ts +31 -1
  41. package/dist/adoptability.js +57 -0
  42. package/dist/cli-main.js +185 -102
  43. package/dist/core/adapter.d.ts +213 -61
  44. package/dist/core/compile.d.ts +2 -2
  45. package/dist/core/compile.js +57 -46
  46. package/dist/core/compose.d.ts +5 -3
  47. package/dist/core/compose.js +5 -3
  48. package/dist/core/config-schema.d.ts +14 -2
  49. package/dist/core/config-schema.js +20 -7
  50. package/dist/core/dialect.d.ts +54 -12
  51. package/dist/core/dialect.js +56 -0
  52. package/dist/core/eval-driver.d.ts +194 -0
  53. package/dist/core/eval-driver.js +3 -0
  54. package/dist/core/frontmatter-read.d.ts +10 -0
  55. package/dist/core/frontmatter-read.js +30 -3
  56. package/dist/core/guards.js +3 -1
  57. package/dist/core/hook-program.d.ts +27 -2
  58. package/dist/core/hook-program.js +29 -24
  59. package/dist/core/hook-protocol.d.ts +54 -0
  60. package/dist/core/install-reader.d.ts +18 -0
  61. package/dist/core/install-reader.js +88 -0
  62. package/dist/core/instruction-chain.d.ts +444 -0
  63. package/dist/core/instruction-chain.js +292 -0
  64. package/dist/core/instruction-weight.d.ts +96 -14
  65. package/dist/core/instruction-weight.js +65 -30
  66. package/dist/core/layout.d.ts +220 -33
  67. package/dist/core/layout.js +115 -1
  68. package/dist/core/lethal-trifecta.d.ts +12 -7
  69. package/dist/core/lethal-trifecta.js +13 -13
  70. package/dist/core/live-driver.d.ts +137 -0
  71. package/dist/core/live-driver.js +14 -0
  72. package/dist/core/markdown.d.ts +23 -0
  73. package/dist/core/markdown.js +77 -28
  74. package/dist/core/orphans.js +9 -7
  75. package/dist/core/settings-codec.d.ts +17 -0
  76. package/dist/core/settings-codec.js +56 -0
  77. package/dist/core/surface-discovery.d.ts +2 -2
  78. package/dist/core/surface-discovery.js +24 -12
  79. package/dist/core/surface-scopes.d.ts +26 -6
  80. package/dist/core/surface-scopes.js +52 -11
  81. package/dist/core/validate.js +16 -3
  82. package/dist/coverage-artifact.d.ts +3 -2
  83. package/dist/coverage-artifact.js +6 -5
  84. package/dist/eval-cache.d.ts +6 -1
  85. package/dist/eval-cache.js +11 -1
  86. package/dist/eval.d.ts +16 -108
  87. package/dist/eval.js +36 -2
  88. package/dist/harness-test.d.ts +3 -63
  89. package/dist/hook-install.d.ts +12 -1
  90. package/dist/hook-install.js +12 -1
  91. package/dist/hook-runtime.js +4 -2
  92. package/dist/hook-state-store.js +3 -1
  93. package/dist/local-files-tracked.d.ts +17 -0
  94. package/dist/local-files-tracked.js +70 -0
  95. package/dist/local-files.d.ts +62 -0
  96. package/dist/local-files.js +183 -0
  97. package/dist/observe.d.ts +3 -2
  98. package/dist/observe.js +7 -6
  99. package/dist/plugin-loader.d.ts +1 -1
  100. package/dist/plugin-loader.js +43 -36
  101. package/dist/scan-behavioral.d.ts +34 -25
  102. package/dist/scan-behavioral.js +122 -58
  103. package/dist/scan-core.js +37 -18
  104. package/dist/scan-files.d.ts +1 -1
  105. package/dist/scan-files.js +53 -33
  106. package/dist/scan-trigger-suggest.d.ts +0 -21
  107. package/dist/scan-trigger-suggest.js +0 -23
  108. package/dist/scan.d.ts +4 -4
  109. package/dist/scan.js +120 -84
  110. package/dist/skill-harness.d.ts +21 -5
  111. package/dist/skill-harness.js +29 -11
  112. package/dist/surface-discovery-fs.d.ts +2 -0
  113. package/dist/surface-discovery-fs.js +108 -6
  114. package/dist/test-coverage-files.js +24 -17
  115. package/dist/test-coverage.d.ts +9 -3
  116. package/dist/test-coverage.js +32 -22
  117. package/dist/verify-plugin-guards.js +1 -1
  118. package/package.json +1 -1
  119. package/dist/skill-reachability.d.ts +0 -68
  120. package/dist/skill-reachability.js +0 -205
  121. /package/dist/{dialect-drift.d.ts → adapters/claude-code/dialect-drift.d.ts} +0 -0
  122. /package/dist/{dialect-drift.js → adapters/claude-code/dialect-drift.js} +0 -0
@@ -58,10 +58,10 @@ const node_crypto_1 = require("node:crypto");
58
58
  const node_fs_1 = require("node:fs");
59
59
  const node_path_1 = require("node:path");
60
60
  const coverage_evidence_js_1 = require("./coverage-evidence.js");
61
+ const local_files_js_1 = require("./local-files.js");
62
+ Object.defineProperty(exports, "COVERAGE_ARTIFACT_FILE", { enumerable: true, get: function () { return local_files_js_1.COVERAGE_ARTIFACT_FILE; } });
61
63
  /** Bumped when the record shape changes in a non-additive way. */
62
64
  exports.COVERAGE_ARTIFACT_VERSION = 1;
63
- /** The artifact filename under `.vigiles/`. */
64
- exports.COVERAGE_ARTIFACT_FILE = "coverage.json";
65
65
  /**
66
66
  * Every {@link RunAttribution}, as data — the artifact's validator reads it, so
67
67
  * the accepted set and the type cannot drift apart into a record shape the
@@ -576,7 +576,7 @@ function mergeRuns(previous, next, executed) {
576
576
  }
577
577
  /** Read the artifact, or `undefined` when there is none / it is not one. */
578
578
  function readCoverageArtifact(root) {
579
- const file = (0, node_path_1.resolve)(root, ".vigiles", exports.COVERAGE_ARTIFACT_FILE);
579
+ const file = (0, node_path_1.resolve)(root, local_files_js_1.VIGILES_DIR, local_files_js_1.COVERAGE_ARTIFACT_FILE);
580
580
  if (!(0, node_fs_1.existsSync)(file))
581
581
  return undefined;
582
582
  let value;
@@ -618,9 +618,10 @@ function isCoverageRun(value) {
618
618
  /** Write the artifact. Best-effort: recording must never fail a green run. */
619
619
  function writeCoverageArtifact(root, artifact) {
620
620
  try {
621
- const dir = (0, node_path_1.resolve)(root, ".vigiles");
621
+ const dir = (0, node_path_1.resolve)(root, local_files_js_1.VIGILES_DIR);
622
622
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
623
- (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(dir, exports.COVERAGE_ARTIFACT_FILE), JSON.stringify(artifact, null, 2) + "\n");
623
+ (0, local_files_js_1.ensureLocalFilesIgnored)(dir);
624
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(dir, local_files_js_1.COVERAGE_ARTIFACT_FILE), JSON.stringify(artifact, null, 2) + "\n");
624
625
  }
625
626
  catch {
626
627
  /* best-effort — a scratch-dir failure is not a test failure */
@@ -74,7 +74,12 @@ export declare function cacheKey(input: CacheKeyInput): SHA256Hash;
74
74
  * gate must surface, not mask. The message tells you how to recover.
75
75
  */
76
76
  export declare function readCache(dir: string, key: SHA256Hash): CacheRecord | null;
77
- /** Write a cached record by key (creating the cache dir as needed). */
77
+ /**
78
+ * Write a cached record by key (creating the cache dir as needed). When `dir` is
79
+ * the default `.vigiles/eval-cache`, the recorded runs are per-checkout, so the
80
+ * `.vigiles/.gitignore` is kept current; a `cacheDir` the spec chose elsewhere is
81
+ * the author's to place and is left alone.
82
+ */
78
83
  export declare function writeCache(dir: string, key: SHA256Hash, record: CacheRecord): void;
79
84
  /** Snapshot the text files under `cwd` as `relativePath → contents` (bounded). */
80
85
  export declare function snapshotDir(cwd: string): Record<string, string>;
@@ -28,6 +28,7 @@ exports.hashDir = hashDir;
28
28
  const node_fs_1 = require("node:fs");
29
29
  const node_path_1 = require("node:path");
30
30
  const hash_js_1 = require("./core/hash.js");
31
+ const local_files_js_1 = require("./local-files.js");
31
32
  const MAX_SNAPSHOT_FILE_BYTES = 1024 * 1024;
32
33
  const SKIP_DIRS = new Set(["node_modules", ".git"]);
33
34
  /**
@@ -113,9 +114,18 @@ function readCache(dir, key) {
113
114
  throw new Error(`eval cache: corrupt record ${path} (invalid JSON) — delete it or clear the cache dir`);
114
115
  }
115
116
  }
116
- /** Write a cached record by key (creating the cache dir as needed). */
117
+ /**
118
+ * Write a cached record by key (creating the cache dir as needed). When `dir` is
119
+ * the default `.vigiles/eval-cache`, the recorded runs are per-checkout, so the
120
+ * `.vigiles/.gitignore` is kept current; a `cacheDir` the spec chose elsewhere is
121
+ * the author's to place and is left alone.
122
+ */
117
123
  function writeCache(dir, key, record) {
118
124
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
125
+ if ((0, node_path_1.basename)(dir) === local_files_js_1.EVAL_CACHE_DIR &&
126
+ (0, node_path_1.basename)((0, node_path_1.dirname)(dir)) === local_files_js_1.VIGILES_DIR) {
127
+ (0, local_files_js_1.ensureLocalFilesIgnored)((0, node_path_1.dirname)(dir));
128
+ }
119
129
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, `${key}.json`), JSON.stringify(record));
120
130
  }
121
131
  /** Snapshot the text files under `cwd` as `relativePath → contents` (bounded). */
package/dist/eval.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { parseToolCalls, parseHooks, parseSubagents, type ToolCall, type Trace } from "./harness-test.js";
1
+ import type { HarnessLiveDriver } from "./core/live-driver.js";
2
+ import type { AgentRunArgs, AgentRunner, EvalDriver, EvalUsage, ModelOutputParser, ParsedModelRun, RunOut } from "./core/eval-driver.js";
3
+ export type { AgentRunArgs, AgentRunner, EvalDriver, EvalUsage, ModelOutputParser, ParsedModelRun, RunOut, } from "./core/eval-driver.js";
4
+ import { type ToolCall, type Trace } from "./harness-test.js";
2
5
  import { type CacheMode } from "./eval-cache.js";
3
6
  import { type EvalLockOptions } from "./eval-lock.js";
4
7
  import type { Check, CheckJSON } from "./check.js";
@@ -54,20 +57,6 @@ export interface EvalArm {
54
57
  */
55
58
  readonly effort?: string | number;
56
59
  }
57
- /** Per-run resource use, parsed from the terminal `result` event (0 when absent). */
58
- export interface EvalUsage {
59
- /** `total_cost_usd` reported by claude. */
60
- readonly costUsd: number;
61
- /** Wall-clock `duration_ms` of the run. */
62
- readonly durationMs: number;
63
- /** Fresh (uncached) input tokens, billed at full input price. */
64
- readonly inputTokens: number;
65
- readonly outputTokens: number;
66
- /** Tokens written to the prompt cache this run (~1.25× input price). */
67
- readonly cacheCreationTokens: number;
68
- /** Tokens served from the prompt cache this run (~0.1× input price). */
69
- readonly cacheReadTokens: number;
70
- }
71
60
  /**
72
61
  * Context handed to `measure` after a run, to compute that run's metrics. It is
73
62
  * a `Trace` (so the bare predicates `usedTool` / `skillResolved` / `toolCount` /
@@ -231,52 +220,6 @@ export interface EvalReport {
231
220
  /** True if a `maxCostUsd` budget cap stopped the run before all trials ran. */
232
221
  readonly aborted: boolean;
233
222
  }
234
- /** The raw output of one trial: the agent's exit code + captured streams. */
235
- export interface RunOut {
236
- code: number;
237
- stdout: string;
238
- /** Captured stderr, when the runner provides it (used for rate-limit detection). */
239
- stderr?: string;
240
- }
241
- /** The per-trial arguments handed to an {@link AgentRunner}. */
242
- export interface AgentRunArgs {
243
- readonly task: string;
244
- readonly cwd: string;
245
- readonly model: string;
246
- /**
247
- * Reasoning-budget level for the run (`claude --effort`). Part of the
248
- * MEASUREMENT, not a run knob: it changes the model's output distribution, not
249
- * the sample size — so it lives on the spec next to `model` (never an env),
250
- * and it is hashed into both the cache key and the eval lock. Deliberately
251
- * `string | number` rather than a literal union: the binary accepts an alias
252
- * map, is case-insensitive, and takes an integer budget, and its own valid set
253
- * MOVED between builds (2.1.42 had no `xhigh`, 2.1.257 does) — a hard-coded
254
- * union would reject a valid level after any upstream addition. A wrong value
255
- * is caught at RUNTIME instead, by {@link effortRejection}, which is what the
256
- * binary actually tells us. Omit for the harness default.
257
- */
258
- readonly effort?: string | number;
259
- readonly tools: readonly string[];
260
- readonly hasSettings: boolean;
261
- readonly pluginDir: string | undefined;
262
- readonly timeoutMs: number;
263
- /** Extra env layered over `process.env` for this run (e.g. `VIGILES_INTERCEPT_TOOLS`). */
264
- readonly env?: Record<string, string>;
265
- /**
266
- * When true, `env` is the COMPLETE spawn environment (an ephemeral run env from
267
- * `ephemeralRunEnv`) — the runner does NOT prepend `process.env`, so the
268
- * real `$HOME` / secrets are scrubbed. Default false: `env` is an overlay over
269
- * `process.env` (the byte-identical-to-today path). Set only by `ephemeralEnv`.
270
- */
271
- readonly replaceEnv?: boolean;
272
- }
273
- /**
274
- * Runs one trial and returns its raw output. The default (`spawnAgent`)
275
- * drives the real `claude` CLI; `runEvalWith` takes one explicitly, so the eval
276
- * orchestration is testable without a model (pass a fake returning canned
277
- * stream-json) and a custom runtime can be plugged in.
278
- */
279
- export type AgentRunner = (args: AgentRunArgs) => Promise<RunOut>;
280
223
  /**
281
224
  * Resolve the environment a trial's subprocess actually runs with — the
282
225
  * SECURITY-CRITICAL decision behind `ephemeralEnv`. When `replaceEnv` is set, the
@@ -528,22 +471,6 @@ export declare function checkReportToJUnit(report: CheckReport, opts?: {
528
471
  }): string;
529
472
  /** Parse per-run cost/latency/tokens from a stream — pure, model-free. */
530
473
  export declare function parseUsage(stdout: string): EvalUsage;
531
- /**
532
- * The harness-specific half of a run trace: how a real model's raw stdout maps
533
- * to the common fields. Claude Code's `parseClaudeRun` reads its stream-json; a
534
- * second harness (Codex) supplies its own parser of `codex exec --json` JSONL, so
535
- * the eval tier (`measureTriggerRate`/`runEval`) isn't bound to Claude's format.
536
- * The non-harness fields (cwd/exitCode/stdout/file/sh) stay in `makeContext`.
537
- */
538
- export interface ParsedModelRun {
539
- readonly turns: number;
540
- readonly output: string;
541
- readonly toolCalls: ReturnType<typeof parseToolCalls>;
542
- readonly hooks: ReturnType<typeof parseHooks>;
543
- readonly subagents: ReturnType<typeof parseSubagents>;
544
- readonly usage: EvalUsage;
545
- }
546
- export type ModelOutputParser = (out: RunOut) => ParsedModelRun;
547
474
  /** Parse Claude Code's stream-json stdout into the common trace fields. */
548
475
  export declare function parseClaudeRun(out: RunOut): ParsedModelRun;
549
476
  /**
@@ -864,37 +791,6 @@ export interface TriggerRateReport {
864
791
  */
865
792
  readonly experimental?: string;
866
793
  }
867
- /**
868
- * An eval-tier transport: how to RUN a real harness turn and PARSE its output.
869
- * The default is Claude Code (`claudeEvalDriver`); a second harness supplies its
870
- * own (e.g. `codexEvalDriver` from `vigiles/codex`) and passes it as
871
- * `measureTriggerRate(spec, { evalDriver })` — the eval-tier analog of
872
- * `runHarnessTest`'s `{ adapter }`. `runError` lets the loop drop an
873
- * errored/rate-limited turn instead of scoring it as a miss.
874
- */
875
- export interface EvalDriver {
876
- readonly runner: AgentRunner;
877
- readonly parse: ModelOutputParser;
878
- readonly runError?: (out: RunOut) => string | null;
879
- /**
880
- * The harness this driver runs (e.g. `"claude-code"`, `"codex"`). Folded into a
881
- * trigger-rate eval's LOCK hash so a report recorded on one harness is marked
882
- * STALE if the eval is later switched to another (a different harness can fire a
883
- * skill differently). Optional for back-compat — absent defaults to
884
- * `"claude-code"`, so an existing single-harness lock is unaffected.
885
- */
886
- readonly harness?: string;
887
- /**
888
- * When set, this driver's trigger-rate number is EXPERIMENTAL and not
889
- * validated — the string is the human caveat explaining why (e.g. Codex has no
890
- * skill-selection event, so firing is inferred from a SKILL.md read, which can
891
- * be wrong in both directions). Absent = supported/trustworthy (the default,
892
- * Claude Code). `measureTriggerRate` copies it onto the report and warns; the
893
- * formatter prints it. Precision-first: never let a possibly-wrong number read
894
- * as a measurement.
895
- */
896
- readonly experimental?: string;
897
- }
898
794
  /**
899
795
  * The default (Claude Code) eval driver: real `claude` + stream-json parsing.
900
796
  *
@@ -909,6 +805,18 @@ export interface EvalDriver {
909
805
  * The asymmetry reflects default-vs-injected, not a hexagonal violation.
910
806
  */
911
807
  export declare const claudeEvalDriver: EvalDriver;
808
+ /**
809
+ * The Claude Code {@link HarnessLiveDriver} — the EXECUTING tiers' side of the
810
+ * adapter, reached through `claudeCodeAdapter.liveDriver()`.
811
+ *
812
+ * It lives HERE, at the composition root, for exactly the reason
813
+ * `claudeEvalDriver` above does: it is assembled from the wired default runner
814
+ * and `whichSkillsFired`, and moving it into `src/adapters/claude-code/` would
815
+ * make `eval.ts → adapters/claude-code → eval.ts` a cycle. The adapter reaches
816
+ * it through a dynamic `import()`, so nothing pays for this graph until an
817
+ * executing tier actually runs.
818
+ */
819
+ export declare const claudeCodeLiveDriver: HarnessLiveDriver;
912
820
  /**
913
821
  * Package loose `<skillsDir>/<name>/SKILL.md` skills into a throwaway plugin dir
914
822
  * that `claude --plugin-dir` accepts — so repo-local skills (e.g. `.claude/skills`)
package/dist/eval.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.claudeEvalDriver = exports.EPHEMERAL_HOME_KEEP = exports.spawnAgent = exports.EFFORT_ENV_VAR = void 0;
3
+ exports.claudeCodeLiveDriver = exports.claudeEvalDriver = exports.EPHEMERAL_HOME_KEEP = exports.spawnAgent = exports.EFFORT_ENV_VAR = void 0;
4
4
  exports.resolveSpawnEnv = resolveSpawnEnv;
5
5
  exports.pinEffortEnv = pinEffortEnv;
6
6
  exports.effortRejection = effortRejection;
@@ -76,8 +76,10 @@ const plugin_loader_js_1 = require("./adapters/claude-code/plugin-loader.js");
76
76
  const runtime_js_1 = require("./adapters/claude-code/runtime.js");
77
77
  const eval_cost_js_1 = require("./eval-cost.js");
78
78
  const proofs_js_1 = require("./core/proofs.js");
79
+ const model_access_js_1 = require("./adapters/claude-code/model-access.js");
79
80
  const harness_test_js_1 = require("./harness-test.js");
80
81
  const eval_cache_js_1 = require("./eval-cache.js");
82
+ const local_files_js_1 = require("./local-files.js");
81
83
  const eval_lock_js_1 = require("./eval-lock.js");
82
84
  const stats_js_1 = require("./stats.js");
83
85
  const check_count_js_1 = require("./check-count.js");
@@ -1330,7 +1332,7 @@ async function runEvalWith(spec, runner) {
1330
1332
  tools: spec.allowedTools ?? ["Read", "Edit", "Write", "Bash"],
1331
1333
  timeoutMs: spec.timeoutMs ?? 240000,
1332
1334
  cache: spec.cache ?? "off",
1333
- cacheDir: spec.cacheDir ?? (0, node_path_1.resolve)(process.cwd(), ".vigiles", "eval-cache"),
1335
+ cacheDir: spec.cacheDir ?? (0, node_path_1.resolve)(process.cwd(), local_files_js_1.VIGILES_DIR, local_files_js_1.EVAL_CACHE_DIR),
1334
1336
  };
1335
1337
  if (cfg.cache !== "off" && !isDatedModel(cfg.model)) {
1336
1338
  warnFloatingModel(cfg.model);
@@ -1413,6 +1415,38 @@ exports.claudeEvalDriver = {
1413
1415
  parse: parseClaudeRun,
1414
1416
  harness: "claude-code",
1415
1417
  };
1418
+ /**
1419
+ * The Claude Code {@link HarnessLiveDriver} — the EXECUTING tiers' side of the
1420
+ * adapter, reached through `claudeCodeAdapter.liveDriver()`.
1421
+ *
1422
+ * It lives HERE, at the composition root, for exactly the reason
1423
+ * `claudeEvalDriver` above does: it is assembled from the wired default runner
1424
+ * and `whichSkillsFired`, and moving it into `src/adapters/claude-code/` would
1425
+ * make `eval.ts → adapters/claude-code → eval.ts` a cycle. The adapter reaches
1426
+ * it through a dynamic `import()`, so nothing pays for this graph until an
1427
+ * executing tier actually runs.
1428
+ */
1429
+ exports.claudeCodeLiveDriver = {
1430
+ evalDriver: exports.claudeEvalDriver,
1431
+ // Env-only, and never a spent token: deciding whether to OFFER a measurement
1432
+ // must not cost one. The three arms are what the consent prompt words
1433
+ // differently — a key bills per token, a session is $0 metered, and neither
1434
+ // present means the tier is skipped with `fix` printed.
1435
+ access: (env) => (0, model_access_js_1.isMeteredAccess)(env)
1436
+ ? { kind: "metered" }
1437
+ : (0, model_access_js_1.hasModelAccess)(env)
1438
+ ? { kind: "subscription" }
1439
+ : { kind: "none", fix: model_access_js_1.CLAUDE_CODE_ACCESS_FIX },
1440
+ // A discrete `Skill` tool_use in the trace says WHICH skill was selected, so
1441
+ // the selection-collision matrix and the adversarial gate can run here.
1442
+ firing: { kind: "event" },
1443
+ // Claude Code namespaces a plugin's skill as `<plugin>:<skill>`; the manifest
1444
+ // name is handed in by the domain, which read it off the layout.
1445
+ firedFor: (skill, plugin) => (t) => whichSkillsFired(t).includes(plugin.name ? `${plugin.name}:${skill}` : skill),
1446
+ // The probe may rebuild the plugin to skills-only stubs: this is the harness
1447
+ // whose plugin shape vigiles packages, so a stubbed rebuild is validated.
1448
+ installsStubs: true,
1449
+ };
1416
1450
  /**
1417
1451
  * Is this directory entry a skill DIRECTORY — following a symlink to one?
1418
1452
  *
@@ -1,5 +1,7 @@
1
1
  import type { HarnessAdapter } from "./core/adapter.js";
2
- import type { HarnessTestDriver, ToolCall, HookFire, ParsedRun, ModelTurn, ModelRequest } from "./core/harness-driver.js";
2
+ import type { HarnessTestDriver, ToolCall, HookFire, ParsedRun, ModelTurn } from "./core/harness-driver.js";
3
+ import type { Trace, SubagentTrace } from "./core/eval-driver.js";
4
+ export type { Trace, SubagentTrace } from "./core/eval-driver.js";
3
5
  import { type SandboxMode } from "./sandbox.js";
4
6
  export { scriptModel } from "./mock-model.js";
5
7
  export type { ModelTurn, ModelRequest, ToolCall, HookFire, HarnessTestDriver, } from "./core/harness-driver.js";
@@ -56,68 +58,6 @@ export interface HarnessTestSpec {
56
58
  */
57
59
  readonly sandbox?: SandboxMode;
58
60
  }
59
- /**
60
- * The observable record of ONE run — the unified shape produced by BOTH testing
61
- * tiers: `runHarnessTest`'s result and `runEval`'s `measure` ctx (`eval.ts`)
62
- * both satisfy it. That's what lets the bare predicates in `harness-assert.ts`
63
- * (`usedTool` / `skillResolved` / `toolCount` / `toolUsedWith` / `hookFired` /
64
- * `outputContains`) run over either, with the testing helpers asserting and eval
65
- * measuring over the same vocabulary.
66
- */
67
- export interface Trace {
68
- /**
69
- * The tools the agent invoked, each paired with its result — parsed from the
70
- * transcript. Empty unless the run captured the stream (`transcript: true` on
71
- * the harness tier; always on the eval tier). Lets a test assert on the
72
- * agent's *actions* (skills, MCP tools, subagents) instead of grepping stdout.
73
- */
74
- readonly toolCalls: readonly ToolCall[];
75
- /**
76
- * The hooks that fired during the run, each with its decision — parsed from
77
- * the CLI's `hook_response` stream events. Same capture requirement as
78
- * `toolCalls` (empty without the stream). Lets a test assert hook firing
79
- * honestly instead of via a marker file.
80
- */
81
- readonly hooks: readonly HookFire[];
82
- /** The agent's final answer text (the terminal `result` event), or "". */
83
- readonly output: string;
84
- /**
85
- * The requests the model received, captured by the scripted mock — each with
86
- * its `system` prompt and `messages`, flattened to text. Lets a test assert
87
- * what actually reached the model (a SessionStart hook's injected context, a
88
- * slash command's expansion), not just that a hook fired. **Harness tier
89
- * only**: the mock sees the requests, so this is populated by `runHarnessTest`
90
- * (with or without `transcript`); the eval tier drives the real API, so its
91
- * `modelRequests` is always empty.
92
- */
93
- readonly modelRequests: readonly ModelRequest[];
94
- /** Number of model turns. */
95
- readonly turns: number;
96
- /**
97
- * Sub-agent (`Task`) runs as nested traces, keyed by `subagent_type`. A
98
- * subagent runs its own session; CC tags its events with `parent_tool_use_id`
99
- * (= the `Task` tool call) so its tool calls are recovered into a sub-trace
100
- * here, lettng a test assert what the subagent DID (not just that `Task` fired).
101
- * Empty unless the stream was captured / the harness emits subagent events.
102
- */
103
- readonly subagents?: readonly SubagentTrace[];
104
- /** Final contents of a file under the working dir, or null if absent. */
105
- file(path: string): string | null;
106
- }
107
- /** A sub-agent (`Task`) run as a nested trace: its name + the tools it used. */
108
- export interface SubagentTrace {
109
- /** The `subagent_type` from the `Task` tool input. */
110
- readonly name: string;
111
- /** The tools the subagent invoked (events tagged with the Task's id). */
112
- readonly toolCalls: readonly ToolCall[];
113
- /**
114
- * The subagent's RETURNED text — the dispatch tool_result the orchestrator
115
- * receives back. This is where a `result()` contract's `vigiles:ok`/`vigiles:err`
116
- * block lands, so `subagent(name, [output(/vigiles:ok/)])` can assert the typed
117
- * outcome. "" if not captured.
118
- */
119
- readonly output: string;
120
- }
121
61
  export interface HarnessTestResult extends Trace {
122
62
  readonly exitCode: number;
123
63
  readonly stdout: string;
@@ -128,7 +128,18 @@ interface ConfigToml {
128
128
  }
129
129
  /** The TOML sibling of {@link mergeHooksJson} (Codex `[[hooks.<event>]]`). */
130
130
  export declare function mergeHooksToml(existing: ConfigToml, compiled: CompiledHooks, hookPath: string): ConfigToml;
131
- /** Serialize a merged config back to its on-disk text (with trailing newline). */
131
+ /**
132
+ * Serialize a merged config back to its on-disk text.
133
+ *
134
+ * ⚠️ DEPRECATED IN PLACE, not deleted, and the distinction matters: the
135
+ * harness-driven path (`installHookFile` in `cli-main.ts`) goes through
136
+ * `PluginLayout.settings.render` now, so no adapter's encoding is decided here
137
+ * any more. The one remaining caller is `cli-main.ts`'s Codex-plugin wiring,
138
+ * which writes `.codex/config.toml` for a harness it names ITSELF, at the
139
+ * composition root — a caller that already knows the encoding, rather than one
140
+ * branching on a layout field. Its `format` argument is therefore a literal at
141
+ * the call site, not a value read off a port.
142
+ */
132
143
  export declare function serializeConfig(merged: Record<string, unknown>, format: "json" | "toml"): string;
133
144
  export {};
134
145
  //# sourceMappingURL=hook-install.d.ts.map
@@ -298,7 +298,18 @@ function mergeHooksToml(existing, compiled, hookPath) {
298
298
  ]));
299
299
  return { ...existing, hooks: { ...before, ...rewritten } };
300
300
  }
301
- /** Serialize a merged config back to its on-disk text (with trailing newline). */
301
+ /**
302
+ * Serialize a merged config back to its on-disk text.
303
+ *
304
+ * ⚠️ DEPRECATED IN PLACE, not deleted, and the distinction matters: the
305
+ * harness-driven path (`installHookFile` in `cli-main.ts`) goes through
306
+ * `PluginLayout.settings.render` now, so no adapter's encoding is decided here
307
+ * any more. The one remaining caller is `cli-main.ts`'s Codex-plugin wiring,
308
+ * which writes `.codex/config.toml` for a harness it names ITSELF, at the
309
+ * composition root — a caller that already knows the encoding, rather than one
310
+ * branching on a layout field. Its `format` argument is therefore a literal at
311
+ * the call site, not a value read off a port.
312
+ */
302
313
  function serializeConfig(merged, format) {
303
314
  return format === "toml"
304
315
  ? stringifyToml(merged).trimEnd() + "\n"
@@ -50,6 +50,7 @@ const hook_install_js_1 = require("./hook-install.js");
50
50
  const hook_providers_js_1 = require("./core/hook-providers.js");
51
51
  const hook_state_store_js_1 = require("./hook-state-store.js");
52
52
  const observe_js_1 = require("./observe.js");
53
+ const local_files_js_1 = require("./local-files.js");
53
54
  const load_hook_js_1 = require("./load-hook.js");
54
55
  const event_capability_js_1 = require("./core/event-capability.js");
55
56
  /**
@@ -168,8 +169,9 @@ async function loadProviderRegistry(root) {
168
169
  /** Append an observe-mode record to `.vigiles/hook-observations.jsonl` (best-effort). */
169
170
  function recordObservation(file, on, would, reason, root) {
170
171
  try {
171
- const dir = (0, node_path_1.resolve)(root, ".vigiles");
172
+ const dir = (0, node_path_1.resolve)(root, local_files_js_1.VIGILES_DIR);
172
173
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
174
+ (0, local_files_js_1.ensureLocalFilesIgnored)(dir);
173
175
  const line = JSON.stringify({
174
176
  ts: new Date().toISOString(),
175
177
  hook: file,
@@ -177,7 +179,7 @@ function recordObservation(file, on, would, reason, root) {
177
179
  would,
178
180
  reason,
179
181
  }) + "\n";
180
- (0, node_fs_1.appendFileSync)((0, node_path_1.resolve)(dir, "hook-observations.jsonl"), line);
182
+ (0, node_fs_1.appendFileSync)((0, node_path_1.resolve)(dir, local_files_js_1.HOOK_OBSERVATIONS_FILE), line);
181
183
  }
182
184
  catch {
183
185
  /* recording is best-effort — never let it break a live session */
@@ -57,6 +57,7 @@ const node_path_1 = require("node:path");
57
57
  const hash_js_1 = require("./core/hash.js");
58
58
  const hook_state_js_1 = require("./core/hook-state.js");
59
59
  const hook_install_js_1 = require("./hook-install.js");
60
+ const local_files_js_1 = require("./local-files.js");
60
61
  /**
61
62
  * The directory a hook's recorded facts live in — the SCOPE of `state()`/`record()`.
62
63
  *
@@ -78,7 +79,7 @@ function hookStateDir(file, cwd = process.cwd()) {
78
79
  const dir = (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, file));
79
80
  const rel = (0, node_path_1.relative)(cwd, dir);
80
81
  const inside = rel !== "" && !rel.startsWith("..") && !(0, node_path_1.isAbsolute)(rel);
81
- return (0, node_path_1.resolve)(cwd, ".vigiles/state", inside ? rel : `external-${(0, hash_js_1.sha256short)(dir)}`);
82
+ return (0, node_path_1.resolve)(cwd, local_files_js_1.VIGILES_DIR, local_files_js_1.HOOK_STATE_DIR, inside ? rel : `external-${(0, hash_js_1.sha256short)(dir)}`);
82
83
  }
83
84
  /** Read one recorded fact for a hook, or `null` if it was never recorded. */
84
85
  function readHookState(file, key, cwd = process.cwd()) {
@@ -112,6 +113,7 @@ function writeHookState(file, w, opts = {}) {
112
113
  by: (0, hook_install_js_1.normalizeHookRef)(file, cwd),
113
114
  };
114
115
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
116
+ (0, local_files_js_1.ensureLocalFilesIgnored)((0, node_path_1.resolve)(cwd, local_files_js_1.VIGILES_DIR));
115
117
  const tmp = `${target}.${String(process.pid)}.tmp`;
116
118
  (0, node_fs_1.writeFileSync)(tmp, JSON.stringify(entry, null, 2) + "\n");
117
119
  (0, node_fs_1.renameSync)(tmp, target);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The per-checkout files under `<root>/.vigiles/` that git tracks, as
3
+ * repo-relative paths from `root`. Empty when nothing is tracked, when `root` is
4
+ * not in a git work tree, or when `git` is missing — silence is the answer to
5
+ * every "cannot tell".
6
+ */
7
+ export declare function trackedLocalFiles(root: string): string[];
8
+ /**
9
+ * The one-line warning for {@link trackedLocalFiles}, or `null` when there is
10
+ * nothing to say. Files inside a tracked local DIRECTORY (`state/`,
11
+ * `eval-cache/`) are named by that directory, so one line stays one line and the
12
+ * command it prints untracks all of them.
13
+ */
14
+ export declare function formatTrackedLocalFiles(tracked: readonly string[]): string | null;
15
+ /** Print the warning for `root` to stderr when there is one. CLI-only. */
16
+ export declare function warnTrackedLocalFiles(root: string): void;
17
+ //# sourceMappingURL=local-files-tracked.d.ts.map
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trackedLocalFiles = trackedLocalFiles;
4
+ exports.formatTrackedLocalFiles = formatTrackedLocalFiles;
5
+ exports.warnTrackedLocalFiles = warnTrackedLocalFiles;
6
+ /**
7
+ * The half `.gitignore` cannot do: tell the owner when a per-checkout file under
8
+ * `.vigiles/` is ALREADY tracked.
9
+ *
10
+ * `ensureLocalFilesIgnored` (`local-files.ts`) keeps new copies out of git, but
11
+ * an ignore rule does not untrack a file git already follows — a repo that
12
+ * committed `.vigiles/coverage.json` before this existed keeps committing every
13
+ * rewrite of it. Only the owner can fix that (`git rm --cached`), and changing
14
+ * the index is theirs to decide, so this reports and never acts.
15
+ *
16
+ * CLI-only, by construction: it spawns `git`, and a hook decision must never pay
17
+ * for a child process. That is why it is a separate module from the ignore half,
18
+ * which hook runtimes import.
19
+ */
20
+ const node_child_process_1 = require("node:child_process");
21
+ const node_path_1 = require("node:path");
22
+ const local_files_js_1 = require("./local-files.js");
23
+ /**
24
+ * The per-checkout files under `<root>/.vigiles/` that git tracks, as
25
+ * repo-relative paths from `root`. Empty when nothing is tracked, when `root` is
26
+ * not in a git work tree, or when `git` is missing — silence is the answer to
27
+ * every "cannot tell".
28
+ */
29
+ function trackedLocalFiles(root) {
30
+ const specs = local_files_js_1.LOCAL_FILES.map((f) => node_path_1.posix.join(local_files_js_1.VIGILES_DIR, f.name));
31
+ try {
32
+ const r = (0, node_child_process_1.spawnSync)("git", ["ls-files", "-z", "--", ...specs], {
33
+ cwd: root,
34
+ encoding: "utf-8",
35
+ stdio: ["ignore", "pipe", "ignore"],
36
+ });
37
+ if (r.status !== 0 || typeof r.stdout !== "string")
38
+ return [];
39
+ return r.stdout.split("\0").filter((p) => p !== "");
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ }
45
+ /**
46
+ * The one-line warning for {@link trackedLocalFiles}, or `null` when there is
47
+ * nothing to say. Files inside a tracked local DIRECTORY (`state/`,
48
+ * `eval-cache/`) are named by that directory, so one line stays one line and the
49
+ * command it prints untracks all of them.
50
+ */
51
+ function formatTrackedLocalFiles(tracked) {
52
+ if (tracked.length === 0)
53
+ return null;
54
+ // `.vigiles/state/.claude/hooks/x.json` → `.vigiles/state`: the list entry.
55
+ const entries = [
56
+ ...new Set(tracked.map((p) => p.split("/").slice(0, 2).join("/"))),
57
+ ];
58
+ const one = entries.length === 1;
59
+ return (`⚠ ${entries.join(", ")} ${one ? "is" : "are"} tracked by git, but ` +
60
+ `${local_files_js_1.VIGILES_DIR}/ local files describe one checkout and would credit a ` +
61
+ `machine where nothing ran. .gitignore does not untrack a tracked file; ` +
62
+ `untrack ${one ? "it" : "them"} once: git rm -r --cached ${entries.join(" ")}`);
63
+ }
64
+ /** Print the warning for `root` to stderr when there is one. CLI-only. */
65
+ function warnTrackedLocalFiles(root) {
66
+ const line = formatTrackedLocalFiles(trackedLocalFiles(root));
67
+ if (line)
68
+ process.stderr.write(line + "\n");
69
+ }
70
+ //# sourceMappingURL=local-files-tracked.js.map
@@ -0,0 +1,62 @@
1
+ /** vigiles's own directory in a project. */
2
+ export declare const VIGILES_DIR = ".vigiles";
3
+ /** The execution tier of coverage (`coverage-artifact.ts`). */
4
+ export declare const COVERAGE_ARTIFACT_FILE = "coverage.json";
5
+ /** The flight-recorder ledger (`observe.ts`). */
6
+ export declare const LEDGER_FILE = "runs.jsonl";
7
+ /** Compiled hooks' named state, `record()`/`state()` (`hook-state-store.ts`). */
8
+ export declare const HOOK_STATE_DIR = "state";
9
+ /** The skill in progress (`adapters/claude-code/skill-runtime.ts`). */
10
+ export declare const ACTIVE_SKILL_FILE = "active-skill.json";
11
+ /** The subagent stack in progress (`adapters/claude-code/agent-runtime.ts`). */
12
+ export declare const ACTIVE_AGENT_FILE = "active-agent.json";
13
+ /** Inside-an-effect-boundary marker (`adapters/claude-code/effect-region.ts`). */
14
+ export declare const EFFECT_ACTIVE_FILE = "effect-active.json";
15
+ /** The calls a guard allowed this session (`core/guards.ts`). */
16
+ export declare const GUARD_LEDGER_FILE = "guard-ledger.json";
17
+ /** What an `observe`-mode hook would have blocked (`hook-runtime.ts`). */
18
+ export declare const HOOK_OBSERVATIONS_FILE = "hook-observations.jsonl";
19
+ /** Recorded model runs for eval replay, the default `cacheDir` (`eval.ts`). */
20
+ export declare const EVAL_CACHE_DIR = "eval-cache";
21
+ /** One per-checkout path under `.vigiles/`. */
22
+ export interface LocalFile {
23
+ /** The name directly under `.vigiles/`. */
24
+ readonly name: string;
25
+ /** A directory (its whole subtree is local) rather than a single file. */
26
+ readonly dir: boolean;
27
+ }
28
+ /** Every per-checkout path vigiles writes under `.vigiles/`. The one list. */
29
+ export declare const LOCAL_FILES: readonly LocalFile[];
30
+ /**
31
+ * The names under `.vigiles/` that ARE the project's and must stay committable.
32
+ * Not used at runtime — it is the other half of the classification the guard in
33
+ * `local-files.test.ts` holds the source to, and the list that test checks is
34
+ * NOT ignored.
35
+ */
36
+ export declare const COMMITTED_PATHS: readonly string[];
37
+ /** The header comment on a `.vigiles/.gitignore` vigiles creates. */
38
+ export declare const LOCAL_GITIGNORE_HEADER: readonly string[];
39
+ /** The ignore lines, in order: every local path, then the file itself. */
40
+ export declare function localIgnoreEntries(): string[];
41
+ /**
42
+ * Keep `<vigilesDir>/.gitignore` listing every local path. Call it where a
43
+ * local file is written — not from `vigiles init`, which a project may never
44
+ * run, while the write always happens.
45
+ *
46
+ * - Absent → created with {@link LOCAL_GITIGNORE_HEADER} and every entry.
47
+ * - Every entry IN EFFECT → not touched (no rewrite, no mtime change).
48
+ * - Otherwise the entries not in effect are appended and nothing is removed
49
+ * or reordered. Appending is enough because git's last matching rule wins.
50
+ *
51
+ * "In effect" is judged the way git reads the file, not by text membership:
52
+ * lines in order, a later `!/entry` cancels an earlier `/entry`, and leading
53
+ * whitespace is part of the pattern (only trailing whitespace is dropped).
54
+ * Both cases were measured with `git check-ignore`: `/coverage.json` followed
55
+ * by `!/coverage.json`, and ` /coverage.json`, each leave the file NOT
56
+ * ignored while a trimmed set would call the entry present.
57
+ *
58
+ * Costs one read on the hot path. Best-effort: any fs error is swallowed —
59
+ * keeping git tidy must never break a hook, a test run or an audit.
60
+ */
61
+ export declare function ensureLocalFilesIgnored(vigilesDir: string): void;
62
+ //# sourceMappingURL=local-files.d.ts.map