@bendyline/gezel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,693 @@
1
+ /**
2
+ * ─ Shared deliverable checks ─────────────────────────────────────────
3
+ *
4
+ * Pure, dependency-free predicates over file content and file listings.
5
+ * The ONE source of truth consumed by three surfaces that must agree:
6
+ *
7
+ * 1. the gate engine's declarative checks (service/tasks/gate-eval.ts)
8
+ * 2. the standard gate-script library (packages/script-stdlib, via the
9
+ * bundled `@bendyline/gezel-sdk/checks` re-export)
10
+ * 3. the eval harness's success sniffs (evals/src)
11
+ *
12
+ * Keep this directory zod-free and Node-API-free: it is bundled into the
13
+ * sandbox SDK (`noExternal`), and its failure prose is shown verbatim to
14
+ * users and models.
15
+ */
16
+ /** Read-only view of a workspace the file checks evaluate against. */
17
+ interface WorkspaceLike {
18
+ /** File content (relative path), or null if absent. */
19
+ read(file: string): Promise<string | null>;
20
+ /** Relative paths of all files (for count/scan checks). */
21
+ list(): Promise<string[]>;
22
+ /**
23
+ * Raw bytes (relative path), or null if absent. OPTIONAL: only the
24
+ * surfaces that can serve bytes implement it, and only the checks that
25
+ * genuinely need bytes (image-signature validation) call it. Text
26
+ * checks must keep using `read` — decoding binary through `read`
27
+ * is lossy, which is precisely why an extension-only image count was
28
+ * gameable with text stubs. A check that requires bytes must degrade
29
+ * explicitly when this is absent rather than silently passing.
30
+ */
31
+ readBytes?(file: string): Promise<Uint8Array | null>;
32
+ }
33
+ interface CheckResult {
34
+ ok: boolean;
35
+ /**
36
+ * One human-readable line. On failure: the concrete gap to fix (this
37
+ * exact prose lands in gate-rejection messages). On success: a brief
38
+ * diagnostic ("index.html is 4312 bytes").
39
+ */
40
+ detail: string;
41
+ }
42
+
43
+ /**
44
+ * HTML deliverable checks: truncation detection, inline-script
45
+ * extraction, V8 syntax validation, and the two content sniffs the
46
+ * craftbook runtime uses (`html-complete`, `html-game`).
47
+ *
48
+ * Ported from evals/src/html-validation.ts and the service's
49
+ * chat/step-sniff.ts so all three consumers share one implementation —
50
+ * including the wild-caught failure modes documented inline.
51
+ */
52
+ /**
53
+ * Minimum inline JS bytes for an "interactive game" page to count as
54
+ * non-skeleton. matrix calibration: a tight tic-tac-toe game
55
+ * (state, win-detect, click handler, reset) fits cleanly in ~2.5 KB; 4 KB
56
+ * rejected real working games; 2 KB rejects skeletons while letting
57
+ * tight implementations through.
58
+ */
59
+ declare const MIN_INLINE_JS_BYTES = 2048;
60
+ /**
61
+ * Count `<script>` openers vs `</script>` closers. When closers <
62
+ * openers the document was truncated mid-script and inline-JS
63
+ * extraction silently dropped a real body.
64
+ */
65
+ declare function detectUnclosedScript(html: string): {
66
+ opens: number;
67
+ closes: number;
68
+ unclosed: boolean;
69
+ };
70
+ interface InlineScript {
71
+ /** The raw inline JS body (between `<script>` open and close). */
72
+ body: string;
73
+ /** Attribute string after the `<script` tag name (e.g. `type="module"`). */
74
+ attrs: string;
75
+ }
76
+ /**
77
+ * Every inline `<script>` body in document order. External scripts
78
+ * (`src=`) and non-JS types (JSON-LD etc.) are skipped.
79
+ */
80
+ declare function extractInlineScripts(html: string): InlineScript[];
81
+ interface ScriptValidation {
82
+ /** Sum of `body.length` across all inline non-empty scripts. */
83
+ totalBytes: number;
84
+ /** True iff every script body parses without SyntaxError. */
85
+ allParse: boolean;
86
+ /** Per-script parse status. */
87
+ perScript: Array<{
88
+ bytes: number;
89
+ parses: boolean;
90
+ error?: string;
91
+ }>;
92
+ /** First parse error encountered, for the failure line. */
93
+ firstError?: string;
94
+ }
95
+ /**
96
+ * Try-parse each script body via `new Function(body)` — V8's parser.
97
+ * `type="module"` scripts skip the parse (top-level `import` fails under
98
+ * the function-body parser) but still count toward size; a genuinely
99
+ * broken module fails at the runtime-render layer instead.
100
+ */
101
+ declare function validateScriptSyntax(scripts: ReadonlyArray<InlineScript>): ScriptValidation;
102
+ /**
103
+ * Conservative sniff for TypeScript-only constructs inside a script that
104
+ * already FAILED to parse as JavaScript. Only patterns that are never
105
+ * valid JS: postfix non-null assertions, `as` casts before a delimiter,
106
+ * parameter/property type annotations with a following identifier-ish
107
+ * type, and `interface`/`enum` declarations. Returns a short description
108
+ * of the first construct found (with an excerpt), or null.
109
+ */
110
+ declare function detectTypeScriptOnlySyntax(body: string): string | null;
111
+ /** Total trimmed bytes of inline `<script>` bodies. */
112
+ declare function inlineJsBytes(html: string): number;
113
+ /**
114
+ * Generic "this HTML file isn't truncated": balanced `<script>` tags
115
+ * (the truncation failure mode is an open `<script>` with no closer)
116
+ * AND a closing `</body>` or `</html>`.
117
+ */
118
+ declare function htmlCompleteSniff(html: string): boolean;
119
+ /**
120
+ * "Plausibly a real browser game": a game *surface*, at least one CLOSED
121
+ * script, and non-trivial inline JS (default floor 400 bytes —
122
+ * `advanceWhen.minBytes` guards total size separately).
123
+ *
124
+ * A "surface" is a canvas/SVG render target OR a real animation/tick loop
125
+ * (`requestAnimationFrame`, `setInterval`/`setTimeout`, or a conventional
126
+ * frame function). The loop branch matters: plenty of legitimate games —
127
+ * board games, multi-screen arcade games, anything that animates by
128
+ * mutating the DOM each frame — never touch `<canvas>`. Requiring a
129
+ * canvas held 2 of 3 genuinely-passing DOM arcade games at the build gate
130
+ * gate-liveness run, while the eval grader (which
131
+ * treats render-surface as one optional signal of six) passed all three.
132
+ * The gate must agree with the grader on what "is a game" means — canvas
133
+ * is one way to be a game, not the definition. The closed-script +
134
+ * substantial-JS floors still exclude static pages and truncated stubs.
135
+ */
136
+ declare function htmlGameSniff(html: string, minJsBytes?: number): boolean;
137
+
138
+ /**
139
+ * File-fact checks: sizes, counts, CSS volume, content patterns. Ported
140
+ * verbatim from service/src/tasks/gate-eval.ts so the failure prose
141
+ * users see is byte-identical wherever a check runs.
142
+ */
143
+ declare function fileMinBytes(ws: WorkspaceLike, file: string, bytes: number, trim?: boolean): Promise<CheckResult>;
144
+ declare function fileMinLines(ws: WorkspaceLike, file: string, minLines: number): Promise<CheckResult>;
145
+ declare function totalMinBytes(ws: WorkspaceLike, files: string[], bytes: number): Promise<CheckResult>;
146
+ declare function fileCountByExt(ws: WorkspaceLike, ext: string[], min: number, dir?: string, opts?: {
147
+ verifyImageBytes?: boolean;
148
+ }): Promise<CheckResult & {
149
+ matched: string[];
150
+ }>;
151
+ /**
152
+ * `<style>` blocks + inline `style=""` attributes + linked local
153
+ * stylesheets in `file` total ≥ `bytes`. Style attributes count because a
154
+ * fully-inline-styled page is real CSS work — ignoring them false-failed
155
+ * valid pages that never opened a `<style>` block.
156
+ */
157
+ declare function cssMinBytes(ws: WorkspaceLike, bytes: number, file?: string): Promise<CheckResult>;
158
+ declare function containsPattern(ws: WorkspaceLike, file: string, pattern: string, flags?: string, label?: string): Promise<CheckResult>;
159
+ declare function notContainsPattern(ws: WorkspaceLike, file: string, pattern: string, flags?: string, label?: string): Promise<CheckResult>;
160
+ /**
161
+ * Grep-returns-results across the workspace: at least `minMatches` files
162
+ * (optionally under `dir`, optionally filtered to `ext`) whose content
163
+ * matches `pattern`.
164
+ */
165
+ declare function grepMatches(ws: WorkspaceLike, pattern: string, opts?: {
166
+ dir?: string;
167
+ ext?: string[];
168
+ flags?: string;
169
+ minMatches?: number;
170
+ }): Promise<CheckResult & {
171
+ matched: string[];
172
+ }>;
173
+
174
+ /**
175
+ * Reference-resolution checks: do the things a document points at
176
+ * actually exist? Ported from the petShop eval sniff
177
+ * (evals/src/success-check.ts) — its "working image link" rule caught
178
+ * the most common broken-deliverable mode: assets generated but linked
179
+ * from the wrong path.
180
+ */
181
+ declare const IMG_EXT: RegExp;
182
+ /**
183
+ * Resolve a relative path against a base file's directory using POSIX
184
+ * semantics (`..` walks up, `.` is no-op, leading `/` is project root).
185
+ * Returns null for external/anchor/data refs or paths escaping the root.
186
+ */
187
+ declare function resolveRelative(basePath: string, srcRaw: string): string | null;
188
+ /** Every `<img src="…">` value in document order. */
189
+ declare function findImageRefs(html: string): string[];
190
+ interface ImageRefsReport {
191
+ ok: boolean;
192
+ detail: string;
193
+ /** Refs that point at image files which do NOT exist in the project. */
194
+ broken: string[];
195
+ /** Count of refs that resolve to real files. */
196
+ working: number;
197
+ /** Total image-shaped refs found. */
198
+ total: number;
199
+ }
200
+ /**
201
+ * Check that `<img>` refs in `html` resolve to real files.
202
+ * `requireAll: false` (default) = at least one working image ref;
203
+ * `true` = every image-shaped ref must resolve.
204
+ */
205
+ declare function imageRefsResolve(html: string, htmlPath: string, projectFiles: readonly string[], requireAll?: boolean): ImageRefsReport;
206
+
207
+ /**
208
+ * Static-scan an ESM/JS/TS file for two high-confidence import errors that
209
+ * break the module at LOAD time (so nothing runs) yet slip past the
210
+ * inline-`<script>` JS-parse gate — which skips `type="module"` and never
211
+ * sees a standalone `.mjs`/`.ts`:
212
+ *
213
+ * 1. A named import pulled from the WRONG `node:` builtin (the canonical
214
+ * case: `import { dirname } from 'node:url'` — `dirname` is `node:path`).
215
+ * Throws `SyntaxError: … does not provide an export named …`.
216
+ * 2. `require(...)` inside a `.mjs` file — `require` is not defined in ESM
217
+ * and throws at runtime. (Restricted to `.mjs`, where ESM is
218
+ * unambiguous; a bare `.js`/`.ts` may legitimately be CommonJS.)
219
+ *
220
+ * Returns the FIRST issue with a prescriptive fix (the "name one gap"
221
+ * discipline). A file with no `node:` named imports — and no require() in a
222
+ * `.mjs` — passes; there is nothing this check can be confident about.
223
+ */
224
+ declare function esmImports(content: string, file?: string): CheckResult;
225
+ /**
226
+ * Standalone-file parse floor for `.js`/`.mjs` sources, without the
227
+ * TypeScript compiler (usable from the sandboxed stdlib). Import/export
228
+ * statements are textually stripped (top-level module syntax can't be
229
+ * function-parsed), then the remainder must parse via `new Function` —
230
+ * catching the dominant truncation / unbalanced-brace failure class the
231
+ * way `validateScriptSyntax` does for inline HTML scripts. TypeScript
232
+ * sources need the service-side `sourceParses` gate check instead.
233
+ */
234
+ declare function standaloneJsParses(content: string, file?: string): CheckResult;
235
+
236
+ /**
237
+ * Text/structure checks: distinct-match counting, ordered Markdown
238
+ * sections, JSON validity. Ported from evals/src/success-check.ts
239
+ * (incident-postmortem's citation + section rules).
240
+ */
241
+
242
+ type JsonScalar = string | number | boolean | null;
243
+ interface JsonPathEqualsResult extends CheckResult {
244
+ actual?: unknown;
245
+ }
246
+ /**
247
+ * Count DISTINCT regex matches in `text` (distinct by capture group 1
248
+ * when present, else by the whole match, case-insensitive). A file cited
249
+ * six times counts once.
250
+ */
251
+ declare function countDistinctMatches(text: string, pattern: RegExp): number;
252
+ /**
253
+ * Verify `text` contains each header in `headers` IN ORDER (later
254
+ * headers must appear AFTER earlier ones). Headers match `^#+\s+<h>$`
255
+ * (case-insensitive, multi-line) — `#`/`##`/`###` all qualify.
256
+ */
257
+ declare function requireOrderedSections(text: string, headers: readonly string[]): {
258
+ ok: true;
259
+ } | {
260
+ ok: false;
261
+ missing: string;
262
+ foundIndex: number;
263
+ };
264
+ declare function jsonValid(content: string): {
265
+ ok: boolean;
266
+ error?: string;
267
+ };
268
+ declare function jsonPathEquals(ws: WorkspaceLike, file: string, path: string, expected: JsonScalar, label?: string): Promise<JsonPathEqualsResult>;
269
+
270
+ /**
271
+ * Grounding + citation checks — the anti-fabrication vocabulary.
272
+ *
273
+ * Ported from the eval graders (evals/src/scenarios/decoy-research.ts) so
274
+ * the gate that fires in production is byte-identical to the one the eval
275
+ * suite proved out:
276
+ *
277
+ * - `valueGrounding` generalizes decoy-research's `checkBriefing`:
278
+ * required facts must be present, forbidden (decoy) values must be
279
+ * absent anywhere — even to contrast them.
280
+ * - `citationsResolve` is the squisq lesson as a mechanical gate: every
281
+ * source a deliverable cites must resolve to a real file (no
282
+ * fabricated paths). URLs can't be fetched offline, so they fail
283
+ * open unless an explicit corpus allowlist is supplied.
284
+ */
285
+ /**
286
+ * Collapse digit-grouping separators so "$4,217,300", "4 217 300", and
287
+ * "4217300" all compare equal. Only separators between a digit and a
288
+ * 3-digit group are touched, so list punctuation ("1, 2, 3") and prose
289
+ * spacing survive. (Ported verbatim from decoy-research.)
290
+ */
291
+ declare function normalizeDigitGroups(text: string): string;
292
+ /** A single grounded fact: at least one `required` form must appear and no
293
+ * `forbidden` (decoy) form may. Values are regex sources, matched
294
+ * case-insensitively against the (optionally digit-normalized) text. */
295
+ interface GroundingFact {
296
+ /** Stable id, surfaced in the failure message. */
297
+ id: string;
298
+ /** Short human label ("Q3 revenue"); falls back to `id`. */
299
+ label?: string;
300
+ /** Value(s) that MUST appear — the fact passes when ANY one matches. */
301
+ required: string[];
302
+ /** Value(s) that must NOT appear anywhere (the decoy twins). */
303
+ forbidden?: string[];
304
+ }
305
+ interface GroundingResult extends CheckResult {
306
+ /** Ids of facts that passed. */
307
+ signals: string[];
308
+ /** Forbidden (decoy) values that were detected, for logs/facts.json. */
309
+ decoysDetected: string[];
310
+ }
311
+ /**
312
+ * Behavioral fact-check over a deliverable's text: for each fact an
313
+ * authorized value must be present and every forbidden twin absent.
314
+ * Pure (string in, verdict out) so it drives gate scripts and the eval
315
+ * grader from one codebase. Reports the FIRST failing fact concretely
316
+ * (Law 3: name the gap, not the rule).
317
+ */
318
+ declare function valueGrounding(text: string, facts: readonly GroundingFact[], opts?: {
319
+ normalizeDigits?: boolean;
320
+ }): GroundingResult;
321
+ interface CitationsResult extends CheckResult {
322
+ /** Cited paths that resolved to a real workspace file. */
323
+ resolved: string[];
324
+ /** Cited paths/URLs that did NOT resolve. */
325
+ unresolved: string[];
326
+ /** Cited URLs (not checked offline unless a corpus allowlist is given). */
327
+ urls: string[];
328
+ }
329
+ /**
330
+ * Every source `file` cites must exist. File-path citations are resolved
331
+ * against the workspace listing (tolerant of leading `./`, `/`, and
332
+ * `workspace/`, case-insensitive). URLs cannot be fetched offline, so
333
+ * they pass unless `corpus` is supplied, in which case every cited path
334
+ * AND URL must be a member of the allowlist. The anti-fabrication gate.
335
+ */
336
+ declare function citationsResolve(ws: WorkspaceLike, file: string, opts?: {
337
+ pattern?: string;
338
+ flags?: string;
339
+ minCitations?: number;
340
+ corpus?: string[];
341
+ }): Promise<CitationsResult>;
342
+ /** Spec for {@link valuesSubsetOf}. */
343
+ interface ValuesSubsetSpec {
344
+ /**
345
+ * Regex source with ONE capture group; every match's group 1 in the
346
+ * output and in each source is a "value". Matched with `g` plus any
347
+ * extra `flags`.
348
+ */
349
+ pattern: string;
350
+ flags?: string;
351
+ /**
352
+ * Presence floor: the output must contain at least this many values
353
+ * (default 0 — a subset check alone passes an output with no values;
354
+ * pair with a shape check, or set a floor here, when values are
355
+ * mandatory).
356
+ */
357
+ minMatches?: number;
358
+ }
359
+ interface ValuesSubsetResult extends CheckResult {
360
+ /** Distinct values found in the output. */
361
+ checked: number;
362
+ /** Output values that appear in NO source (dedup, output order). */
363
+ invented: string[];
364
+ }
365
+ /**
366
+ * Value-conservation check for transform/ETL deliverables: every value the
367
+ * output carries (per `pattern`) must appear verbatim in at least one
368
+ * source text. Catches the classic integrity failure where a model
369
+ * regenerates identifiers instead of preserving them — renumbered record
370
+ * ids, invented ticket refs, made-up SKUs — which survives every
371
+ * shape/schema check because the output LOOKS right. Wild-caught
372
+ * core sweep: 7 of 8 local models failed precision ETL solely
373
+ * by renumbering source ids. Pure (strings in, verdict out) so it drives
374
+ * gate checks, gate scripts, and eval graders from one codebase.
375
+ */
376
+ declare function valuesSubsetOf(outputText: string, sourceTexts: readonly string[], spec: ValuesSubsetSpec): ValuesSubsetResult;
377
+
378
+ interface SecurityReportOptions {
379
+ /** Path to the machine-readable findings JSON. */
380
+ findings?: string;
381
+ /** Section headings the report must contain. */
382
+ requiredSections?: string[];
383
+ /** Minimum systemic themes required once findings ≥ themeThreshold. */
384
+ minThemes?: number;
385
+ /** Findings count at/above which systemic-theme synthesis is required. */
386
+ themeThreshold?: number;
387
+ }
388
+ interface SecurityReportResult extends CheckResult {
389
+ findingCount: number;
390
+ /** Cited files that don't exist in the workspace, for logs/facts. */
391
+ fabricated: string[];
392
+ }
393
+ declare function securityReport(ws: WorkspaceLike, reportFile: string, opts?: SecurityReportOptions): Promise<SecurityReportResult>;
394
+
395
+ /** ISO yyyy-mm-dd AND a real calendar date (ported from data-wrangle). */
396
+ declare function isRealIsoDate(value: string): boolean;
397
+ /**
398
+ * Built-in cell/field types, or any other string is treated as a regex
399
+ * source the value must match. Returns `detail` describing the mismatch.
400
+ */
401
+ type CellType = 'string' | 'nonempty' | 'number' | 'integer' | 'boolean' | 'date' | 'iso-date' | 'email' | (string & {});
402
+ interface ParsedTable {
403
+ headers: string[];
404
+ rows: string[][];
405
+ }
406
+ /** Parse the first GitHub-flavored Markdown table (header row, a `|---|`
407
+ * delimiter row, then contiguous body rows). Returns null if none. */
408
+ declare function parseMarkdownTable(text: string): ParsedTable | null;
409
+ interface TableShapeSpec {
410
+ /** Header names that must be present (case-insensitive). */
411
+ requiredColumns?: string[];
412
+ /** Minimum body-row count. */
413
+ minRows?: number;
414
+ /** Per-column value type (built-in CellType or a regex source). */
415
+ columnTypes?: Record<string, CellType>;
416
+ }
417
+ interface TableShapeResult extends CheckResult {
418
+ headers: string[];
419
+ rowCount: number;
420
+ }
421
+ /** Validate the first Markdown table in `text`: required header set, row
422
+ * floor, and per-column value types. */
423
+ declare function tableShape(text: string, spec: TableShapeSpec): TableShapeResult;
424
+ /**
425
+ * Minimal RFC-4180-ish CSV parser: quoted fields, embedded commas,
426
+ * escaped double-quotes (""), CRLF, and a leading UTF-8 BOM. Returns a
427
+ * grid of trimmed-on-cell cells (callers trim further as needed).
428
+ */
429
+ declare function parseCsv(text: string): string[][];
430
+ /**
431
+ * Cheap, dependency-free sniff: does `text` look like a produced DATA
432
+ * deliverable — a non-empty JSON array of records, a comma-delimited
433
+ * table with a header + at least one data row, or a Markdown table —
434
+ * rather than an empty file or the transform *script* that would
435
+ * produce it? This is the data-class analogue of `htmlCompleteSniff`:
436
+ * it answers "is this plausibly the real output", not "is every value
437
+ * correct" (that's {@link recordSchema}'s job, used when a field schema
438
+ * is known). The shared-column-shape requirement on the delimited path
439
+ * keeps a source file accidentally read as CSV (ragged, mostly
440
+ * single-column lines) from passing as data.
441
+ */
442
+ declare function dataTableSniff(text: string): boolean;
443
+ interface CsvShapeSpec {
444
+ /** Header names that must be present exactly. */
445
+ requiredColumns?: string[];
446
+ /** Complete header row, in order. When set, no extra/missing columns are allowed. */
447
+ exactColumns?: string[];
448
+ /** Minimum data-row count, excluding the header row. */
449
+ minRows?: number;
450
+ /** Reject rows whose cell count differs from the header. Defaults to true. */
451
+ consistentColumns?: boolean;
452
+ /** Per-column allowed values. Empty cells are ignored here; use exactColumns/requiredColumns for shape. */
453
+ allowedValues?: Record<string, string[]>;
454
+ }
455
+ interface CsvShapeResult extends CheckResult {
456
+ headers: string[];
457
+ rowCount: number;
458
+ }
459
+ /** Validate CSV file shape: header, row count, ragged rows, and optional picklist values. */
460
+ declare function csvShape(text: string | null, spec: CsvShapeSpec): CsvShapeResult;
461
+ interface RecordFieldSpec {
462
+ name: string;
463
+ /** Value type; omit (or 'string') for any non-empty string. */
464
+ type?: CellType;
465
+ /** Defaults to true. */
466
+ required?: boolean;
467
+ }
468
+ interface RecordSchemaSpec {
469
+ fields: RecordFieldSpec[];
470
+ /** Reject rows carrying fields not in `fields`. Defaults to false. */
471
+ allowExtraFields?: boolean;
472
+ minRows?: number;
473
+ /** Field whose values must be unique across all rows. */
474
+ uniqueBy?: string;
475
+ /** 'json' | 'csv' | 'auto' (default 'auto' — sniff by first char). */
476
+ format?: 'json' | 'csv' | 'auto';
477
+ }
478
+ interface RecordSchemaResult extends CheckResult {
479
+ rowCount: number;
480
+ }
481
+ /**
482
+ * Validate a JSON array (or CSV) of records against a declared schema.
483
+ * Checks run in a fixed order and STOP at the first failure, naming
484
+ * exactly one gap with the offending value (the data-wrangle discipline).
485
+ * `null` text means the deliverable doesn't exist yet.
486
+ */
487
+ declare function recordSchema(text: string | null, spec: RecordSchemaSpec): RecordSchemaResult;
488
+
489
+ interface WordBandResult extends CheckResult {
490
+ words: number;
491
+ }
492
+ /** Word count within `[min, max]` (either bound optional). */
493
+ declare function wordBand(text: string, opts?: {
494
+ min?: number;
495
+ max?: number;
496
+ stripMarkdown?: boolean;
497
+ }): WordBandResult;
498
+ interface ReadingLevelResult extends CheckResult {
499
+ /** Flesch-Kincaid grade level. */
500
+ grade: number;
501
+ /** Flesch reading-ease (higher = easier). */
502
+ ease: number;
503
+ words: number;
504
+ sentences: number;
505
+ }
506
+ /**
507
+ * Flesch-Kincaid grade level and Flesch reading-ease, banded by any of
508
+ * `minGrade`/`maxGrade`/`minEase`/`maxEase`. Reports the first band the
509
+ * text falls outside of.
510
+ */
511
+ declare function readingLevel(text: string, opts?: {
512
+ minGrade?: number;
513
+ maxGrade?: number;
514
+ minEase?: number;
515
+ maxEase?: number;
516
+ stripMarkdown?: boolean;
517
+ }): ReadingLevelResult;
518
+ /** A canonical term plus the spellings/numberings that should NOT appear. */
519
+ interface EntitySpec {
520
+ canonical: string;
521
+ /** Forbidden variants (drifted spellings, wrong numbers). */
522
+ variants: string[];
523
+ }
524
+ interface EntitiesResult extends CheckResult {
525
+ violations: Array<{
526
+ canonical: string;
527
+ variant: string;
528
+ }>;
529
+ }
530
+ interface UnsupportedClaimPattern {
531
+ /** Regex for a high-risk claim phrase whose wording must be source-grounded. */
532
+ pattern: string;
533
+ /** Repair guidance shown when this pattern matches unsupported prose. */
534
+ label?: string;
535
+ }
536
+ interface UnsupportedClaimViolation {
537
+ pattern: string;
538
+ label?: string;
539
+ match: string;
540
+ }
541
+ interface UnsupportedClaimsResult extends CheckResult {
542
+ violations: UnsupportedClaimViolation[];
543
+ missingSources: string[];
544
+ }
545
+ /**
546
+ * Fail if any declared `variant` of an entity appears in `text` — i.e.
547
+ * the document drifted from the canonical spelling/number somewhere.
548
+ * Case-sensitive (so "ACME" vs "Acme" is caught). Reports the first
549
+ * inconsistency.
550
+ */
551
+ declare function namedEntitiesConsistent(text: string, entities: readonly EntitySpec[]): EntitiesResult;
552
+ /**
553
+ * Fail when high-risk claim wording appears in `file` but the exact
554
+ * matched phrase is absent from the declared source files. This is a
555
+ * deterministic middle ground for factual comms gates: it catches common
556
+ * overclaim/tone drift while still allowing loaded phrases that the source
557
+ * brief explicitly authorized.
558
+ */
559
+ declare function unsupportedClaims(ws: WorkspaceLike, file: string, sourceFiles: readonly string[], patterns: readonly UnsupportedClaimPattern[], opts?: {
560
+ flags?: string;
561
+ maxViolations?: number;
562
+ }): Promise<UnsupportedClaimsResult>;
563
+
564
+ /**
565
+ * Plain-language hints derived from runtime/test output. Failure lines
566
+ * from assertion harnesses name the mismatch but not the mistake; small
567
+ * models keep patching the wrong thing until a hint names it. Generalized
568
+ * from the eval harness's perf-budget `wrapperReturnHint` (0/3 → 3/3 once
569
+ * the wrapper misread was named).
570
+ */
571
+ /**
572
+ * Detect the "returned a wrapper object where an array was expected"
573
+ * shape in assertion output lines (`… expected [...], got {...}`) and
574
+ * name it. Returns null when no line matches — a bare array with wrong
575
+ * contents is a genuinely different mistake and gets no hint.
576
+ */
577
+ declare function wrapperReturnHint(outputLines: readonly string[]): string | null;
578
+
579
+ interface MarkdownHeadingsMatchResult extends CheckResult {
580
+ outlineHeadings: string[];
581
+ documentHeadings: string[];
582
+ mismatchIndex?: number;
583
+ }
584
+ /**
585
+ * Compare a Markdown deck's H1 boundaries with a locked outline whose slides
586
+ * are authored as `## Slide N — Title` (also accepts `## N. Title`).
587
+ */
588
+ declare function markdownHeadingsMatch(ws: WorkspaceLike, file: string, outlineFile: string): Promise<MarkdownHeadingsMatchResult>;
589
+
590
+ /**
591
+ * Human-line explainers for the step sniffs. A gate verdict that says
592
+ * "index.html failed the html-game check" restates the rule; one that
593
+ * says "no render surface and no frame loop — add the game loop" names
594
+ * the model's actual gap (Law 3 of the task-completion strategy: the
595
+ * verdict must quote the failing observation, not the rule). Composed
596
+ * from the same primitives the sniffs themselves use, so the diagnosis
597
+ * can never disagree with the verdict.
598
+ */
599
+ type ExplainableSniff = 'html-complete' | 'html-game' | 'nonempty' | 'json-valid' | 'data-table';
600
+ /**
601
+ * One imperative line explaining why `content` fails the named sniff.
602
+ * Callers only invoke this AFTER the sniff returned false; for content
603
+ * that actually passes, a generic line is returned rather than lying
604
+ * about a defect.
605
+ */
606
+ declare function explainSniff(name: ExplainableSniff, content: string): string;
607
+
608
+ /**
609
+ * Pure helpers for the LLM-judge gate check (`kind: 'judge'`) —
610
+ * prompt building, verdict parsing, and the verbatim-evidence wall.
611
+ * No LLM calls, no filesystem: the service's gate evaluator supplies
612
+ * the artifact text and the one-shot executor.
613
+ *
614
+ * The evidence wall is the growth-proposals pattern (packages/service/
615
+ * src/growth/proposals.ts): a quote survives only when it matches the
616
+ * artifact exactly or as a ≥24-char normalized substring — a judge
617
+ * that fabricates its evidence loses the verdict.
618
+ */
619
+ declare const MIN_JUDGE_EVIDENCE_SUBSTRING = 24;
620
+ interface JudgeVerdict {
621
+ verdict: 'pass' | 'fail';
622
+ reasons: string[];
623
+ evidence: string[];
624
+ confidence?: 'low' | 'medium' | 'high';
625
+ }
626
+ declare function buildJudgePrompt(opts: {
627
+ rubric: string;
628
+ file: string;
629
+ artifactText: string;
630
+ sources?: Array<{
631
+ path: string;
632
+ text: string;
633
+ }>;
634
+ requireEvidence?: boolean;
635
+ }): string;
636
+ /**
637
+ * Extract and validate a JudgeVerdict from a raw model reply. Accepts
638
+ * a ```json fenced block, a bare JSON object, or JSON embedded in
639
+ * surrounding prose (first `{` to last `}`) — the keurmeester parse
640
+ * ladder. Throws when nothing validates.
641
+ */
642
+ declare function parseJudgeVerdict(raw: string): JudgeVerdict;
643
+ /**
644
+ * The verbatim wall: keep only the quotes that actually appear in the
645
+ * artifact (whitespace-normalized exact match, or a substring of the
646
+ * artifact at ≥ MIN_JUDGE_EVIDENCE_SUBSTRING chars).
647
+ */
648
+ declare function validateJudgeEvidence(verdict: JudgeVerdict, artifactText: string): {
649
+ kept: string[];
650
+ dropped: number;
651
+ };
652
+
653
+ /**
654
+ * Structural plan validation (`kind: 'planStructure'`) — the Planner
655
+ * role's mechanical floor. Validates the FIRST Markdown table in the
656
+ * text against the plan contract: `ID | Task | Owner | Depends on |
657
+ * Done when` (+ optional `Estimate`), owners on-roster, dependencies
658
+ * that resolve, contain no cycles, and (by default) point only at
659
+ * EARLIER rows, and done-states long enough to be checkable.
660
+ *
661
+ * Law-3 details: every failure names the offending row and cell
662
+ * verbatim so the rejection is the fix instruction.
663
+ */
664
+ interface PlanStructureSpec {
665
+ minRows?: number;
666
+ /** When given, every Owner must be one of these names (case-insensitive). */
667
+ ownerRoster?: readonly string[];
668
+ /** Rows may only depend on earlier rows (default true). */
669
+ requireEarlierOnly?: boolean;
670
+ /** Minimum Done-when cell length (default 12 chars). */
671
+ doneWhenMinChars?: number;
672
+ }
673
+ interface PlanRow {
674
+ id: string;
675
+ task: string;
676
+ owner: string;
677
+ dependsOn: string[];
678
+ doneWhen: string;
679
+ estimate?: string;
680
+ }
681
+ interface PlanStructureResult {
682
+ ok: boolean;
683
+ /** First failure, row+cell named (empty when ok). */
684
+ detail: string;
685
+ rows: PlanRow[];
686
+ unknownDeps: string[];
687
+ cycleIds: string[];
688
+ missingOwners: string[];
689
+ weakDoneStates: string[];
690
+ }
691
+ declare function planStructure(text: string, spec?: PlanStructureSpec): PlanStructureResult;
692
+
693
+ export { type CellType, type CheckResult, type CitationsResult, type CsvShapeResult, type CsvShapeSpec, type EntitiesResult, type EntitySpec, type ExplainableSniff, type GroundingFact, type GroundingResult, IMG_EXT, type ImageRefsReport, type InlineScript, type JsonPathEqualsResult, type JsonScalar, type JudgeVerdict, MIN_INLINE_JS_BYTES, MIN_JUDGE_EVIDENCE_SUBSTRING, type MarkdownHeadingsMatchResult, type ParsedTable, type PlanRow, type PlanStructureResult, type PlanStructureSpec, type ReadingLevelResult, type RecordFieldSpec, type RecordSchemaResult, type RecordSchemaSpec, type ScriptValidation, type SecurityReportOptions, type SecurityReportResult, type TableShapeResult, type TableShapeSpec, type UnsupportedClaimPattern, type UnsupportedClaimViolation, type UnsupportedClaimsResult, type ValuesSubsetResult, type ValuesSubsetSpec, type WordBandResult, type WorkspaceLike, buildJudgePrompt, citationsResolve, containsPattern, countDistinctMatches, cssMinBytes, csvShape, dataTableSniff, detectTypeScriptOnlySyntax, detectUnclosedScript, esmImports, explainSniff, extractInlineScripts, fileCountByExt, fileMinBytes, fileMinLines, findImageRefs, grepMatches, htmlCompleteSniff, htmlGameSniff, imageRefsResolve, inlineJsBytes, isRealIsoDate, jsonPathEquals, jsonValid, markdownHeadingsMatch, namedEntitiesConsistent, normalizeDigitGroups, notContainsPattern, parseCsv, parseJudgeVerdict, parseMarkdownTable, planStructure, readingLevel, recordSchema, requireOrderedSections, resolveRelative, securityReport, standaloneJsParses, tableShape, totalMinBytes, unsupportedClaims, validateJudgeEvidence, validateScriptSyntax, valueGrounding, valuesSubsetOf, wordBand, wrapperReturnHint };