@vibe-agent-toolkit/agent-skills 0.2.0-rc.2 → 0.2.0-rc.3

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 (194) hide show
  1. package/dist/files-config.d.ts +34 -1
  2. package/dist/files-config.d.ts.map +1 -1
  3. package/dist/files-config.js +280 -36
  4. package/dist/files-config.js.map +1 -1
  5. package/dist/fs-attribution.d.ts +45 -0
  6. package/dist/fs-attribution.d.ts.map +1 -0
  7. package/dist/fs-attribution.js +57 -0
  8. package/dist/fs-attribution.js.map +1 -0
  9. package/dist/index.d.ts +5 -4
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +8 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/inventory/detectors/declared-but-missing.d.ts +1 -1
  14. package/dist/inventory/detectors/declared-but-missing.d.ts.map +1 -1
  15. package/dist/inventory/detectors/marketplace-source-missing.d.ts +1 -1
  16. package/dist/inventory/detectors/marketplace-source-missing.d.ts.map +1 -1
  17. package/dist/inventory/detectors/present-but-undeclared.d.ts +1 -1
  18. package/dist/inventory/detectors/present-but-undeclared.d.ts.map +1 -1
  19. package/dist/inventory/detectors/reference-target-missing.d.ts +1 -1
  20. package/dist/inventory/detectors/reference-target-missing.d.ts.map +1 -1
  21. package/dist/inventory/index.d.ts +1 -1
  22. package/dist/inventory/index.d.ts.map +1 -1
  23. package/dist/inventory/index.js +1 -1
  24. package/dist/inventory/index.js.map +1 -1
  25. package/dist/inventory/serialize.d.ts +4 -4
  26. package/dist/inventory/serialize.d.ts.map +1 -1
  27. package/dist/inventory/serialize.js +6 -6
  28. package/dist/inventory/serialize.js.map +1 -1
  29. package/dist/post-build-checks.d.ts +1 -1
  30. package/dist/post-build-checks.d.ts.map +1 -1
  31. package/dist/post-build-checks.js.map +1 -1
  32. package/dist/projection/skill-extent.d.ts +247 -0
  33. package/dist/projection/skill-extent.d.ts.map +1 -0
  34. package/dist/projection/skill-extent.js +558 -0
  35. package/dist/projection/skill-extent.js.map +1 -0
  36. package/dist/schemas/agent-skill-frontmatter.js +1 -1
  37. package/dist/schemas/agent-skill-frontmatter.js.map +1 -1
  38. package/dist/skill-packager.d.ts +34 -4
  39. package/dist/skill-packager.d.ts.map +1 -1
  40. package/dist/skill-packager.js +113 -23
  41. package/dist/skill-packager.js.map +1 -1
  42. package/dist/skill-source/git-clone.d.ts.map +1 -1
  43. package/dist/skill-source/git-clone.js +9 -8
  44. package/dist/skill-source/git-clone.js.map +1 -1
  45. package/dist/skill-test/baseline-delta.d.ts +1575 -0
  46. package/dist/skill-test/baseline-delta.d.ts.map +1 -0
  47. package/dist/skill-test/baseline-delta.js +272 -0
  48. package/dist/skill-test/baseline-delta.js.map +1 -0
  49. package/dist/skill-test/baseline-integrity.d.ts +944 -0
  50. package/dist/skill-test/baseline-integrity.d.ts.map +1 -0
  51. package/dist/skill-test/baseline-integrity.js +3083 -0
  52. package/dist/skill-test/baseline-integrity.js.map +1 -0
  53. package/dist/skill-test/eval-executor.d.ts +19 -3
  54. package/dist/skill-test/eval-executor.d.ts.map +1 -1
  55. package/dist/skill-test/eval-executor.js +3 -3
  56. package/dist/skill-test/eval-executor.js.map +1 -1
  57. package/dist/skill-test/eval-fragment.d.ts +80 -6
  58. package/dist/skill-test/eval-fragment.d.ts.map +1 -1
  59. package/dist/skill-test/eval-fragment.js +61 -7
  60. package/dist/skill-test/eval-fragment.js.map +1 -1
  61. package/dist/skill-test/eval-grader.d.ts.map +1 -1
  62. package/dist/skill-test/eval-grader.js +143 -7
  63. package/dist/skill-test/eval-grader.js.map +1 -1
  64. package/dist/skill-test/eval-inputs.d.ts +107 -6
  65. package/dist/skill-test/eval-inputs.d.ts.map +1 -1
  66. package/dist/skill-test/eval-inputs.js +173 -16
  67. package/dist/skill-test/eval-inputs.js.map +1 -1
  68. package/dist/skill-test/eval-lint.d.ts +13 -0
  69. package/dist/skill-test/eval-lint.d.ts.map +1 -1
  70. package/dist/skill-test/eval-lint.js +17 -4
  71. package/dist/skill-test/eval-lint.js.map +1 -1
  72. package/dist/skill-test/executor-prompt.d.ts +13 -5
  73. package/dist/skill-test/executor-prompt.d.ts.map +1 -1
  74. package/dist/skill-test/executor-prompt.js +8 -5
  75. package/dist/skill-test/executor-prompt.js.map +1 -1
  76. package/dist/skill-test/exit-codes.d.ts +53 -16
  77. package/dist/skill-test/exit-codes.d.ts.map +1 -1
  78. package/dist/skill-test/exit-codes.js +123 -40
  79. package/dist/skill-test/exit-codes.js.map +1 -1
  80. package/dist/skill-test/fragment-merge.d.ts +1 -18
  81. package/dist/skill-test/fragment-merge.d.ts.map +1 -1
  82. package/dist/skill-test/fragment-merge.js +63 -4
  83. package/dist/skill-test/fragment-merge.js.map +1 -1
  84. package/dist/skill-test/grader-prompt.d.ts +27 -7
  85. package/dist/skill-test/grader-prompt.d.ts.map +1 -1
  86. package/dist/skill-test/grader-prompt.js +253 -61
  87. package/dist/skill-test/grader-prompt.js.map +1 -1
  88. package/dist/skill-test/grader-text.d.ts +145 -0
  89. package/dist/skill-test/grader-text.d.ts.map +1 -0
  90. package/dist/skill-test/grader-text.js +410 -0
  91. package/dist/skill-test/grader-text.js.map +1 -0
  92. package/dist/skill-test/grading-adapter.d.ts +32 -0
  93. package/dist/skill-test/grading-adapter.d.ts.map +1 -1
  94. package/dist/skill-test/grading-adapter.js +50 -3
  95. package/dist/skill-test/grading-adapter.js.map +1 -1
  96. package/dist/skill-test/grading-schema.d.ts +160 -5
  97. package/dist/skill-test/grading-schema.d.ts.map +1 -1
  98. package/dist/skill-test/grading-schema.js +66 -7
  99. package/dist/skill-test/grading-schema.js.map +1 -1
  100. package/dist/skill-test/index.d.ts +1 -1
  101. package/dist/skill-test/index.d.ts.map +1 -1
  102. package/dist/skill-test/index.js +1 -1
  103. package/dist/skill-test/index.js.map +1 -1
  104. package/dist/skill-test/lock.d.ts +56 -0
  105. package/dist/skill-test/lock.d.ts.map +1 -1
  106. package/dist/skill-test/lock.js +75 -2
  107. package/dist/skill-test/lock.js.map +1 -1
  108. package/dist/skill-test/pipeline.d.ts +16 -0
  109. package/dist/skill-test/pipeline.d.ts.map +1 -1
  110. package/dist/skill-test/pipeline.js +16 -9
  111. package/dist/skill-test/pipeline.js.map +1 -1
  112. package/dist/skill-test/preflight.d.ts.map +1 -1
  113. package/dist/skill-test/preflight.js +58 -14
  114. package/dist/skill-test/preflight.js.map +1 -1
  115. package/dist/skill-test/run-harness.d.ts +578 -35
  116. package/dist/skill-test/run-harness.d.ts.map +1 -1
  117. package/dist/skill-test/run-harness.js +1278 -147
  118. package/dist/skill-test/run-harness.js.map +1 -1
  119. package/dist/skill-test/staging.d.ts +8 -2
  120. package/dist/skill-test/staging.d.ts.map +1 -1
  121. package/dist/skill-test/staging.js +8 -4
  122. package/dist/skill-test/staging.js.map +1 -1
  123. package/dist/skill-test/tool-eval-schema.d.ts +6 -6
  124. package/dist/test-input.d.ts +1 -1
  125. package/dist/test-input.d.ts.map +1 -1
  126. package/dist/validators/agent-instruction-presence.d.ts +1 -1
  127. package/dist/validators/agent-instruction-presence.d.ts.map +1 -1
  128. package/dist/validators/bundled-resource-link-detection.d.ts +1 -1
  129. package/dist/validators/bundled-resource-link-detection.d.ts.map +1 -1
  130. package/dist/validators/bundled-resource-link-detection.js +1 -1
  131. package/dist/validators/bundled-resource-link-detection.js.map +1 -1
  132. package/dist/validators/compat-detectors.d.ts +1 -1
  133. package/dist/validators/compat-detectors.d.ts.map +1 -1
  134. package/dist/validators/compat-detectors.js +1 -1
  135. package/dist/validators/compat-detectors.js.map +1 -1
  136. package/dist/validators/cross-skill-dependency-detection.d.ts +1 -1
  137. package/dist/validators/cross-skill-dependency-detection.d.ts.map +1 -1
  138. package/dist/validators/cross-skill-dependency-detection.js +1 -1
  139. package/dist/validators/cross-skill-dependency-detection.js.map +1 -1
  140. package/dist/validators/description-style-detection.d.ts +1 -1
  141. package/dist/validators/description-style-detection.d.ts.map +1 -1
  142. package/dist/validators/description-style-detection.js +1 -1
  143. package/dist/validators/description-style-detection.js.map +1 -1
  144. package/dist/validators/frontmatter-validation.d.ts +1 -1
  145. package/dist/validators/frontmatter-validation.d.ts.map +1 -1
  146. package/dist/validators/frontmatter-validation.js +1 -1
  147. package/dist/validators/frontmatter-validation.js.map +1 -1
  148. package/dist/validators/imperative-body-detection.d.ts +1 -1
  149. package/dist/validators/imperative-body-detection.d.ts.map +1 -1
  150. package/dist/validators/imperative-body-detection.js +1 -1
  151. package/dist/validators/imperative-body-detection.js.map +1 -1
  152. package/dist/validators/index.d.ts +1 -1
  153. package/dist/validators/index.d.ts.map +1 -1
  154. package/dist/validators/kebab-case-detection.d.ts +1 -1
  155. package/dist/validators/kebab-case-detection.d.ts.map +1 -1
  156. package/dist/validators/kebab-case-detection.js +1 -1
  157. package/dist/validators/kebab-case-detection.js.map +1 -1
  158. package/dist/validators/marketplace-validator.js +1 -1
  159. package/dist/validators/marketplace-validator.js.map +1 -1
  160. package/dist/validators/packaging-validator.d.ts +38 -5
  161. package/dist/validators/packaging-validator.d.ts.map +1 -1
  162. package/dist/validators/packaging-validator.js +87 -10
  163. package/dist/validators/packaging-validator.js.map +1 -1
  164. package/dist/validators/plugin-hosted-shape.d.ts +1 -1
  165. package/dist/validators/plugin-hosted-shape.d.ts.map +1 -1
  166. package/dist/validators/plugin-hosted-shape.js +1 -1
  167. package/dist/validators/plugin-hosted-shape.js.map +1 -1
  168. package/dist/validators/plugin-recommended-fields.d.ts +1 -1
  169. package/dist/validators/plugin-recommended-fields.d.ts.map +1 -1
  170. package/dist/validators/plugin-recommended-fields.js +1 -1
  171. package/dist/validators/plugin-recommended-fields.js.map +1 -1
  172. package/dist/validators/registry-validator.js +1 -1
  173. package/dist/validators/registry-validator.js.map +1 -1
  174. package/dist/validators/rule-engine/rule-engine.d.ts +1 -1
  175. package/dist/validators/rule-engine/rule-engine.d.ts.map +1 -1
  176. package/dist/validators/rule-engine/rule-engine.js +20 -9
  177. package/dist/validators/rule-engine/rule-engine.js.map +1 -1
  178. package/dist/validators/skill-validator.d.ts.map +1 -1
  179. package/dist/validators/skill-validator.js +21 -3
  180. package/dist/validators/skill-validator.js.map +1 -1
  181. package/dist/validators/types.d.ts +1 -1
  182. package/dist/validators/types.d.ts.map +1 -1
  183. package/dist/validators/unified-validator.js +1 -1
  184. package/dist/validators/unified-validator.js.map +1 -1
  185. package/dist/validators/validation-rules.d.ts +1 -1
  186. package/dist/validators/validation-rules.d.ts.map +1 -1
  187. package/dist/validators/walker-to-issues.d.ts +7 -3
  188. package/dist/validators/walker-to-issues.d.ts.map +1 -1
  189. package/dist/validators/walker-to-issues.js +37 -8
  190. package/dist/validators/walker-to-issues.js.map +1 -1
  191. package/dist/walk-link-graph.d.ts.map +1 -1
  192. package/dist/walk-link-graph.js +36 -2
  193. package/dist/walk-link-graph.js.map +1 -1
  194. package/package.json +7 -7
@@ -0,0 +1,944 @@
1
+ import { z } from 'zod';
2
+ export declare function harnessNeedles(harnessRoot: string): PathNeedle[];
3
+ /**
4
+ * Strip every channel by which the executor ENVIRONMENT would tell the skill-absent
5
+ * arm where the skill is staged.
6
+ *
7
+ * A path reaches a child process through four channels — prompt, argv, cwd, and
8
+ * env. The first three are handled at the call site; this is the fourth, and it is
9
+ * the one that is easy to miss because nothing at the spawn call mentions it: the
10
+ * run assembles ONE env and both arms were given it verbatim.
11
+ *
12
+ * Two rules, deliberately in this order:
13
+ * 1. drop {@link CONTROL_ARM_FORBIDDEN_ENV_KEYS} by name;
14
+ * 2. drop ANY entry whose value contains the harness root — which covers
15
+ * adopter-declared `env:` values that interpolated `${stagedSkillDir}` or
16
+ * `${harnessRoot}`, and, more importantly, covers the next such channel
17
+ * somebody adds without knowing this function exists.
18
+ *
19
+ * Rule 2 is a value scan rather than a longer key list on purpose: a key list only
20
+ * ever knows about the leaks already found. It is accident-catching HYGIENE, not an
21
+ * adversarial control — a determined skill author splits the path across two vars
22
+ * (`A=/tmp/vat-skill-test` + `B=/<key>/…`) and the per-value scan never joins them.
23
+ * The adversarial control is that the path is not REACHABLE, not that it is unnamed.
24
+ *
25
+ * Rule 2 exempts {@link protectedEnvNames}, which is WIDER than the three names the
26
+ * paragraph below uses as its example: it is every process essential, the auth
27
+ * allowlist, every inference credential, the whole `CREDENTIAL_ROUTING_DENY` list,
28
+ * AND this run's `modelVars`. The model vars are the ones that matter most here and
29
+ * were missing until they were threaded in: dropping a model var because its value
30
+ * happens to sit under `--out` runs the control arm ON A DIFFERENT MODEL, which is
31
+ * a far worse confound than the missing PATH this exemption exists to prevent, and
32
+ * one no operator would think to look for.
33
+ *
34
+ * The three named below are the illustration. An `--out` under any of their values (e.g.
35
+ * `--out .` from a repo root, which puts `<repo>/node_modules/.bin` on PATH inside a
36
+ * `bun run`) would otherwise spawn the control arm with no PATH at all. That does not
37
+ * fail; it DEGRADES the control, which scores lower, which reports as skill lift. A
38
+ * scrub that manufactures the very delta the product sells is worse than the leak it
39
+ * closes, so a protected var that names the harness root is RETAINED and reported
40
+ * loudly instead — the operator can see it and move `--out`.
41
+ *
42
+ * `${fixturesDir}` resolves under the WORKSPACES root, not the harness root, so the
43
+ * control arm keeps its declared input files — the arms stay identical in
44
+ * everything except the skill itself.
45
+ *
46
+ * Pure. Returns the scrubbed env and three disjoint name lists for the transparency
47
+ * lines: the rule-1 drops, the rule-2 drops, and the protected names RETAINED
48
+ * despite naming the harness root.
49
+ *
50
+ * The two drop lists are separate because they mean opposite things to an operator.
51
+ * A rule-1 drop is routine — `CLAUDE_PLUGIN_ROOT` is withheld on every plugin-layout
52
+ * baseline run by design, and its value may name nothing in the harness at all (the
53
+ * installed-plugin-cache case). A rule-2 drop says the operator's OWN declared `env:`
54
+ * named the harness root, which is the one worth acting on. Merged into one list under
55
+ * one "naming the harness root" sentence, the routine drop was both mislabelled and
56
+ * loud enough to bury the interesting one.
57
+ */
58
+ export declare function scrubControlArmEnv(env: NodeJS.ProcessEnv, harnessRoot: string,
59
+ /**
60
+ * This run's model env var names. REQUIRED, not defaulted: an omitted list
61
+ * silently narrows the exemption set, and the var it drops decides which model
62
+ * the control arm runs — a defect that reads as skill lift and is invisible in
63
+ * the output. Pass `[]` when the run genuinely has none.
64
+ */
65
+ modelVars: readonly string[],
66
+ /**
67
+ * The platform whose env-name casing rules apply. Defaults to this process's.
68
+ * Threaded in ONLY so the win32 branch of {@link isProtectedName} is testable:
69
+ * without it the case-insensitive lookup this function deliberately uses instead
70
+ * of `protectedNames.has` was unexercised on every platform CI runs the unit
71
+ * suite on, and swapping the two was green.
72
+ */
73
+ platform?: string): {
74
+ env: NodeJS.ProcessEnv;
75
+ /** Rule 1: dropped by NAME, whatever the value. */
76
+ droppedForbiddenKey: string[];
77
+ /** Rule 2: dropped because the VALUE names the harness root. */
78
+ droppedNamingRoot: string[];
79
+ retainedLeaks: string[];
80
+ };
81
+ export declare const BaselineContaminationHitSchema: z.ZodObject<{
82
+ /**
83
+ * What matched: a path under vat's harness root, a declared executable's name,
84
+ * the OTHER arm's workspace directory, or one of vat's private tmp dirs.
85
+ *
86
+ * `sibling-arm` is a materially different finding from the first two. Those mean
87
+ * the arm found a copy of the skill; this one means it read the treatment arm's
88
+ * live working directory, so the control may be echoing an answer the treatment
89
+ * produced seconds earlier rather than solving anything.
90
+ *
91
+ * `vat-private-dir` is worse than all of them. It means the arm reached the held
92
+ * eval suite (the `expected_output` ANSWER KEY) or the grader dir (the run's
93
+ * integrity nonce). Reaching a copy of the skill inflates the control arm;
94
+ * reaching the answer key inflates BOTH arms, so it does not even show up as a
95
+ * shrunken delta — see {@link vatPrivateDirNeedles}.
96
+ */
97
+ kind: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
98
+ /**
99
+ * The matched token — a path SUFFIX, a verbatim skill line, or the executable
100
+ * basename.
101
+ *
102
+ * ⚠️ PATH MATCHES ARE TRUNCATED TO THEIR LAST TWO SEGMENTS ON PURPOSE, and that
103
+ * is not cosmetic. Needle sets run longest-first, so the needle that matches a
104
+ * full-path reach is the ENTIRE absolute root — on Windows
105
+ * `C:/Users/<username>/AppData/Local/Temp/vat-skill-test/<key>`, which puts the
106
+ * operator's login name in an artifact adopters attach to bug reports, in the one
107
+ * field that reads like a short opaque token. The last two segments identify the
108
+ * directory just as well (`vat-skill-test/<key>`) and name nobody. See
109
+ * {@link redactPathMatch}; the untruncated path is still visible in `excerpt`,
110
+ * which carries its own warning.
111
+ *
112
+ * A skill-content match is a verbatim line of the skill's own body and is NOT
113
+ * truncated — it has no `/`-separated structure to trim, and the whole point of
114
+ * that signal is that the operator can find the line in SKILL.md by eye.
115
+ */
116
+ match: z.ZodString;
117
+ /**
118
+ * A short excerpt of the transcript around the match, for triage.
119
+ *
120
+ * RAW TRANSCRIPT — the arm's own bytes, its own casing, its own separators,
121
+ * bounded and whitespace-collapsed. That is a promise this field has to keep to
122
+ * be worth anything: its only use is to be carried back to the transcript and
123
+ * searched for, and it once came from the NORMALIZED haystack instead, so
124
+ * `SKILL.md` was reported as `skill.md` and a JSON-escaped `\n` as `/n` — an
125
+ * operator grepping for the reported excerpt found nothing, in a field that
126
+ * exists solely so they can. The structured scan quotes the raw tool input; the
127
+ * flat and content scans map their match back through {@link ScanHaystack}.
128
+ * (Whitespace collapse is the one transform that survives, and it is stated here:
129
+ * grep for a distinctive WORD from the excerpt, not for the whole line.)
130
+ *
131
+ * NOT redacted. The most likely way a harness path reaches a transcript is an
132
+ * `env` dump, so this window can capture adjacent environment values —
133
+ * `GITHUB_TOKEN=…`, `HOME=/Users/<name>` have both been observed — and
134
+ * `baseline.json` is a file adopters attach to reports. Treat it as sensitive;
135
+ * `grading.json` already quotes transcripts verbatim, so this is the same
136
+ * exposure, not a new one.
137
+ */
138
+ excerpt: z.ZodString;
139
+ }, "strict", z.ZodTypeAny, {
140
+ match: string;
141
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
142
+ excerpt: string;
143
+ }, {
144
+ match: string;
145
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
146
+ excerpt: string;
147
+ }>;
148
+ export type BaselineContaminationHit = z.infer<typeof BaselineContaminationHitSchema>;
149
+ /** Per-eval contamination finding for the WITHOUT arm. */
150
+ export declare const BaselineContaminationSchema: z.ZodObject<{
151
+ evalId: z.ZodString;
152
+ hits: z.ZodArray<z.ZodObject<{
153
+ /**
154
+ * What matched: a path under vat's harness root, a declared executable's name,
155
+ * the OTHER arm's workspace directory, or one of vat's private tmp dirs.
156
+ *
157
+ * `sibling-arm` is a materially different finding from the first two. Those mean
158
+ * the arm found a copy of the skill; this one means it read the treatment arm's
159
+ * live working directory, so the control may be echoing an answer the treatment
160
+ * produced seconds earlier rather than solving anything.
161
+ *
162
+ * `vat-private-dir` is worse than all of them. It means the arm reached the held
163
+ * eval suite (the `expected_output` ANSWER KEY) or the grader dir (the run's
164
+ * integrity nonce). Reaching a copy of the skill inflates the control arm;
165
+ * reaching the answer key inflates BOTH arms, so it does not even show up as a
166
+ * shrunken delta — see {@link vatPrivateDirNeedles}.
167
+ */
168
+ kind: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
169
+ /**
170
+ * The matched token — a path SUFFIX, a verbatim skill line, or the executable
171
+ * basename.
172
+ *
173
+ * ⚠️ PATH MATCHES ARE TRUNCATED TO THEIR LAST TWO SEGMENTS ON PURPOSE, and that
174
+ * is not cosmetic. Needle sets run longest-first, so the needle that matches a
175
+ * full-path reach is the ENTIRE absolute root — on Windows
176
+ * `C:/Users/<username>/AppData/Local/Temp/vat-skill-test/<key>`, which puts the
177
+ * operator's login name in an artifact adopters attach to bug reports, in the one
178
+ * field that reads like a short opaque token. The last two segments identify the
179
+ * directory just as well (`vat-skill-test/<key>`) and name nobody. See
180
+ * {@link redactPathMatch}; the untruncated path is still visible in `excerpt`,
181
+ * which carries its own warning.
182
+ *
183
+ * A skill-content match is a verbatim line of the skill's own body and is NOT
184
+ * truncated — it has no `/`-separated structure to trim, and the whole point of
185
+ * that signal is that the operator can find the line in SKILL.md by eye.
186
+ */
187
+ match: z.ZodString;
188
+ /**
189
+ * A short excerpt of the transcript around the match, for triage.
190
+ *
191
+ * RAW TRANSCRIPT — the arm's own bytes, its own casing, its own separators,
192
+ * bounded and whitespace-collapsed. That is a promise this field has to keep to
193
+ * be worth anything: its only use is to be carried back to the transcript and
194
+ * searched for, and it once came from the NORMALIZED haystack instead, so
195
+ * `SKILL.md` was reported as `skill.md` and a JSON-escaped `\n` as `/n` — an
196
+ * operator grepping for the reported excerpt found nothing, in a field that
197
+ * exists solely so they can. The structured scan quotes the raw tool input; the
198
+ * flat and content scans map their match back through {@link ScanHaystack}.
199
+ * (Whitespace collapse is the one transform that survives, and it is stated here:
200
+ * grep for a distinctive WORD from the excerpt, not for the whole line.)
201
+ *
202
+ * NOT redacted. The most likely way a harness path reaches a transcript is an
203
+ * `env` dump, so this window can capture adjacent environment values —
204
+ * `GITHUB_TOKEN=…`, `HOME=/Users/<name>` have both been observed — and
205
+ * `baseline.json` is a file adopters attach to reports. Treat it as sensitive;
206
+ * `grading.json` already quotes transcripts verbatim, so this is the same
207
+ * exposure, not a new one.
208
+ */
209
+ excerpt: z.ZodString;
210
+ }, "strict", z.ZodTypeAny, {
211
+ match: string;
212
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
213
+ excerpt: string;
214
+ }, {
215
+ match: string;
216
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
217
+ excerpt: string;
218
+ }>, "many">;
219
+ }, "strict", z.ZodTypeAny, {
220
+ evalId: string;
221
+ hits: {
222
+ match: string;
223
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
224
+ excerpt: string;
225
+ }[];
226
+ }, {
227
+ evalId: string;
228
+ hits: {
229
+ match: string;
230
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
231
+ excerpt: string;
232
+ }[];
233
+ }>;
234
+ export type BaselineContamination = z.infer<typeof BaselineContaminationSchema>;
235
+ /**
236
+ * The run-level `baselineIntegrity` block stamped into `baseline.json`.
237
+ *
238
+ * `contaminated: true` means at least one WITHOUT-arm eval demonstrably reached
239
+ * the skill; the A/B delta for the run is then NOT interpretable as skill lift.
240
+ * The block is written on EVERY baseline run, clean or not, so its absence
241
+ * always means "this baseline.json predates integrity checking" and never
242
+ * "checked and clean".
243
+ */
244
+ /**
245
+ * A detector that can be ARMED for a run.
246
+ *
247
+ * Deliberately the SAME enum as a hit's `kind`, not a parallel list: each
248
+ * detector produces exactly one kind of hit, so two lists would be a drift
249
+ * hazard for no expressive gain. Which detectors were armed is the difference
250
+ * between "checked and clean" and "nothing was looking":
251
+ *
252
+ * - `harness-path` — vat's staged trees ({@link harnessNeedles}). Always armed.
253
+ * - `sibling-arm` — the other arm's live workspace. Armed only when there is one.
254
+ * - `vat-private-dir` — the held answer key and the grader dir
255
+ * ({@link vatPrivateDirNeedles}).
256
+ * - `declared-executable` — the skill's executables by name. Armed ONLY for a
257
+ * skill that ships executables; an instruction-only skill (the common case)
258
+ * has no such name, so this signal is absent.
259
+ * - `skill-content` — verbatim lines of the staged SKILL.md
260
+ * ({@link skillContentNeedles}). The one signal that does not need the arm to
261
+ * name a path or an executable, and therefore the only cover an
262
+ * instruction-only skill has. Unarmed when the body yields no line distinctive
263
+ * enough to accuse anyone with, which is itself worth seeing.
264
+ */
265
+ export declare const ContaminationSignalSchema: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
266
+ export type ContaminationSignal = z.infer<typeof ContaminationSignalSchema>;
267
+ /** One eval whose two arms were graded against a different number of expectations. */
268
+ export declare const BaselineArmSkewSchema: z.ZodObject<{
269
+ evalId: z.ZodString;
270
+ withTotal: z.ZodNumber;
271
+ withoutTotal: z.ZodNumber;
272
+ }, "strict", z.ZodTypeAny, {
273
+ evalId: string;
274
+ withTotal: number;
275
+ withoutTotal: number;
276
+ }, {
277
+ evalId: string;
278
+ withTotal: number;
279
+ withoutTotal: number;
280
+ }>;
281
+ export type BaselineArmSkew = z.infer<typeof BaselineArmSkewSchema>;
282
+ /**
283
+ * One CONTROL-arm eval that produced no grade at all, and why.
284
+ *
285
+ * Distinct from {@link BaselineArmSkewSchema}, which describes two arms that both
286
+ * graded and disagreed about how deep. This describes an arm that never got as far
287
+ * as a verdict: its executor failed to spawn, stalled, hit the wall clock, or its
288
+ * grader exited non-zero / wrote no fragment / wrote something unparseable / echoed
289
+ * the wrong nonce / graded a different number of expectations than the eval declares.
290
+ *
291
+ * Every one of those used to propagate out of `runPipeline` and fail the WHOLE run
292
+ * (exit 1) — discarding fully-billed treatment work that was perfectly good, and
293
+ * writing no `grading.json` at all. The treatment arm still hard-fails that way,
294
+ * because without a treatment result there is nothing to salvage; the control arm
295
+ * does not, because the thing it feeds (the delta) has a way to say "I cannot be
296
+ * computed" and the thing the operator paid for (the treatment grading) does not.
297
+ *
298
+ * `detail` is the arm-named error message verbatim — it already carries the eval id
299
+ * and the failing stage, and it is the text an operator would otherwise have seen on
300
+ * stderr before the run died.
301
+ */
302
+ export declare const BaselineControlArmFailureSchema: z.ZodObject<{
303
+ evalId: z.ZodString;
304
+ detail: z.ZodString;
305
+ }, "strict", z.ZodTypeAny, {
306
+ detail: string;
307
+ evalId: string;
308
+ }, {
309
+ detail: string;
310
+ evalId: string;
311
+ }>;
312
+ export type BaselineControlArmFailure = z.infer<typeof BaselineControlArmFailureSchema>;
313
+ /**
314
+ * What ONE arm actually graded for ONE eval: how many expectations it was graded
315
+ * against, and how many of them passed.
316
+ *
317
+ * Deliberately ONE type feeding two consumers. {@link armExpectationSkew} reads only
318
+ * `total` — whether subtracting the arms is legal at all — while the delta block
319
+ * reads `passed` to do the subtraction. Deriving those from two separate shapes is
320
+ * exactly the drift this module keeps warning about: the two blocks would be free to
321
+ * disagree about what each arm graded, and the one that disagreed quietly would be
322
+ * believed. `passed` is carried here even though the parity check ignores it so that
323
+ * a single derivation at the call site feeds both.
324
+ */
325
+ export interface ArmEvalGrade {
326
+ evalId: string;
327
+ passed: number;
328
+ total: number;
329
+ }
330
+ /**
331
+ * Evals whose two arms were graded against DIFFERENT numbers of expectations.
332
+ *
333
+ * `--baseline` sells a DELTA, and a delta between two differently-sized
334
+ * denominators is not a delta. vat computes each arm's `summary` from the
335
+ * fragments it received, so both reports are internally consistent by
336
+ * construction and `reconcileGrading` cannot see this — a grader that emitted 2
337
+ * entries for a 3-expectation eval on the control arm yields
338
+ * `baseline.summary = {passed:2,total:2}`, which reads as **100% without the
339
+ * skill**. That is the most damaging direction the number can be wrong in: it
340
+ * says the skill did nothing.
341
+ *
342
+ * Both directions count, and so does an eval graded on only one arm — the missing
343
+ * side is reported as 0 rather than dropped, since a silently absent eval skews
344
+ * the run total exactly as a short-graded one does.
345
+ *
346
+ * THE ONE-ARM CASE IS NOW THE LOAD-BEARING ONE, and it is why this function did not
347
+ * become redundant when the declared-count check landed. Both arms are pinned to the
348
+ * eval's DECLARED expectation count at the grader boundary
349
+ * (`assertExpectationCountDeclared` in eval-grader.ts), so two arms that both
350
+ * produced a fragment can no longer disagree about `total` — which reads like this
351
+ * check has nothing left to find. It has two live inputs it always had and only now
352
+ * carries alone:
353
+ *
354
+ * - an eval the CONTROL arm never graded at all, because its executor or grader
355
+ * broke (see {@link BaselineControlArmFailureSchema}). That is not a hypothetical:
356
+ * it is the routine outcome the run no longer aborts on, so it arrives here on
357
+ * every such run.
358
+ * - an eval graded on one arm only for any other reason — a fragment lost, an arm
359
+ * mislabelled, a future path that grades the arms from different suites.
360
+ *
361
+ * It is also the SINGLE AUTHORITY `computeBaselineDelta` consults before withholding
362
+ * (see baseline-delta.ts). Deleting it would leave the delta with no way to say
363
+ * "these two totals cannot be subtracted", which is the whole reason `null` exists.
364
+ *
365
+ * The declared-count check is the STRONGER cousin this docblock used to say was "NOT
366
+ * here … it needs the suite threaded into the merge". That premise was wrong: the
367
+ * declared list is already in hand at the grader boundary, one call before the
368
+ * fragment is returned, so no threading was needed and the check now lives there.
369
+ * It catches what parity structurally cannot — both graders drifting the SAME way,
370
+ * which keeps parity satisfied while every reported number is wrong.
371
+ */
372
+ export declare function armExpectationSkew(withArm: readonly ArmEvalGrade[], withoutArm: readonly ArmEvalGrade[]): BaselineArmSkew[];
373
+ /**
374
+ * Why one eval's scan could not run at full strength.
375
+ *
376
+ * The structured scan ({@link detectBaselineContamination}) needs three things the
377
+ * transcript is not contractually obliged to provide: parseable stream-json with no
378
+ * dropped lines, and a cwd it can follow from the arm's own workspace through every
379
+ * `cd`.
380
+ *
381
+ * ⚠️ THE TWO FAILURES ARE NOT THE SAME SHAPE, and treating them alike was a defect
382
+ * in its own right. An UNPARSEABLE transcript leaves nothing to walk, so the scan
383
+ * falls back to the flat text match this module used to be — which both over-reports
384
+ * (a `find` that PRINTS a path reads as a reach) and under-reports (a relative reach
385
+ * after a `cd` loses the leading slash a bare-name needle requires). An unevaluable
386
+ * `cd`, or a dropped line, leaves a HOLE instead: everything resolved before it is
387
+ * still validly resolved and is kept, and only what came after and needed a cwd is
388
+ * lost. Falling back for that case discarded good evidence AND reintroduced the flat
389
+ * scanner's over-reporting — one leading `cd $HOME` was enough to turn a pure
390
+ * directory listing into the "reached the answer key" verdict.
391
+ *
392
+ * A fallback that is not announced is the failure mode this whole module exists
393
+ * to prevent: `contaminated: false` from a degraded scan is written with exactly
394
+ * the same bytes as `contaminated: false` from a scan that actually looked.
395
+ * `signals` already separates "checked and clean" from "nothing was armed"; this
396
+ * separates "checked and clean" from "checked with the blunt instrument".
397
+ */
398
+ export declare const BASELINE_SCAN_DEGRADATION_REASONS: readonly ["transcript-unparsed", "cwd-unknown", "cwd-untracked", "transcript-malformed", "glob-unexpanded"];
399
+ export declare const BaselineScanDegradationSchema: z.ZodObject<{
400
+ reason: z.ZodEnum<["transcript-unparsed", "cwd-unknown", "cwd-untracked", "transcript-malformed", "glob-unexpanded"]>;
401
+ /** Human-readable specifics — the offending `cd`, or what the parse produced. */
402
+ detail: z.ZodString;
403
+ /** Which eval degraded. Absent on the value the detector returns; stamped by the caller. */
404
+ evalId: z.ZodOptional<z.ZodString>;
405
+ }, "strict", z.ZodTypeAny, {
406
+ detail: string;
407
+ reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
408
+ evalId?: string | undefined;
409
+ }, {
410
+ detail: string;
411
+ reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
412
+ evalId?: string | undefined;
413
+ }>;
414
+ export type BaselineScanDegradation = z.infer<typeof BaselineScanDegradationSchema>;
415
+ export declare const BaselineIntegritySchema: z.ZodObject<{
416
+ contaminated: z.ZodBoolean;
417
+ /**
418
+ * The evals whose scan did not run at full strength, and why. Empty means every
419
+ * eval got a complete structured scan — the only state in which
420
+ * `contaminated: false` means "looked properly and found nothing".
421
+ *
422
+ * NOT all the same failure: `transcript-unparsed` fell back to the flat text
423
+ * match, while `cwd-untracked` and `transcript-malformed` kept every reach they
424
+ * had resolved and lost only what came after the hole. See
425
+ * {@link BASELINE_SCAN_DEGRADATION_REASONS}.
426
+ */
427
+ degraded: z.ZodArray<z.ZodObject<{
428
+ reason: z.ZodEnum<["transcript-unparsed", "cwd-unknown", "cwd-untracked", "transcript-malformed", "glob-unexpanded"]>;
429
+ /** Human-readable specifics — the offending `cd`, or what the parse produced. */
430
+ detail: z.ZodString;
431
+ /** Which eval degraded. Absent on the value the detector returns; stamped by the caller. */
432
+ evalId: z.ZodOptional<z.ZodString>;
433
+ }, "strict", z.ZodTypeAny, {
434
+ detail: string;
435
+ reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
436
+ evalId?: string | undefined;
437
+ }, {
438
+ detail: string;
439
+ reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
440
+ evalId?: string | undefined;
441
+ }>, "many">;
442
+ /**
443
+ * Whether the two arms were graded against the same expectations, and so
444
+ * whether subtracting one summary from the other means anything.
445
+ *
446
+ * Separate from `contaminated` because they fail differently: contamination
447
+ * says the control HAD the treatment, comparability says the two numbers were
448
+ * never measuring the same thing. A run can be clean and incomparable.
449
+ */
450
+ comparable: z.ZodBoolean;
451
+ /** The evals behind a `comparable: false`; empty when the arms agree. */
452
+ skew: z.ZodArray<z.ZodObject<{
453
+ evalId: z.ZodString;
454
+ withTotal: z.ZodNumber;
455
+ withoutTotal: z.ZodNumber;
456
+ }, "strict", z.ZodTypeAny, {
457
+ evalId: string;
458
+ withTotal: number;
459
+ withoutTotal: number;
460
+ }, {
461
+ evalId: string;
462
+ withTotal: number;
463
+ withoutTotal: number;
464
+ }>, "many">;
465
+ /**
466
+ * Control-arm evals that produced no grade at all — see
467
+ * {@link BaselineControlArmFailureSchema}.
468
+ *
469
+ * A SEPARATE field from `skew` even though every entry here also produces a skew
470
+ * entry (an eval the control arm never graded has `withoutTotal: 0`), because the
471
+ * two say different things to an operator: skew says "the two graders disagreed
472
+ * about the job", this says "half the experiment did not run, and here is the
473
+ * spawn/grader failure that stopped it". Collapsing them would report a dead
474
+ * control arm as a grading-depth disagreement, which points triage at the grader
475
+ * prompt instead of at the timeout.
476
+ */
477
+ controlArmFailures: z.ZodArray<z.ZodObject<{
478
+ evalId: z.ZodString;
479
+ detail: z.ZodString;
480
+ }, "strict", z.ZodTypeAny, {
481
+ detail: string;
482
+ evalId: string;
483
+ }, {
484
+ detail: string;
485
+ evalId: string;
486
+ }>, "many">;
487
+ /** Human-readable verdict, safe to print verbatim. */
488
+ summary: z.ZodString;
489
+ /**
490
+ * Which detectors were ARMED for this run.
491
+ *
492
+ * Without it, `contaminated: false` is written identically whether four signals
493
+ * were active or one, so a clean verdict cannot be told apart from a blind one
494
+ * — and the difference is large: the executable-name signal is the only one
495
+ * that sees an ambient copy of the skill in the adopter's own repo, and it does
496
+ * not exist for a skill that ships no executables.
497
+ *
498
+ * An empty array is the loudest case: nothing was looking, and `contaminated`
499
+ * says nothing at all.
500
+ */
501
+ signals: z.ZodArray<z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>, "many">;
502
+ findings: z.ZodArray<z.ZodObject<{
503
+ evalId: z.ZodString;
504
+ hits: z.ZodArray<z.ZodObject<{
505
+ /**
506
+ * What matched: a path under vat's harness root, a declared executable's name,
507
+ * the OTHER arm's workspace directory, or one of vat's private tmp dirs.
508
+ *
509
+ * `sibling-arm` is a materially different finding from the first two. Those mean
510
+ * the arm found a copy of the skill; this one means it read the treatment arm's
511
+ * live working directory, so the control may be echoing an answer the treatment
512
+ * produced seconds earlier rather than solving anything.
513
+ *
514
+ * `vat-private-dir` is worse than all of them. It means the arm reached the held
515
+ * eval suite (the `expected_output` ANSWER KEY) or the grader dir (the run's
516
+ * integrity nonce). Reaching a copy of the skill inflates the control arm;
517
+ * reaching the answer key inflates BOTH arms, so it does not even show up as a
518
+ * shrunken delta — see {@link vatPrivateDirNeedles}.
519
+ */
520
+ kind: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
521
+ /**
522
+ * The matched token — a path SUFFIX, a verbatim skill line, or the executable
523
+ * basename.
524
+ *
525
+ * ⚠️ PATH MATCHES ARE TRUNCATED TO THEIR LAST TWO SEGMENTS ON PURPOSE, and that
526
+ * is not cosmetic. Needle sets run longest-first, so the needle that matches a
527
+ * full-path reach is the ENTIRE absolute root — on Windows
528
+ * `C:/Users/<username>/AppData/Local/Temp/vat-skill-test/<key>`, which puts the
529
+ * operator's login name in an artifact adopters attach to bug reports, in the one
530
+ * field that reads like a short opaque token. The last two segments identify the
531
+ * directory just as well (`vat-skill-test/<key>`) and name nobody. See
532
+ * {@link redactPathMatch}; the untruncated path is still visible in `excerpt`,
533
+ * which carries its own warning.
534
+ *
535
+ * A skill-content match is a verbatim line of the skill's own body and is NOT
536
+ * truncated — it has no `/`-separated structure to trim, and the whole point of
537
+ * that signal is that the operator can find the line in SKILL.md by eye.
538
+ */
539
+ match: z.ZodString;
540
+ /**
541
+ * A short excerpt of the transcript around the match, for triage.
542
+ *
543
+ * RAW TRANSCRIPT — the arm's own bytes, its own casing, its own separators,
544
+ * bounded and whitespace-collapsed. That is a promise this field has to keep to
545
+ * be worth anything: its only use is to be carried back to the transcript and
546
+ * searched for, and it once came from the NORMALIZED haystack instead, so
547
+ * `SKILL.md` was reported as `skill.md` and a JSON-escaped `\n` as `/n` — an
548
+ * operator grepping for the reported excerpt found nothing, in a field that
549
+ * exists solely so they can. The structured scan quotes the raw tool input; the
550
+ * flat and content scans map their match back through {@link ScanHaystack}.
551
+ * (Whitespace collapse is the one transform that survives, and it is stated here:
552
+ * grep for a distinctive WORD from the excerpt, not for the whole line.)
553
+ *
554
+ * NOT redacted. The most likely way a harness path reaches a transcript is an
555
+ * `env` dump, so this window can capture adjacent environment values —
556
+ * `GITHUB_TOKEN=…`, `HOME=/Users/<name>` have both been observed — and
557
+ * `baseline.json` is a file adopters attach to reports. Treat it as sensitive;
558
+ * `grading.json` already quotes transcripts verbatim, so this is the same
559
+ * exposure, not a new one.
560
+ */
561
+ excerpt: z.ZodString;
562
+ }, "strict", z.ZodTypeAny, {
563
+ match: string;
564
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
565
+ excerpt: string;
566
+ }, {
567
+ match: string;
568
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
569
+ excerpt: string;
570
+ }>, "many">;
571
+ }, "strict", z.ZodTypeAny, {
572
+ evalId: string;
573
+ hits: {
574
+ match: string;
575
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
576
+ excerpt: string;
577
+ }[];
578
+ }, {
579
+ evalId: string;
580
+ hits: {
581
+ match: string;
582
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
583
+ excerpt: string;
584
+ }[];
585
+ }>, "many">;
586
+ }, "strict", z.ZodTypeAny, {
587
+ summary: string;
588
+ contaminated: boolean;
589
+ degraded: {
590
+ detail: string;
591
+ reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
592
+ evalId?: string | undefined;
593
+ }[];
594
+ comparable: boolean;
595
+ skew: {
596
+ evalId: string;
597
+ withTotal: number;
598
+ withoutTotal: number;
599
+ }[];
600
+ controlArmFailures: {
601
+ detail: string;
602
+ evalId: string;
603
+ }[];
604
+ signals: ("harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content")[];
605
+ findings: {
606
+ evalId: string;
607
+ hits: {
608
+ match: string;
609
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
610
+ excerpt: string;
611
+ }[];
612
+ }[];
613
+ }, {
614
+ summary: string;
615
+ contaminated: boolean;
616
+ degraded: {
617
+ detail: string;
618
+ reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
619
+ evalId?: string | undefined;
620
+ }[];
621
+ comparable: boolean;
622
+ skew: {
623
+ evalId: string;
624
+ withTotal: number;
625
+ withoutTotal: number;
626
+ }[];
627
+ controlArmFailures: {
628
+ detail: string;
629
+ evalId: string;
630
+ }[];
631
+ signals: ("harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content")[];
632
+ findings: {
633
+ evalId: string;
634
+ hits: {
635
+ match: string;
636
+ kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
637
+ excerpt: string;
638
+ }[];
639
+ }[];
640
+ }>;
641
+ export type BaselineIntegrity = z.infer<typeof BaselineIntegritySchema>;
642
+ /**
643
+ * One needle plus the string that is safe to REPORT when it matches.
644
+ *
645
+ * The two are separate because they answer different questions and a single string
646
+ * cannot do both: the needle has to be the arm's own spelling or it matches nothing,
647
+ * while `match` is quoted into bug reports and must name no person. Computing the
648
+ * report at CONSTRUCTION is also the only place the provenance is known — a
649
+ * suffix needle looks like a complete two-segment path by the time a hit is built,
650
+ * and that is exactly how it came to be emitted with no truncation marker.
651
+ */
652
+ export interface PathNeedle {
653
+ /** Normalized; matched at a path boundary. */
654
+ needle: string;
655
+ /** Redacted, and marked `…/` whenever it is not the whole thing it came from. */
656
+ match: string;
657
+ }
658
+ export interface DetectBaselineContaminationInput {
659
+ /** The WITHOUT arm's captured stream-json transcript. */
660
+ transcript: string;
661
+ /**
662
+ * vat's harness root. Any mention of it in the control arm's transcript means
663
+ * the arm reached into vat's own staged trees — which, now that neither the
664
+ * prompt nor the cwd points there, it could only do by going looking.
665
+ */
666
+ harnessRoot: string;
667
+ /**
668
+ * The skill's declared executables as SKILL-RELATIVE PATHS, exactly as the
669
+ * packaging config spells them (`scripts/csvsum.py`), from
670
+ * `deriveDeclaredExecutableNames`.
671
+ *
672
+ * ⚠️ PATHS, NOT NAMES, and the difference is the whole signal — see
673
+ * {@link reachIsDeclaredExecutable}. This field used to carry the
674
+ * extension-stripped basename, which convicted a clean control arm for reading
675
+ * its own `/tmp/summary.txt`.
676
+ */
677
+ executablePaths?: readonly string[];
678
+ /**
679
+ * ⛔ NOT A FIELD — A TRIPWIRE. `executableNames` was the previous spelling and
680
+ * carried extension-stripped basenames; it is gone, and `never` is here so that a
681
+ * caller still passing it FAILS TO COMPILE.
682
+ *
683
+ * Declared because the renaming alone was silent. The harness builds this object
684
+ * with conditional SPREADS (`...(x === undefined ? {} : { executableNames: … })`),
685
+ * and a spread is not a fresh object literal, so TypeScript applies no
686
+ * excess-property check: dropping the old name simply left the property unread and
687
+ * the `declared-executable` signal unarmed on every run — reported as
688
+ * `contaminated: false` by a detector that was no longer looking. That is precisely
689
+ * the "silently clean" failure this module exists to prevent, so the rename gets a
690
+ * compile error rather than a comment.
691
+ */
692
+ executableNames?: never;
693
+ /**
694
+ * The control arm's OWN workspace root, so a path under it is recognised as the
695
+ * arm's own scratch file rather than a reach. The executor prompt states the
696
+ * working directory absolutely, so the arm reuses that absolute path routinely;
697
+ * without this every such mention would read as an escape.
698
+ *
699
+ * Absent → nothing is suppressed, which is the conservative direction for a
700
+ * caller that has not threaded it: the check gets noisier, never blinder.
701
+ */
702
+ armWorkspaceDir?: string;
703
+ /**
704
+ * The arm's actual STARTING working directory — the per-eval workspace
705
+ * `<armWorkspaceDir>/<evalId>`, which is what the executor is spawned in.
706
+ *
707
+ * Distinct from `armWorkspaceDir` on purpose, and the difference is one
708
+ * directory level that decides every relative reach in the transcript. The cwd
709
+ * walk anchors here; containment ("is this the arm's own file?") is judged
710
+ * against `armWorkspaceDir`, which covers the arm's whole tree. Anchoring the
711
+ * walk one level too high would resolve `cd ../../..` to the temp dir's PARENT,
712
+ * and every subsequent relative path — the entire class of reach this scan
713
+ * exists to catch — would land somewhere no needle can see, reporting clean.
714
+ *
715
+ * Defaults to `armWorkspaceDir` when absent, which is the closest anchor
716
+ * available and still better than none.
717
+ */
718
+ armCwd?: string;
719
+ /**
720
+ * Verbatim lines lifted from the staged SKILL.md by {@link skillContentNeedles}
721
+ * — the ONLY signal that sees an ambient copy of an instruction-only skill,
722
+ * which ships nothing this module could match by name or path.
723
+ */
724
+ skillContentNeedles?: readonly string[];
725
+ /**
726
+ * The OTHER arm's workspace directory — for a skill-absent eval, the treatment
727
+ * arm's live working directory.
728
+ *
729
+ * Not a path into vat's staged copies, so no harness needle can see it: the two
730
+ * arm dirs are siblings under one workspaces root, and `existsSync` on
731
+ * `../<sibling>/<id>/fixtures/input.md` from the control arm returns TRUE. Round 3
732
+ * gave each arm its own directory, which stopped them writing over each other; it
733
+ * did not stop one READING the other, and reading the treatment arm's output
734
+ * mid-run lets the control score like the treatment while producing a transcript
735
+ * with no harness path in it at all.
736
+ *
737
+ * Prevention is now the opaque per-arm token (the sibling's directory name is
738
+ * unguessable and no longer spells `with`), so this is the detection half: if the
739
+ * arm names it anyway — because it enumerated `..` — say so.
740
+ *
741
+ * The ABSOLUTE path to that directory; the needles are derived from it.
742
+ */
743
+ siblingArmDir?: string;
744
+ /**
745
+ * VAT's private tmp dirs for this run — the held eval suite (the answer key) and
746
+ * the grader dir (the integrity nonce). Absolute paths; see
747
+ * {@link vatPrivateDirNeedles} for why these need a detector of their own and
748
+ * why the useful needle is a name PREFIX rather than the token.
749
+ *
750
+ * Never pass the workspaces root here: it is the arm's own legitimate cwd, and a
751
+ * prefix needle built from it fires on every clean run.
752
+ */
753
+ vatPrivateDirs?: readonly (string | undefined)[];
754
+ }
755
+ /**
756
+ * The needles that mean "this transcript named the OTHER arm's workspace".
757
+ *
758
+ * Two: the full path, and the bare per-arm token. The token is 16 hex characters
759
+ * minted per run, so a boundary-anchored match on it is essentially free of false
760
+ * positives — the arm's own token is different, and nothing else on the machine
761
+ * spells it.
762
+ *
763
+ * The LEADING `/` requirement (via {@link needsLeadingBoundary}) is what keeps
764
+ * `ls ..` honest: listing the parent prints the sibling's directory name as a bare
765
+ * basename on its own line, which is "I looked around", not "I read the other arm".
766
+ * Every genuine reach — `cat ../<token>/e1/out.md`, an absolute path, `find ..
767
+ * -name '*.md'` printing `../<token>/…` — carries the slash.
768
+ */
769
+ export declare function siblingArmNeedles(siblingArmDir: string): PathNeedle[];
770
+ /**
771
+ * The needles that mean "this transcript reached a dir only VAT is supposed to
772
+ * see" — the held eval suite (`<tmp>/vat-skill-evals-<token>`, which holds the
773
+ * `expected_output` ANSWER KEY for a fetched-artifact subject) and the grader dir
774
+ * (`<tmp>/vat-skill-grade-<token>`, which holds the run's integrity nonce).
775
+ *
776
+ * These are a FOURTH reachable class the harness-path detector cannot see: they
777
+ * are siblings of the arm's cwd under the OS temp dir, contain no harness path,
778
+ * and are two hops (`ls ../..`) or one `$TMPDIR` expansion away. Worse than
779
+ * reaching the skill — reaching the answer key inflates BOTH arms, so it cannot
780
+ * show up as a shrunken delta at all.
781
+ *
782
+ * Three needles, longest-first: the full path, the whole dir NAME, and the
783
+ * dir-name prefix with the per-run token stripped (`vat-skill-evals-`).
784
+ *
785
+ * The prefix is what catches the reach an arm can actually type. The token is 16
786
+ * random hex it has no way to know, so it globs — `cat $TMPDIR/vat-skill-evals-&#42;/
787
+ * evals.json` — and that string contains neither the full path nor the name. (One
788
+ * `&#42;` defeating a needle that embeds a random key is the same trap that once
789
+ * made the whole harness-suffix scheme inert.)
790
+ *
791
+ * The prefix needle carries no `/`, so {@link needsLeadingBoundary} requires one
792
+ * before it — without that, `ls $TMPDIR` printing `vat-skill-evals-abc` on its own
793
+ * line stamps a clean run contaminated. That rule is also why this must NEVER be
794
+ * built for the workspaces root: `vat-skill-test-ws-` is the arm's own legitimate
795
+ * cwd and its absolute path is in the arm's own prompt, so the needle would fire
796
+ * on every single clean run.
797
+ */
798
+ export declare function vatPrivateDirNeedles(dir: string): PathNeedle[];
799
+ /**
800
+ * Verbatim lines from the skill body that mean "this transcript SAW the skill".
801
+ *
802
+ * THE GAP THIS FILLS. Every other signal in this module needs the arm to name a
803
+ * PATH or an executable. An instruction-only skill — the common case — ships no
804
+ * executable, and the two ambient classes vat cannot remove (the adopter's own
805
+ * repo/build output, the installed plugin cache) produce no harness path. So
806
+ * `grep -rl "<phrase>" .` → `Read` → answer was completely invisible, and
807
+ * `contaminated: false` meant "saw no evidence" while reading as "verified none".
808
+ * Content is the only thing that survives all three of those steps: a `grep` hit
809
+ * line, a `Read` echo, and an answer that quotes the skill with no path attached.
810
+ *
811
+ * Selection is deterministic and needs no corpus: LONGEST first, because length is
812
+ * rarity for free. A candidate must be plain body prose —
813
+ *
814
+ * - frontmatter is skipped (the `name:` is the skill's own identifier and shows
815
+ * up in ordinary config and prose; it is not distinctive enough to accuse
816
+ * anyone with);
817
+ * - fenced code is skipped, since a code block is often copied from a shared
818
+ * upstream and is the same in every skill that wraps the same tool;
819
+ * - headings, table rows and block quotes are skipped as structural.
820
+ *
821
+ * 📌 A rule that lines carrying `"` or `\` are unusable has been REMOVED. It was
822
+ * true while the haystack was the RAW stream-json, where such characters are
823
+ * escaped and {@link normalizeForMatch} then rewrites the escape, so the needle
824
+ * could never match its own text. The content signal now reads
825
+ * {@link contentHaystackFor} — the DECODED assistant text, tool inputs and tool
826
+ * results — where there is no escaping to defeat, so those lines are usable
827
+ * needles again, and dropping them was silently costing quote-heavy skills their
828
+ * only signal. Re-add the rule only if the content haystack goes back to raw JSON.
829
+ *
830
+ * `excludedText` is every channel through which VAT ITSELF hands the arm text:
831
+ * the run's eval prompts, its `expected_output`, its expectations, AND the
832
+ * contents of the input `files` fixtures VAT stages into the arm's workspace and
833
+ * tells it to work on. This is not optional care in any of the four. An adopter
834
+ * who quotes a sentence of their SKILL.md in a prompt — or ships a fixture that
835
+ * does — would otherwise stamp EVERY run contaminated, including the arm that
836
+ * merely read the input it was given, and the attached triage instruction
837
+ * ("uninstall the ambient copy of the plugin") would send the operator hunting
838
+ * something that does not exist. The skill's own words reaching the arm through
839
+ * vat are not the arm reaching the skill.
840
+ */
841
+ export declare function skillContentNeedles(skillMarkdown: string, excludedText?: string): string[];
842
+ /**
843
+ * What a tool call DOES with the paths in its input.
844
+ *
845
+ * `write` is the one that is not about reading at all, and it exists because its
846
+ * absence was a false-positive engine. {@link segmentReaches} used to stamp every
847
+ * path word in a segment with the segment's single intent, so the DESTINATION of a
848
+ * redirect or a copy inherited `retrieval` from the command that produced the
849
+ * bytes — and declared executable names are basenames minus extension, i.e.
850
+ * `summary`, `report`, `index`, `run`. `sort data.csv > /tmp/summary.txt`,
851
+ * `cp notes.md /tmp/summary.md` and `cat parts/*.json > /tmp/index.json` each
852
+ * reported `declared-executable`: the arm was accused of RUNNING the skill's tool
853
+ * on the strength of having written a file with an ordinary name. A write target
854
+ * is not evidence that anything ran.
855
+ */
856
+ export type ToolIntent = 'retrieval' | 'enumeration' | 'write' | 'other';
857
+ /** What a scan of one skill-absent transcript produced. */
858
+ export interface BaselineContaminationScan {
859
+ /**
860
+ * Stable order: harness path, sibling arm, vat private dirs, skill content,
861
+ * then declared executables in declared order. At most one per group.
862
+ */
863
+ hits: BaselineContaminationHit[];
864
+ /** Present only when the structured scan could not run — see the schema. */
865
+ degraded?: BaselineScanDegradation;
866
+ }
867
+ /**
868
+ * Scan the skill-absent arm's transcript for proof it reached the skill.
869
+ *
870
+ * Pure + unit-testable. See the module-level block above {@link ToolIntent} for
871
+ * the architecture and for the two demonstrated defects that forced it.
872
+ */
873
+ export declare function detectBaselineContamination(input: DetectBaselineContaminationInput): BaselineContaminationScan;
874
+ /**
875
+ * Which contamination detectors are ARMED for a run, derived from the SAME input
876
+ * object handed to {@link detectBaselineContamination} so the two cannot disagree
877
+ * about what was checked. A signal counts as armed only when it has a non-empty
878
+ * needle set — a dir that was never threaded through produces no needles and must
879
+ * not be reported as "checked".
880
+ */
881
+ export declare function activeContaminationSignals(input: DetectBaselineContaminationInput): ContaminationSignal[];
882
+ /**
883
+ * Everything the run-level integrity block is assembled from.
884
+ *
885
+ * An OBJECT, and every field REQUIRED — no optionals, no defaults. Each of these
886
+ * five is a coverage claim, and every one of them is believed in the same
887
+ * direction: an absent `signals` reads as "checked by nothing", an absent
888
+ * `degraded` reads as "every scan was structured", an absent `controlArmFailures`
889
+ * reads as "both arms ran". A default on any of them lets a caller overclaim
890
+ * silently; a required field is a compile error the day a new call site appears.
891
+ *
892
+ * `degraded` was defaulted to `[]` for exactly one release and the harness never
893
+ * passed it, so `baseline.json` read identically whether the contamination scan
894
+ * walked the transcript or fell back blind. That is the whole reason the rule is
895
+ * written down here rather than assumed.
896
+ */
897
+ export interface SummarizeBaselineIntegrityInput {
898
+ /** Per-eval contamination findings; empty on a clean run. */
899
+ findings: readonly BaselineContamination[];
900
+ /** Which detectors were ARMED for this run — see {@link activeContaminationSignals}. */
901
+ signals: readonly ContaminationSignal[];
902
+ /** Evals whose two arms are not comparable — see {@link armExpectationSkew}. */
903
+ skew: readonly BaselineArmSkew[];
904
+ /** Evals whose scan fell back to flat matching — see {@link BaselineScanDegradationSchema}. */
905
+ degraded: readonly BaselineScanDegradation[];
906
+ /** Control-arm evals that never produced a grade — see {@link BaselineControlArmFailureSchema}. */
907
+ controlArmFailures: readonly BaselineControlArmFailure[];
908
+ /**
909
+ * How many CONTROL-ARM evals were actually scanned for contamination.
910
+ *
911
+ * ⛔ REQUIRED, and for the same reason the other four are: every one of these is a
912
+ * coverage claim, and a default lets a caller overclaim silently in the direction
913
+ * where the claim gets believed. Confirmed on a real run where both control arms
914
+ * died on an executor timeout, so ZERO transcripts were scanned and `baseline.json`
915
+ * still opened with "No skill-absent eval was observed reaching the skill. The A/B
916
+ * delta is interpretable as instruction lift" — two false sentences, with the
917
+ * correction arriving third. A count of zero means nothing was looked at, which is
918
+ * not the same finding as looking and seeing nothing, and
919
+ * {@link summarizeBaselineIntegrity} now leads with the absence rather than
920
+ * burying it.
921
+ *
922
+ * Pass the number of control-arm evals whose transcript reached
923
+ * {@link detectBaselineContamination}, NOT the number of evals declared.
924
+ */
925
+ observedEvals: number;
926
+ }
927
+ /**
928
+ * Assemble the run-level integrity block from every WITHOUT-arm eval's findings.
929
+ * `findings` is empty on a clean run — the block is still emitted, with
930
+ * `contaminated: false`, so "checked and clean" is distinguishable from
931
+ * "never checked".
932
+ *
933
+ * `comparable` is derived from `skew` ALONE, deliberately, even though a
934
+ * `controlArmFailures` entry also means the arms cannot be subtracted. `skew` is
935
+ * the single authority `computeBaselineDelta` consults, and a second input to
936
+ * `comparable` here would let this block say "not comparable" while the delta block
937
+ * beside it printed a number — the exact two-blocks-disagreeing failure this module
938
+ * keeps warning about. It costs nothing: a control-arm failure removes that eval's
939
+ * control grade entirely, and {@link armExpectationSkew} reports a one-armed eval as
940
+ * skew by construction, so the two always agree. See the run-harness test that pins
941
+ * that derivation end to end rather than trusting this sentence.
942
+ */
943
+ export declare function summarizeBaselineIntegrity(input: SummarizeBaselineIntegrityInput): BaselineIntegrity;
944
+ //# sourceMappingURL=baseline-integrity.d.ts.map