@snag-run/core 0.3.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.
package/dist/schema.js ADDED
@@ -0,0 +1,573 @@
1
+ import { z } from "zod";
2
+ // failure-map/v1 Zod schema — the interop contract shared by the CLI
3
+ // (generation + render) and, later, the backend. Ported from
4
+ // spikes/failure-map/failure-map.schema.json with the cli-v1.md decisions:
5
+ // - categorical `severity` is DROPPED (scores.severity is the only impact
6
+ // encoding); the band is derived (see severity.ts).
7
+ // - discriminated union on node `type` instead of the spike's if/then.
8
+ // - two schemas: the LLM generation surface ({ nodes, edges }) and the full
9
+ // engine-assembled document (schema + subject + meta envelope).
10
+ // - hard validation via superRefine: unique node ids + edge referential
11
+ // integrity. Soft lint lives in lint.ts.
12
+ const SCHEMA_ID = "failure-map/v1";
13
+ /** The canonical node-id format on the persisted document: lowercase
14
+ * alphanumerics + underscore. Enforced on the final {@link failureMapSchema}
15
+ * (the interop contract a renderer/backend relies on), NOT on the generation
16
+ * surface — see {@link coerceNodeIds}. */
17
+ const NODE_ID_PATTERN = /^[a-z0-9_]+$/;
18
+ const NODE_ID_MESSAGE = "node id must match ^[a-z0-9_]+$";
19
+ /** Node id as the LLM emits it (#280): any non-empty string. The strict
20
+ * `^[a-z0-9_]+$` form is NOT required here — a single malformed id (uppercase,
21
+ * `-`, `.`, a `file:line`-style id, …) used to reject the *entire* generated
22
+ * map at the `generateObject` SDK boundary, and the ~2 retries could not
23
+ * reliably re-roll a clean one (the whole-map-reject brittleness). Ids are
24
+ * mechanical identifiers, so {@link coerceNodeIds} repairs them to
25
+ * {@link NODE_ID_PATTERN} after generation instead of failing. */
26
+ const nodeId = z.string().min(1, "node id must be a non-empty string");
27
+ /**
28
+ * One FMEA risk factor (integer 1-5). `bounded` chooses the JSON-Schema
29
+ * encoding: `minimum`/`maximum` (the natural form) when `true`, or a Zod
30
+ * `.refine()` that carries NO numeric-bound keywords when `false`. The
31
+ * bounds-free form exists for OpenAI-compatible gateways whose structured-output
32
+ * validator rejects `minimum`/`maximum` on integers — AssemblyAI's LLM gateway
33
+ * returns "For 'integer' type, properties maximum, minimum are not supported"
34
+ * (#231). Both forms validate the 1-5 range identically once the model responds;
35
+ * only the schema *hint* sent to the model differs.
36
+ */
37
+ function riskFactor(bounded) {
38
+ const base = z
39
+ .number()
40
+ .int()
41
+ .describe("FMEA risk factor: integer 1-5 (inclusive)");
42
+ return bounded
43
+ ? base.min(1).max(5)
44
+ : base.refine((n) => n >= 1 && n <= 5, {
45
+ message: "FMEA risk factor must be between 1 and 5 (inclusive)",
46
+ });
47
+ }
48
+ function buildScores(bounded) {
49
+ const factor = riskFactor(bounded);
50
+ return z.object({ severity: factor, frequency: factor, detection: factor });
51
+ }
52
+ /** FMEA risk factors (each 1-5). RPN = severity * frequency * detection. The
53
+ * default bounded encoding; see {@link generationSchemaNoNumericBounds} for the
54
+ * gateway-safe variant (#231). */
55
+ export const scoresSchema = buildScores(true);
56
+ // Fields shared by every node. `.object()` (not `.strict()`) strips unknown
57
+ // keys — this is what lets the spike fixture's legacy categorical `severity`
58
+ // parse while keeping it out of the derived type.
59
+ const baseNodeShape = {
60
+ id: nodeId,
61
+ label: z.string(),
62
+ group: z.string().optional(),
63
+ sourceRef: z.string().optional(),
64
+ };
65
+ /** A non-failure node on the map (the happy-path spine + branches). */
66
+ export const flowNodeSchema = z.object({
67
+ ...baseNodeShape,
68
+ type: z.enum(["step", "decision", "terminal", "recovery"]),
69
+ });
70
+ // The failure-node fields *other than* `scores` — shared by the bounded export
71
+ // below and the bounds-free generation variant (#231) so the two can never
72
+ // drift. `scores` is added separately because it is the only field whose
73
+ // encoding varies by provider.
74
+ const failureNodeShapeSansScores = {
75
+ ...baseNodeShape,
76
+ type: z.literal("failure"),
77
+ /** Coarse failure-mode taxonomy for grouping/coverage (#56). Free-form during
78
+ * the discovery phase — required so the model reliably populates it, but NOT
79
+ * an enum yet (the vocabulary is still settling across real maps). */
80
+ class: z.string(),
81
+ silent: z.boolean(),
82
+ handled: z.boolean(),
83
+ reviewWouldMiss: z.boolean(),
84
+ trigger: z.string(),
85
+ manifestation: z.string(),
86
+ suggestedFix: z.string(),
87
+ fixEffort: z.enum(["trivial", "small", "medium", "large"]).optional(),
88
+ /** Engine-stamped stable identity (ADR 0002 seam, #78). A deterministic
89
+ * SHA-256 fingerprint over normalized semantic fields (`class` + `trigger`
90
+ * + `manifestation`). Optional for back-compat with already-committed maps
91
+ * and fixtures; always populated by `assembleDocument` on newly assembled
92
+ * documents. The LLM never produces this field. */
93
+ fingerprint: z.string().optional(),
94
+ };
95
+ /** A failure node / failure mode. Carries the required FMEA fields. */
96
+ export const failureNodeSchema = z.object({
97
+ ...failureNodeShapeSansScores,
98
+ scores: scoresSchema,
99
+ });
100
+ /** Discriminated union on `type`: failure vs. flow node. */
101
+ export const nodeSchema = z.discriminatedUnion("type", [
102
+ failureNodeSchema,
103
+ flowNodeSchema,
104
+ ]);
105
+ export const edgeSchema = z.object({
106
+ from: z.string(),
107
+ to: z.string(),
108
+ type: z.enum(["happy", "branch", "recovery", "failure"]),
109
+ condition: z.string().optional(),
110
+ });
111
+ // Hard validation shared by the generation schema and the full document:
112
+ // unique node ids + edge referential integrity. Both produce a
113
+ // broken/unrenderable artifact, so they reject rather than warn.
114
+ function refineNodesAndEdges(value, ctx) {
115
+ const seen = new Set();
116
+ value.nodes.forEach((node, i) => {
117
+ if (seen.has(node.id)) {
118
+ ctx.addIssue({
119
+ code: z.ZodIssueCode.custom,
120
+ message: `duplicate node id: ${node.id}`,
121
+ path: ["nodes", i, "id"],
122
+ });
123
+ }
124
+ seen.add(node.id);
125
+ });
126
+ value.edges.forEach((edge, i) => {
127
+ if (!seen.has(edge.from)) {
128
+ ctx.addIssue({
129
+ code: z.ZodIssueCode.custom,
130
+ message: `edge references unknown node id in 'from': ${edge.from}`,
131
+ path: ["edges", i, "from"],
132
+ });
133
+ }
134
+ if (!seen.has(edge.to)) {
135
+ ctx.addIssue({
136
+ code: z.ZodIssueCode.custom,
137
+ message: `edge references unknown node id in 'to': ${edge.to}`,
138
+ path: ["edges", i, "to"],
139
+ });
140
+ }
141
+ });
142
+ }
143
+ const nodesAndEdges = {
144
+ nodes: z.array(nodeSchema).min(1),
145
+ edges: z.array(edgeSchema),
146
+ };
147
+ /**
148
+ * The LLM generation surface: the tight { nodes, edges } analysis the model
149
+ * produces. Smallest surface = best structured-output reliability.
150
+ */
151
+ export const generationSchema = z
152
+ .object(nodesAndEdges)
153
+ .superRefine(refineNodesAndEdges);
154
+ /**
155
+ * The bounds-free generation surface (#231): identical to {@link generationSchema}
156
+ * except the FMEA `scores` carry no JSON-Schema `minimum`/`maximum`. The engine
157
+ * sends this to OpenAI-compatible gateways (AssemblyAI, …) whose structured-output
158
+ * validator rejects numeric bounds on integers. The 1-5 range is still enforced —
159
+ * by the `.refine()` on each factor when this schema parses, and again by the
160
+ * full document schema (which keeps the bounded `scoresSchema`) on assembly — so
161
+ * nothing is lost but the in-schema hint to the model.
162
+ */
163
+ const failureNodeSchemaNoNumericBounds = z.object({
164
+ ...failureNodeShapeSansScores,
165
+ scores: buildScores(false),
166
+ });
167
+ const nodeSchemaNoNumericBounds = z.discriminatedUnion("type", [
168
+ failureNodeSchemaNoNumericBounds,
169
+ flowNodeSchema,
170
+ ]);
171
+ export const generationSchemaNoNumericBounds = z
172
+ .object({
173
+ nodes: z.array(nodeSchemaNoNumericBounds).min(1),
174
+ edges: z.array(edgeSchema),
175
+ })
176
+ .superRefine(refineNodesAndEdges);
177
+ // --- #272 fix: REQUIRE sourceRef on code-intake failure nodes ---
178
+ //
179
+ // The code-v3 emit prompt forcefully demands a `file:line` citation on every
180
+ // failure node, yet three prompt variants produced ZERO citations — because
181
+ // `sourceRef` is `.optional()` on the base node and structured-output models
182
+ // drop optional fields regardless of the prose (root cause (a) in
183
+ // codemap-sourceref-grounding). The lever the prompt can't pull is the SCHEMA:
184
+ // making the field required compels the model to emit it.
185
+ //
186
+ // Required ONLY for code intakes (`module`/`capability`) and ONLY on the FAILURE
187
+ // arm — spec/doc maps have no source line, and flow (step/decision/…) nodes keep
188
+ // `sourceRef` optional. The hard `file:line`/in-scope validity check stays SOFT
189
+ // (lint.ts `uncited-code-failure`): a non-empty string is all the schema demands,
190
+ // so a slightly-off citation can't fail the whole map (the brittle gate that broke
191
+ // generation). The soft lint then flags hallucinated/out-of-scope refs. These
192
+ // variants mirror the two discriminated-union surfaces above; the flat/strict
193
+ // surface is deliberately left unchanged (it flattens every node to one shape, so
194
+ // requiring sourceRef there would force flow nodes to cite too — revisit when an
195
+ // OpenAI code-intake eval needs it).
196
+ export const REQUIRED_SOURCEREF_MESSAGE = 'every code-map failure node must cite the file:line it lives on — set sourceRef (e.g. "worktree.go:110")';
197
+ /** The failure-node fields (sans `scores`) with `sourceRef` made required. */
198
+ const failureNodeCodeShapeSansScores = {
199
+ ...failureNodeShapeSansScores,
200
+ sourceRef: z.string().min(1, REQUIRED_SOURCEREF_MESSAGE),
201
+ };
202
+ function codeGenerationSchema(bounded) {
203
+ const failureNode = z.object({
204
+ ...failureNodeCodeShapeSansScores,
205
+ scores: buildScores(bounded),
206
+ });
207
+ const node = z.discriminatedUnion("type", [failureNode, flowNodeSchema]);
208
+ return z
209
+ .object({ nodes: z.array(node).min(1), edges: z.array(edgeSchema) })
210
+ .superRefine(refineNodesAndEdges);
211
+ }
212
+ /** The bounded code-intake generation surface: {@link generationSchema} with a
213
+ * required `sourceRef` on failure nodes (#272). Sent to providers that accept
214
+ * integer numeric bounds (anthropic). */
215
+ export const generationSchemaCode = codeGenerationSchema(true);
216
+ /** The bounds-free code-intake generation surface: {@link
217
+ * generationSchemaNoNumericBounds} with a required `sourceRef` on failure nodes
218
+ * (#272). Sent to OpenAI-compatible gateways that reject integer bounds. */
219
+ export const generationSchemaCodeNoNumericBounds = codeGenerationSchema(false);
220
+ /**
221
+ * The STRICT-compatible generation surface (#247). OpenAI strict structured
222
+ * output (`response_format: json_schema`, `strict: true`) is the token-level
223
+ * enforcement that stops the model re-emitting nodes — which non-strict gateways
224
+ * (AssemblyAI) otherwise turn into a duplicate-id hard failure. But strict mode
225
+ * forbids `oneOf`, and our node {@link nodeSchema} is a discriminated union
226
+ * (`failure | flow`) which Zod emits as `oneOf` — so strict rejects it outright
227
+ * (`'oneOf' is not permitted`).
228
+ *
229
+ * This surface sidesteps that by FLATTENING the union into a single node object:
230
+ * one `type` enum (incl. `"failure"`) and the failure-only fields made nullable
231
+ * (populated for `type: "failure"`, `null` otherwise). No union → no `oneOf`.
232
+ * It is also bounds-free (strict also rejects integer `minimum`/`maximum`). The
233
+ * flat output is re-projected onto the canonical discriminated union by
234
+ * {@link normalizeFlatGeneration} immediately after generation, so the rest of
235
+ * the engine (envelope, assembly, the full document schema) is unchanged — this
236
+ * is purely the shape we hand the model, not a new internal node type.
237
+ */
238
+ // Strict mode forbids integer `minimum`/`maximum`, so the flat factor carries no
239
+ // in-schema bound — that leaves the JSON-Schema `description` as the ONLY in-schema
240
+ // 1-5 signal the model gets (the canonical `riskFactor` describe is otherwise lost
241
+ // on this surface). `normalizeFlatGeneration` re-imposes the hard 1-5 bound after
242
+ // generation, where a violation is a no-retry terminal error — so guiding the model
243
+ // up front matters. The text mirrors `riskFactor` for parity (#247).
244
+ const flatBareFactor = z
245
+ .number()
246
+ .int()
247
+ .describe("FMEA risk factor: integer 1-5 (inclusive)");
248
+ const flatNodeSchema = z.object({
249
+ id: nodeId,
250
+ type: z.enum(["step", "decision", "terminal", "recovery", "failure"]),
251
+ label: z.string(),
252
+ // Optional-on-the-union fields, made nullable so strict keeps them `required`.
253
+ group: z.string().nullable(),
254
+ sourceRef: z.string().nullable(),
255
+ // Failure-only fields: present for `type: "failure"`, `null` for flow nodes.
256
+ class: z.string().nullable(),
257
+ silent: z.boolean().nullable(),
258
+ handled: z.boolean().nullable(),
259
+ reviewWouldMiss: z.boolean().nullable(),
260
+ trigger: z.string().nullable(),
261
+ manifestation: z.string().nullable(),
262
+ suggestedFix: z.string().nullable(),
263
+ fixEffort: z.enum(["trivial", "small", "medium", "large"]).nullable(),
264
+ scores: z
265
+ .object({
266
+ severity: flatBareFactor,
267
+ frequency: flatBareFactor,
268
+ detection: flatBareFactor,
269
+ })
270
+ .nullable(),
271
+ });
272
+ const flatEdgeSchema = z.object({
273
+ from: z.string(),
274
+ to: z.string(),
275
+ type: z.enum(["happy", "branch", "recovery", "failure"]),
276
+ condition: z.string().nullable(),
277
+ });
278
+ export const flatGenerationSchema = z.object({
279
+ nodes: z.array(flatNodeSchema).min(1),
280
+ edges: z.array(flatEdgeSchema),
281
+ });
282
+ /**
283
+ * Project a flat strict-mode generation ({@link flatGenerationSchema}) back onto
284
+ * the canonical discriminated-union {@link Generation}: drop the `null`
285
+ * failure-only fields from flow nodes, keep them on failure nodes, strip `null`
286
+ * optionals, then re-validate through {@link generationSchema}. That final parse
287
+ * re-imposes everything the flat surface relaxed for strict's sake — the 1-5
288
+ * `scores` bounds, the per-`type` required fields (a `failure` node missing its
289
+ * `scores`/`class` throws here), unique ids and edge referential integrity — so a
290
+ * malformed flat map fails exactly as a malformed union map would. `input` is
291
+ * `unknown` (defence-in-depth for an injected seam): it is parsed through the flat
292
+ * schema first.
293
+ */
294
+ export function normalizeFlatGeneration(input) {
295
+ const flat = flatGenerationSchema.parse(input);
296
+ const nodes = flat.nodes.map((n) => {
297
+ const base = {
298
+ id: n.id,
299
+ label: n.label,
300
+ ...(n.group != null ? { group: n.group } : {}),
301
+ ...(n.sourceRef != null ? { sourceRef: n.sourceRef } : {}),
302
+ };
303
+ if (n.type === "failure") {
304
+ return {
305
+ ...base,
306
+ type: "failure",
307
+ class: n.class,
308
+ silent: n.silent,
309
+ handled: n.handled,
310
+ reviewWouldMiss: n.reviewWouldMiss,
311
+ trigger: n.trigger,
312
+ manifestation: n.manifestation,
313
+ suggestedFix: n.suggestedFix,
314
+ ...(n.fixEffort != null ? { fixEffort: n.fixEffort } : {}),
315
+ scores: n.scores,
316
+ };
317
+ }
318
+ return { ...base, type: n.type };
319
+ });
320
+ const edges = flat.edges.map((e) => ({
321
+ from: e.from,
322
+ to: e.to,
323
+ type: e.type,
324
+ ...(e.condition != null ? { condition: e.condition } : {}),
325
+ }));
326
+ return generationSchema.parse({ nodes, edges });
327
+ }
328
+ /** Lowercase + collapse every run of non-`[a-z0-9_]` to a single `_`, then trim
329
+ * leading/trailing `_`. `worktree.go:18` → `worktree_go_18`, `Fail-GitHang` →
330
+ * `fail_githang`. An id that is all punctuation collapses to `""` and the caller
331
+ * substitutes a placeholder. */
332
+ function slugifyNodeId(raw) {
333
+ return raw
334
+ .toLowerCase()
335
+ .replace(/[^a-z0-9_]+/g, "_")
336
+ .replace(/^_+|_+$/g, "");
337
+ }
338
+ /**
339
+ * Repair LLM-emitted node ids to the canonical {@link NODE_ID_PATTERN} (#280)
340
+ * and rewrite every edge endpoint with the SAME mapping so references stay
341
+ * intact (an un-rewritten endpoint would become a dangling edge). Ids are
342
+ * mechanical identifiers — a bad character should be slugified, never fatal.
343
+ *
344
+ * Identity-preserving by construction:
345
+ * - Distinct raw ids that slugify to the same value are kept distinct (a `_2`,
346
+ * `_3`, … suffix on collision), so two nodes never silently merge.
347
+ * - Equal raw ids map to the same slug, so a genuine duplicate-id still trips
348
+ * {@link refineNodesAndEdges} downstream (this does not paper over duplicates).
349
+ * - An empty slug (all-punctuation id) falls back to `node`.
350
+ * - An edge endpoint with no matching node id is left as-is, so a dangling edge
351
+ * still surfaces as a referential-integrity error rather than being hidden.
352
+ *
353
+ * Does NOT touch the semantic `fingerprint` (over class/trigger/manifestation),
354
+ * so stable identity / reconciliation is unaffected.
355
+ */
356
+ export function coerceNodeIds(generation) {
357
+ const mapping = new Map();
358
+ const used = new Set();
359
+ // Pass 1: reserve ids ALREADY in canonical form so a valid id is never
360
+ // displaced by an earlier node whose slug happens to collide with it. This
361
+ // keeps the assignment independent of node order and makes #280 a pure repair
362
+ // of the *malformed* ids — the stable ones pass through untouched.
363
+ for (const node of generation.nodes) {
364
+ if (NODE_ID_PATTERN.test(node.id) && !mapping.has(node.id)) {
365
+ used.add(node.id);
366
+ mapping.set(node.id, node.id);
367
+ }
368
+ }
369
+ // Pass 2: slugify the rest, collision-guarding against the reserved set.
370
+ for (const node of generation.nodes) {
371
+ if (mapping.has(node.id))
372
+ continue; // reserved canonical, or an equal raw id (dup stays a dup)
373
+ const base = slugifyNodeId(node.id) || "node";
374
+ let candidate = base;
375
+ let n = 2;
376
+ while (used.has(candidate))
377
+ candidate = `${base}_${n++}`;
378
+ used.add(candidate);
379
+ mapping.set(node.id, candidate);
380
+ }
381
+ const nodes = generation.nodes.map((node) => ({
382
+ ...node,
383
+ id: mapping.get(node.id) ?? node.id,
384
+ }));
385
+ const edges = generation.edges.map((edge) => ({
386
+ ...edge,
387
+ from: mapping.get(edge.from) ?? edge.from,
388
+ to: mapping.get(edge.to) ?? edge.to,
389
+ }));
390
+ return { nodes, edges };
391
+ }
392
+ // --- #272: a grounding `sourceRef` on failure nodes for CODE intakes ---
393
+ //
394
+ // `sourceRef` is `optional()` on the base node because spec/PRD maps
395
+ // (`subject.kind: document | process | pull_request`) have no source line to
396
+ // cite. For the code intakes (`module`/`capability`) every FAILURE node SHOULD
397
+ // cite the `file:line` that produces it — the lever against the speculative
398
+ // "false-unaddressed" garbage surfaced by the treetop dogfood (#131): a citation
399
+ // collapses the gap between an imagined guard and the code as built. The code-v2
400
+ // prompt requests it; a missing/invalid citation is surfaced as a SOFT lint
401
+ // warning (see lint.ts `uncited-code-failure`), never a hard rejection — hard
402
+ // gating the whole map on every node proved too brittle to generate (one of ~30
403
+ // failure nodes missing a ref fails the whole map; whole-map retry can't repair a
404
+ // single node). The helpers below are the shared validity check for that lint.
405
+ /** Split a code subject's `ref` ("a.go, dir/b.go") into its discovered path set
406
+ * (#272). Absent/empty ref → empty set (scope unknown: format is still checked,
407
+ * path membership is not). */
408
+ export function scopePathsFromRef(ref) {
409
+ if (!ref)
410
+ return new Set();
411
+ return new Set(ref
412
+ .split(",")
413
+ .map((p) => p.trim())
414
+ .filter((p) => p.length > 0));
415
+ }
416
+ /**
417
+ * Validate a failure node's `sourceRef` as a concrete, in-scope citation (#272).
418
+ * Returns a human-readable problem (used verbatim as the retry/repair hint the
419
+ * model sees on a re-prompt) or `null` when the ref is acceptable. A valid ref is
420
+ * `file:line` (or `file:line-line`) whose `file` is one of the analyzed-scope
421
+ * paths. When `scopePaths` is empty the path-membership check is skipped (scope
422
+ * unknown) — the `file:line` shape is still enforced.
423
+ */
424
+ export function sourceRefProblem(ref, scopePaths) {
425
+ if (ref == null || ref.trim() === "") {
426
+ return 'every failure node on a code map must cite the file:line that produces it (set sourceRef, e.g. "worktree.go:110")';
427
+ }
428
+ const lastColon = ref.lastIndexOf(":");
429
+ if (lastColon <= 0 || lastColon === ref.length - 1) {
430
+ return `sourceRef "${ref}" must be "file:line" — a file path and the line the failure lives on (e.g. "worktree.go:110")`;
431
+ }
432
+ const path = ref.slice(0, lastColon);
433
+ const lineSpec = ref.slice(lastColon + 1);
434
+ if (!/^\d+(-\d+)?$/.test(lineSpec)) {
435
+ return `sourceRef "${ref}" must end in a line number ("file:line"); "${lineSpec}" is not a line`;
436
+ }
437
+ if (scopePaths.size > 0 && !scopePaths.has(path)) {
438
+ return `sourceRef "${ref}" cites "${path}", which is not in the analyzed scope — cite one of the files you were given`;
439
+ }
440
+ return null;
441
+ }
442
+ /**
443
+ * The artifact a Snag Map is derived from. `kind` is *what is being analyzed in
444
+ * the world* — `document` (a design) or `process` (a real business process; the
445
+ * intake is reserved, not wired yet), or a code grain: `function` ⊂ `module` ⊂
446
+ * `capability` (the cross-cutting grain spanning modules/boundaries, derived
447
+ * agentically by-name, #85), with `pull_request` the orthogonal change-axis. The
448
+ * enum is *additive* — already-committed maps keep validating as kinds are added.
449
+ */
450
+ export const subjectSchema = z.object({
451
+ kind: z.enum([
452
+ "pull_request",
453
+ "document",
454
+ "process",
455
+ "function",
456
+ "module",
457
+ "capability",
458
+ ]),
459
+ ref: z.string().optional(),
460
+ title: z.string(),
461
+ /** Provenance / staleness anchor (#54): the git blob OID of the exact bytes
462
+ * mapped (`git hash-object <file>`). Optional so docs outside a git repo or
463
+ * untracked still validate. The CLI computes it; the engine only stamps it
464
+ * when provided (the engine stays git-free). */
465
+ sourceSha: z.string().optional(),
466
+ });
467
+ /** The code-intake subject kinds — the grains derived from existing source:
468
+ * `module` (review scope, {@link deriveCodeSubject}) and `capability` (by-name
469
+ * discovery, {@link deriveCapabilitySubject}). These are exactly the kinds for
470
+ * which a failure node MUST cite a `sourceRef` (#272); the spec kinds
471
+ * (`document`/`process`/`pull_request`) have no single source line to anchor.
472
+ * (`function` is in the enum but no intake derives it yet — it joins here when
473
+ * wired.) */
474
+ export function isCodeIntakeKind(kind) {
475
+ return kind === "module" || kind === "capability";
476
+ }
477
+ /** The harnesses that can drive the engine (#297) — pure provenance, nothing
478
+ * branches on the value. `snag` is the native CLI on API tokens; `claude-code`
479
+ * and `codex` are skills/wrappers that drive the same engine on a subscription.
480
+ * Knowing which produced a map is what makes the native-API-vs-via-harness
481
+ * quality comparison (eval / model-support work) possible. Additive: a new
482
+ * harness joins the enum without breaking already-committed maps. */
483
+ export const HARNESSES = ["snag", "claude-code", "codex"];
484
+ /** Default harness when none is declared — the native `snag` CLI. */
485
+ export const DEFAULT_HARNESS = "snag";
486
+ /**
487
+ * Engine-stamped provenance (never produced by the LLM) — the poor-man's
488
+ * observability bridge to a future trace server.
489
+ */
490
+ export const metaSchema = z.object({
491
+ engineVersion: z.string(),
492
+ generatedAt: z.string(),
493
+ /** Which harness drove the engine for this map (#297). `snag` (native CLI,
494
+ * default) | `claude-code` | `codex`. Provenance only — nothing branches on
495
+ * it. Optional so already-committed maps without it still validate; always
496
+ * stamped on newly generated maps (defaulting to `snag`). */
497
+ harness: z.enum(HARNESSES).optional(),
498
+ /** API protocol/SDK the map was generated against — Langfuse's "Adapter"
499
+ * (#55). `anthropic` | `openai` today (was `provider`). */
500
+ adapter: z.string(),
501
+ /** User-declared vendor/connection label — Langfuse's "Provider Name" (#55,
502
+ * e.g. "AssemblyAI", "OpenRouter", "Anthropic"). The engine can't infer the
503
+ * vendor (it only knows adapter + base URL), so this is optional and
504
+ * user-supplied. */
505
+ providerName: z.string().optional(),
506
+ model: z.string(),
507
+ /** Resolved provider base URL the map was generated against (provenance).
508
+ * Optional so existing maps/fixtures without it still validate; always
509
+ * stamped on newly generated maps. */
510
+ apiUrl: z.string().url().optional(),
511
+ promptVersion: z.string(),
512
+ /** Liveness/degrade flag (#83): TRUE when the agentic gather loop hit its hard
513
+ * step ceiling without converging, so the map was emitted from possibly
514
+ * incomplete gathered context. Optional + only stamped when true, so doc-path
515
+ * maps and already-committed maps omit it and still validate. Surfacing it in
516
+ * the viewer is a separate slice (#B). */
517
+ gatherStoppedAtCap: z.boolean().optional(),
518
+ /** Sampling temperature actually sent to the model (provenance, #173). Optional
519
+ * + only stamped when a temperature was applied: OpenAI reasoning models
520
+ * (gpt-5.x, o-series) reject an explicit `temperature`, so the engine omits it
521
+ * for them and leaves this absent. Already-committed maps without it still
522
+ * validate. */
523
+ temperature: z.number().optional(),
524
+ /** Output-token budget actually sent to the model (provenance, #234). Optional
525
+ * so already-committed maps without it still validate; always stamped on newly
526
+ * generated maps (the engine always sends an explicit budget — the AI SDK only
527
+ * forwards `max_tokens` when we pass it). */
528
+ maxOutputTokens: z.number().int().positive().optional(),
529
+ tokenUsage: z.object({
530
+ input: z.number().int().nonnegative(),
531
+ output: z.number().int().nonnegative(),
532
+ total: z.number().int().nonnegative(),
533
+ }),
534
+ });
535
+ /**
536
+ * Document-level hard validation: everything {@link refineNodesAndEdges} checks,
537
+ * PLUS the strict `^[a-z0-9_]+$` node-id format. The id format is enforced HERE
538
+ * (the persisted contract) rather than on the generation surface so a malformed
539
+ * model id is repaired by {@link coerceNodeIds} during assembly, not fatal at
540
+ * generation (#280). A hand-edited / already-committed map with a bad id still
541
+ * rejects.
542
+ */
543
+ function refineDocument(value, ctx) {
544
+ value.nodes.forEach((node, i) => {
545
+ if (!NODE_ID_PATTERN.test(node.id)) {
546
+ ctx.addIssue({
547
+ code: z.ZodIssueCode.custom,
548
+ message: NODE_ID_MESSAGE,
549
+ path: ["nodes", i, "id"],
550
+ });
551
+ }
552
+ });
553
+ refineNodesAndEdges(value, ctx);
554
+ }
555
+ /** The full persisted failure-map/v1 document: envelope + analysis. */
556
+ export const failureMapSchema = z
557
+ .object({
558
+ schema: z.literal(SCHEMA_ID),
559
+ subject: subjectSchema,
560
+ meta: metaSchema,
561
+ ...nodesAndEdges,
562
+ })
563
+ .superRefine(refineDocument);
564
+ export { SCHEMA_ID };
565
+ /** Parse + validate the LLM generation surface; throws on invalid input. */
566
+ export function parseGeneration(input) {
567
+ return generationSchema.parse(input);
568
+ }
569
+ /** Parse + validate a full failure-map/v1 document; throws on invalid input. */
570
+ export function parseFailureMap(input) {
571
+ return failureMapSchema.parse(input);
572
+ }
573
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare const SEVERITY_BANDS: readonly ["critical", "high", "medium", "low"];
2
+ export type SeverityBand = (typeof SEVERITY_BANDS)[number];
3
+ /** Derive the categorical band from the 1-5 FMEA severity score. */
4
+ export declare function severityBand(severity: number): SeverityBand;
5
+ //# sourceMappingURL=severity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"severity.d.ts","sourceRoot":"","sources":["../src/severity.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,cAAc,gDAAiD,CAAC;AAC7E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAK3D"}
@@ -0,0 +1,16 @@
1
+ // Severity band derivation. `scores.severity` (integer 1-5) is the single
2
+ // source of truth for impact (cli-v1.md "Severity — single source of truth").
3
+ // The categorical band is *dropped from the schema* and derived wherever a
4
+ // label is needed: 5 -> critical, 4 -> high, 3 -> medium, 1-2 -> low.
5
+ export const SEVERITY_BANDS = ["critical", "high", "medium", "low"];
6
+ /** Derive the categorical band from the 1-5 FMEA severity score. */
7
+ export function severityBand(severity) {
8
+ if (severity >= 5)
9
+ return "critical";
10
+ if (severity === 4)
11
+ return "high";
12
+ if (severity === 3)
13
+ return "medium";
14
+ return "low";
15
+ }
16
+ //# sourceMappingURL=severity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"severity.js","sourceRoot":"","sources":["../src/severity.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,2EAA2E;AAC3E,sEAAsE;AAEtE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAU,CAAC;AAG7E,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IACrC,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACpC,OAAO,KAAK,CAAC;AACf,CAAC"}