clud-bug 0.6.34 → 0.7.0-rc.11

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 (153) hide show
  1. package/README.md +20 -0
  2. package/bin/clud-bug.js +10 -1353
  3. package/data/canonical-v1.json +37 -0
  4. package/dist/cli/agents-md.d.ts +16 -0
  5. package/dist/cli/agents-md.d.ts.map +1 -0
  6. package/dist/cli/agents-md.js +226 -0
  7. package/dist/cli/agents-md.js.map +1 -0
  8. package/dist/cli/audit.d.ts +13 -0
  9. package/dist/cli/audit.d.ts.map +1 -0
  10. package/dist/cli/audit.js +90 -0
  11. package/dist/cli/audit.js.map +1 -0
  12. package/dist/cli/branch-protection.d.ts +57 -0
  13. package/dist/cli/branch-protection.d.ts.map +1 -0
  14. package/dist/cli/branch-protection.js +118 -0
  15. package/dist/cli/branch-protection.js.map +1 -0
  16. package/dist/cli/configure-github.d.ts +58 -0
  17. package/dist/cli/configure-github.d.ts.map +1 -0
  18. package/dist/cli/configure-github.js +242 -0
  19. package/dist/cli/configure-github.js.map +1 -0
  20. package/dist/cli/edit-workflow.d.ts +18 -0
  21. package/dist/cli/edit-workflow.d.ts.map +1 -0
  22. package/dist/cli/edit-workflow.js +43 -0
  23. package/dist/cli/edit-workflow.js.map +1 -0
  24. package/dist/cli/index.d.ts +9 -0
  25. package/dist/cli/index.d.ts.map +1 -0
  26. package/dist/cli/index.js +19 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/cli/main.d.ts +3 -0
  29. package/dist/cli/main.d.ts.map +1 -0
  30. package/dist/cli/main.js +2067 -0
  31. package/dist/cli/main.js.map +1 -0
  32. package/dist/cli/skill-usage.d.ts +109 -0
  33. package/dist/cli/skill-usage.d.ts.map +1 -0
  34. package/dist/cli/skill-usage.js +380 -0
  35. package/dist/cli/skill-usage.js.map +1 -0
  36. package/dist/cli/skills.d.ts +56 -0
  37. package/dist/cli/skills.d.ts.map +1 -0
  38. package/dist/cli/skills.js +292 -0
  39. package/dist/cli/skills.js.map +1 -0
  40. package/dist/cli/update.d.ts +29 -0
  41. package/dist/cli/update.d.ts.map +1 -0
  42. package/dist/cli/update.js +205 -0
  43. package/dist/cli/update.js.map +1 -0
  44. package/dist/cli/usage.d.ts +142 -0
  45. package/dist/cli/usage.d.ts.map +1 -0
  46. package/dist/cli/usage.js +348 -0
  47. package/dist/cli/usage.js.map +1 -0
  48. package/dist/core/audit.d.ts +8 -0
  49. package/dist/core/audit.d.ts.map +1 -0
  50. package/dist/core/audit.js +47 -0
  51. package/dist/core/audit.js.map +1 -0
  52. package/dist/core/auto-resolve.d.ts +235 -0
  53. package/dist/core/auto-resolve.d.ts.map +1 -0
  54. package/dist/core/auto-resolve.js +293 -0
  55. package/dist/core/auto-resolve.js.map +1 -0
  56. package/dist/core/configure-github.d.ts +179 -0
  57. package/dist/core/configure-github.d.ts.map +1 -0
  58. package/dist/core/configure-github.js +289 -0
  59. package/dist/core/configure-github.js.map +1 -0
  60. package/dist/core/detect.d.ts +77 -0
  61. package/dist/core/detect.d.ts.map +1 -0
  62. package/dist/core/detect.js +262 -0
  63. package/dist/core/detect.js.map +1 -0
  64. package/dist/core/diff-findings.d.ts +96 -0
  65. package/dist/core/diff-findings.d.ts.map +1 -0
  66. package/dist/core/diff-findings.js +261 -0
  67. package/dist/core/diff-findings.js.map +1 -0
  68. package/dist/core/formal-review.d.ts +79 -0
  69. package/dist/core/formal-review.d.ts.map +1 -0
  70. package/dist/core/formal-review.js +71 -0
  71. package/dist/core/formal-review.js.map +1 -0
  72. package/dist/core/index.d.ts +17 -0
  73. package/dist/core/index.d.ts.map +1 -0
  74. package/dist/core/index.js +66 -0
  75. package/dist/core/index.js.map +1 -0
  76. package/dist/core/inline-threads.d.ts +160 -0
  77. package/dist/core/inline-threads.d.ts.map +1 -0
  78. package/dist/core/inline-threads.js +369 -0
  79. package/dist/core/inline-threads.js.map +1 -0
  80. package/dist/core/prompt-builder.d.ts +164 -0
  81. package/dist/core/prompt-builder.d.ts.map +1 -0
  82. package/dist/core/prompt-builder.js +419 -0
  83. package/dist/core/prompt-builder.js.map +1 -0
  84. package/dist/core/prompts.d.ts +9 -0
  85. package/dist/core/prompts.d.ts.map +1 -0
  86. package/dist/core/prompts.js +401 -0
  87. package/dist/core/prompts.js.map +1 -0
  88. package/dist/core/render-review.d.ts +6 -0
  89. package/dist/core/render-review.d.ts.map +1 -0
  90. package/dist/core/render-review.js +219 -0
  91. package/dist/core/render-review.js.map +1 -0
  92. package/dist/core/render.d.ts +13 -0
  93. package/dist/core/render.d.ts.map +1 -0
  94. package/dist/core/render.js +62 -0
  95. package/dist/core/render.js.map +1 -0
  96. package/dist/core/resolve-verifier.d.ts +46 -0
  97. package/dist/core/resolve-verifier.d.ts.map +1 -0
  98. package/dist/core/resolve-verifier.js +187 -0
  99. package/dist/core/resolve-verifier.js.map +1 -0
  100. package/dist/core/review-schema-zod.d.ts +240 -0
  101. package/dist/core/review-schema-zod.d.ts.map +1 -0
  102. package/dist/core/review-schema-zod.js +218 -0
  103. package/dist/core/review-schema-zod.js.map +1 -0
  104. package/dist/core/review-schema.d.ts +42 -0
  105. package/dist/core/review-schema.d.ts.map +1 -0
  106. package/dist/core/review-schema.js +156 -0
  107. package/dist/core/review-schema.js.map +1 -0
  108. package/dist/core/review-writeback.d.ts +193 -0
  109. package/dist/core/review-writeback.d.ts.map +1 -0
  110. package/dist/core/review-writeback.js +364 -0
  111. package/dist/core/review-writeback.js.map +1 -0
  112. package/dist/core/skills.d.ts +147 -0
  113. package/dist/core/skills.d.ts.map +1 -0
  114. package/dist/core/skills.js +726 -0
  115. package/dist/core/skills.js.map +1 -0
  116. package/dist/core/version.d.ts +2 -0
  117. package/dist/core/version.d.ts.map +1 -0
  118. package/dist/core/version.js +4 -0
  119. package/dist/core/version.js.map +1 -0
  120. package/package.json +37 -5
  121. package/{lib/agents-md.js → src/cli/agents-md.ts} +25 -14
  122. package/{lib/audit.js → src/cli/audit.ts} +37 -44
  123. package/{lib/branch-protection.js → src/cli/branch-protection.ts} +75 -11
  124. package/src/cli/configure-github.ts +329 -0
  125. package/{lib/edit-workflow.js → src/cli/edit-workflow.ts} +32 -11
  126. package/src/cli/index.ts +107 -0
  127. package/src/cli/main.ts +2204 -0
  128. package/{lib/skill-usage.js → src/cli/skill-usage.ts} +168 -94
  129. package/src/cli/skills.ts +386 -0
  130. package/{lib/update.js → src/cli/update.ts} +87 -27
  131. package/{lib/usage.js → src/cli/usage.ts} +167 -76
  132. package/src/core/audit.ts +53 -0
  133. package/src/core/auto-resolve.ts +488 -0
  134. package/src/core/configure-github.ts +497 -0
  135. package/{lib/detect.js → src/core/detect.ts} +100 -47
  136. package/src/core/diff-findings.ts +323 -0
  137. package/src/core/formal-review.ts +168 -0
  138. package/src/core/index.ts +250 -0
  139. package/src/core/inline-threads.ts +471 -0
  140. package/src/core/prompt-builder.ts +561 -0
  141. package/{lib/prompts.js → src/core/prompts.ts} +16 -2
  142. package/{lib/render-review.js → src/core/render-review.ts} +57 -25
  143. package/{lib/render.js → src/core/render.ts} +37 -17
  144. package/src/core/resolve-verifier.ts +228 -0
  145. package/src/core/review-schema-zod.ts +262 -0
  146. package/{lib/review-schema.js → src/core/review-schema.ts} +68 -5
  147. package/src/core/review-writeback.ts +565 -0
  148. package/{lib/skills.js → src/core/skills.ts} +456 -342
  149. package/src/core/version.ts +3 -0
  150. package/templates/clud-bug-review.md.tmpl +115 -0
  151. package/templates/workflow-py.yml.tmpl +80 -3
  152. package/templates/workflow-ts.yml.tmpl +80 -3
  153. package/templates/workflow.yml.tmpl +175 -8
@@ -0,0 +1,262 @@
1
+ // Zod-typed review schema for the AI-Gateway-shape consumer (clud-bug-app).
2
+ //
3
+ // The CLI runtime in `./review-schema.ts` ships a plain JSON-Schema object
4
+ // because that's what the Agent SDK validator expects on the
5
+ // `--json-schema '<JSON>'` argument. The App's runtime instead funnels the
6
+ // model output through the Vercel AI SDK, which derives a JSON Schema from
7
+ // a Zod schema. Both consumers need to agree on the WIRE shape — separate
8
+ // `critical_findings[] / minor_findings[] / preexisting_findings[]` arrays
9
+ // per SPEC §1.8.1 — but each builds its validator from a different source.
10
+ //
11
+ // This module ports the App's Zod schemas + flat-shape helpers into core
12
+ // so a future drift between the App's Zod and the CLI's JSON-Schema lives
13
+ // in one repo. The equivalence test (test/review-schema-zod.test.js)
14
+ // asserts the two schemas describe the same wire shape (required fields,
15
+ // finding-item shape) for every release.
16
+ //
17
+ // Ported from clud-bug-app/lib/review-schema.ts (commit shipped 2026-06-08).
18
+ // The pure helpers (`flattenFindings`, `unflattenFindings`,
19
+ // `deriveSummaryCounts`, `deriveSkillsReferenced`, `buildReviewFromFindings`)
20
+ // are byte-equivalent to the App's helpers — see test for the equivalence
21
+ // fixtures.
22
+
23
+ import { z } from 'zod';
24
+
25
+ // Severity buckets per SPEC §1.8.1. Only used by the internal `Finding`
26
+ // type — the wire `findingItemSchema` does NOT carry severity.
27
+ export const severityValues = ['critical', 'minor', 'preexisting'] as const;
28
+ export const severitySchema = z.enum(severityValues);
29
+ export type Severity = z.infer<typeof severitySchema>;
30
+
31
+ // Status header at the top of the review file.
32
+ export const statusHeaderValues = [
33
+ 'critical findings',
34
+ 'clean',
35
+ 'bare',
36
+ ] as const;
37
+ export const statusHeaderSchema = z.enum(statusHeaderValues);
38
+ export type StatusHeader = z.infer<typeof statusHeaderSchema>;
39
+
40
+ export const summaryCountsSchema = z.object({
41
+ critical: z.number().int().min(0),
42
+ minor: z.number().int().min(0),
43
+ preexisting: z.number().int().min(0),
44
+ resolved_from_prior: z.number().int().min(0),
45
+ still_open: z.number().int().min(0),
46
+ });
47
+ export type SummaryCounts = z.infer<typeof summaryCountsSchema>;
48
+
49
+ /**
50
+ * Wire-shape finding item — NO severity field (mirrors the CLI's
51
+ * `FINDING_ITEM` JSON Schema in `./review-schema.ts`). Severity is implicit
52
+ * in which array the item lives in (`critical_findings`/`minor_findings`/
53
+ * `preexisting_findings`).
54
+ */
55
+ export const findingItemSchema = z.object({
56
+ skill: z.string().min(1),
57
+ file: z.string().optional(),
58
+ line: z.number().int().min(1).optional(),
59
+ summary: z.string().min(1),
60
+ reasoning: z.string().optional(),
61
+ });
62
+ export type FindingItem = z.infer<typeof findingItemSchema>;
63
+
64
+ /** Per-skill scan report — one entry per loaded skill (even silent ones). */
65
+ export const perSkillScanItemSchema = z.object({
66
+ skill: z.string(),
67
+ outcome: z.string(),
68
+ });
69
+ export type PerSkillScanItem = z.infer<typeof perSkillScanItemSchema>;
70
+
71
+ /** Dedicated-section block for `review_mode: dedicated` skills. */
72
+ export const dedicatedSectionSchema = z.object({
73
+ section_name: z.string(),
74
+ skill: z.string(),
75
+ findings: z.array(findingItemSchema),
76
+ });
77
+ export type DedicatedSection = z.infer<typeof dedicatedSectionSchema>;
78
+
79
+ /**
80
+ * Full review payload — wire shape. The model produces this; the App
81
+ * orchestrator immediately flattens to the internal `Finding[]` shape via
82
+ * `flattenFindings()` for multi-pass + aggregator work, then unflattens
83
+ * back via `unflattenFindings()` before writeback.
84
+ */
85
+ export const reviewSchema = z.object({
86
+ status_header: statusHeaderSchema,
87
+ summary_counts: summaryCountsSchema,
88
+ per_skill_scan: z.array(perSkillScanItemSchema),
89
+ critical_findings: z.array(findingItemSchema),
90
+ minor_findings: z.array(findingItemSchema),
91
+ preexisting_findings: z.array(findingItemSchema),
92
+ dedicated_sections: z.array(dedicatedSectionSchema).optional(),
93
+ diagnostics: z.array(z.string()).optional(),
94
+ skills_referenced: z.array(z.string()),
95
+ last_reviewed_sha: z.string(),
96
+ });
97
+ export type Review = z.infer<typeof reviewSchema>;
98
+
99
+ /**
100
+ * Internal flat-finding type used by App orchestrator, multi-pass
101
+ * aggregator, skill-usage telemetry, etc. Created from a wire-shape
102
+ * `Review` via `flattenFindings()`. Has explicit `severity` field so
103
+ * internal code doesn't need to track which array a finding came from.
104
+ *
105
+ * Exported from the core barrel as `ZodFinding` to disambiguate from
106
+ * the CLI-shape `ReviewFinding` (which never carries severity — its
107
+ * severity comes from the array it lives in).
108
+ */
109
+ export type Finding = FindingItem & { severity: Severity };
110
+
111
+ /**
112
+ * Zod schema describing the internal Finding shape (for tests + cross-check
113
+ * Pass 2 independentFindings). The wire equivalent is `findingItemSchema`
114
+ * which does NOT have severity.
115
+ */
116
+ export const findingSchema = findingItemSchema.extend({
117
+ severity: severitySchema,
118
+ });
119
+
120
+ /**
121
+ * Flatten wire-shape `Review.critical_findings / minor_findings /
122
+ * preexisting_findings` into a single `Finding[]` with severity tagged.
123
+ * Preserves ordering: criticals first, then minors, then preexistings.
124
+ */
125
+ export function flattenFindings(review: Review): Finding[] {
126
+ const out: Finding[] = [];
127
+ for (const f of review.critical_findings) out.push({ ...f, severity: 'critical' });
128
+ for (const f of review.minor_findings) out.push({ ...f, severity: 'minor' });
129
+ for (const f of review.preexisting_findings) out.push({ ...f, severity: 'preexisting' });
130
+ return out;
131
+ }
132
+
133
+ /**
134
+ * Inverse of `flattenFindings`: split a flat `Finding[]` back into the
135
+ * three wire-shape arrays. Used at writeback time after multi-pass
136
+ * aggregation has produced the final flat list.
137
+ */
138
+ export function unflattenFindings(findings: Finding[]): {
139
+ critical_findings: FindingItem[];
140
+ minor_findings: FindingItem[];
141
+ preexisting_findings: FindingItem[];
142
+ } {
143
+ const stripSeverity = (f: Finding): FindingItem => {
144
+ const { severity: _s, ...rest } = f;
145
+ return rest;
146
+ };
147
+ return {
148
+ critical_findings: findings.filter((f) => f.severity === 'critical').map(stripSeverity),
149
+ minor_findings: findings.filter((f) => f.severity === 'minor').map(stripSeverity),
150
+ preexisting_findings: findings.filter((f) => f.severity === 'preexisting').map(stripSeverity),
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Derive `summary_counts` from a flat `Finding[]` list. Canonical
156
+ * source-of-truth used by the orchestrator after flattening, to
157
+ * overwrite the model's potentially-drifted counts.
158
+ */
159
+ export function deriveSummaryCounts(findings: Finding[]): SummaryCounts {
160
+ return {
161
+ critical: findings.filter((f) => f.severity === 'critical').length,
162
+ minor: findings.filter((f) => f.severity === 'minor').length,
163
+ preexisting: findings.filter((f) => f.severity === 'preexisting').length,
164
+ resolved_from_prior: 0,
165
+ still_open: 0,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Derive `skills_referenced` from a flat `Finding[]` list, preserving
171
+ * citation order (first appearance wins) and deduplicating.
172
+ */
173
+ export function deriveSkillsReferenced(findings: Finding[]): string[] {
174
+ const seen = new Set<string>();
175
+ const out: string[] = [];
176
+ for (const f of findings) {
177
+ if (seen.has(f.skill)) continue;
178
+ seen.add(f.skill);
179
+ out.push(f.skill);
180
+ }
181
+ return out;
182
+ }
183
+
184
+ /**
185
+ * Test helper: build a wire-shape `Review` from a flat `Finding[]` list.
186
+ * Tests historically built reviews with a flat `findings: [...]` field; the
187
+ * wire shape (separate severity arrays + per_skill_scan + last_reviewed_sha
188
+ * required) is more verbose. This helper keeps fixtures short — pass a
189
+ * flat list, get back a valid wire-shape Review with derived counts and
190
+ * skills.
191
+ *
192
+ * Production code should NOT use this; it constructs reviews from AI output
193
+ * directly. This is purely for test ergonomics.
194
+ */
195
+ export function buildReviewFromFindings(opts: {
196
+ findings: Finding[];
197
+ status_header?: StatusHeader;
198
+ last_reviewed_sha?: string;
199
+ per_skill_scan?: PerSkillScanItem[];
200
+ dedicated_sections?: DedicatedSection[];
201
+ diagnostics?: string[];
202
+ }): Review {
203
+ const split = unflattenFindings(opts.findings);
204
+ // Default status_header is derived from severity, NOT just emptiness.
205
+ // The App's original buildReviewFromFindings defaulted to
206
+ // 'critical findings' for ANY non-empty list, including minor-only and
207
+ // preexisting-only inputs. That was a bug (caught by clud-bug-review
208
+ // on PR #158): a review with only minor findings should be 'clean', not
209
+ // 'critical findings'. Fixed here on port to core.
210
+ //
211
+ // Callers that need the old behavior can pass `status_header` explicitly.
212
+ // Callers that want SPEC §1.8.1 semantics (the default) get the correct
213
+ // bucket: criticals present → 'critical findings'; else → 'clean'.
214
+ const hasCritical = opts.findings.some((f) => f.severity === 'critical');
215
+ const defaultStatus = hasCritical ? 'critical findings' : 'clean';
216
+ return {
217
+ status_header: opts.status_header ?? defaultStatus,
218
+ summary_counts: deriveSummaryCounts(opts.findings),
219
+ skills_referenced: deriveSkillsReferenced(opts.findings),
220
+ per_skill_scan: opts.per_skill_scan ?? [],
221
+ critical_findings: split.critical_findings,
222
+ minor_findings: split.minor_findings,
223
+ preexisting_findings: split.preexisting_findings,
224
+ ...(opts.dedicated_sections !== undefined
225
+ ? { dedicated_sections: opts.dedicated_sections }
226
+ : {}),
227
+ ...(opts.diagnostics !== undefined ? { diagnostics: opts.diagnostics } : {}),
228
+ last_reviewed_sha: opts.last_reviewed_sha ?? '',
229
+ };
230
+ }
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // D.2.5 — cross-check pass schema
234
+ // ---------------------------------------------------------------------------
235
+
236
+ /**
237
+ * Per-finding verdict from a cross-check pass. The pass2 model echoes
238
+ * back Pass 1's findings by 0-indexed `pass1Index` + `agreed`/`disagreed`
239
+ * + rationale. The aggregator stitches these into
240
+ * `MultiPassReview.findings[].attributions`.
241
+ *
242
+ * Cross-check Pass 2 operates on a flat finding list (its own
243
+ * representation), so its independentFindings carry severity — uses the
244
+ * legacy `findingSchema` shape.
245
+ */
246
+ export const crossCheckVerdictSchema = z.object({
247
+ pass1Index: z.number().int().min(0),
248
+ verdict: z.enum(['agreed', 'disagreed']),
249
+ rationale: z.string().optional(),
250
+ });
251
+ export type CrossCheckVerdictSchema = z.infer<typeof crossCheckVerdictSchema>;
252
+
253
+ /**
254
+ * Full cross-check response. Pass 2 outputs verdicts on Pass-1 findings
255
+ * plus its own independent finds (in the internal `findingSchema` shape
256
+ * with severity, since cross-check works on already-flattened lists).
257
+ */
258
+ export const crossCheckSchema = z.object({
259
+ verdicts: z.array(crossCheckVerdictSchema),
260
+ independentFindings: z.array(findingSchema),
261
+ });
262
+ export type CrossCheck = z.infer<typeof crossCheckSchema>;
@@ -23,10 +23,21 @@
23
23
  // keep the model from inventing fields.
24
24
  //
25
25
  // Bumped via deliberate edit; not derived from a TypeScript type. The
26
- // rendering side (lib/render-review.js) treats unknown fields permissively
26
+ // rendering side (./render-review.ts) treats unknown fields permissively
27
27
  // — schema and renderer can drift up to one minor version safely.
28
28
 
29
- const FINDING_ITEM = {
29
+ // The JSON Schema shape is structurally rich (oneOf, enum, conditional
30
+ // required fields) and is consumed as a raw JSON object by Agent SDK
31
+ // validators that have their own runtime semantics. Typing it as
32
+ // `Record<string, unknown>` would erase the literal-property structure
33
+ // callers rely on for IDE navigation; typing each sub-object exactly
34
+ // would tightly couple every test that asserts a specific path. We use
35
+ // a structural alias `JSONSchemaObject` here so the export keeps its
36
+ // rich literal type (caller-visible field names) without forcing a
37
+ // schema-spec round-trip.
38
+ type JSONSchemaObject = Record<string, unknown>;
39
+
40
+ const FINDING_ITEM: JSONSchemaObject = {
30
41
  type: 'object',
31
42
  additionalProperties: false,
32
43
  properties: {
@@ -55,7 +66,7 @@ const FINDING_ITEM = {
55
66
  required: ['skill', 'summary'],
56
67
  };
57
68
 
58
- const PER_SKILL_SCAN_ITEM = {
69
+ const PER_SKILL_SCAN_ITEM: JSONSchemaObject = {
59
70
  type: 'object',
60
71
  additionalProperties: false,
61
72
  properties: {
@@ -68,7 +79,7 @@ const PER_SKILL_SCAN_ITEM = {
68
79
  required: ['skill', 'outcome'],
69
80
  };
70
81
 
71
- export const REVIEW_SCHEMA = {
82
+ export const REVIEW_SCHEMA: JSONSchemaObject = {
72
83
  type: 'object',
73
84
  additionalProperties: false,
74
85
  properties: {
@@ -154,6 +165,58 @@ export const REVIEW_SCHEMA = {
154
165
  // `--json-schema '<JSON>'` argument. Single-line (the workflow YAML uses
155
166
  // the pipe block; single-quoted JSON inside that needs to stay flat to
156
167
  // avoid YAML parser surprises with embedded newlines).
157
- export function serializedReviewSchema() {
168
+ export function serializedReviewSchema(): string {
158
169
  return JSON.stringify(REVIEW_SCHEMA);
159
170
  }
171
+
172
+ // --- Schema-derived runtime types for renderReview() input ---
173
+ //
174
+ // The TypeScript types below mirror the JSON Schema shape above so that
175
+ // render-review.ts can consume the parsed JSON with structural typing.
176
+ // They are intentionally permissive (every field optional except where
177
+ // the schema's `required` list forces it) because the renderer is the
178
+ // last line of defense — malformed JSON should degrade rather than throw.
179
+
180
+ export type FindingSeverity = 'critical' | 'minor' | 'preexisting';
181
+
182
+ export interface ReviewFinding {
183
+ skill: string;
184
+ summary: string;
185
+ file?: string;
186
+ line?: number;
187
+ reasoning?: string;
188
+ }
189
+
190
+ export interface PerSkillScanItem {
191
+ skill: string;
192
+ outcome: string;
193
+ }
194
+
195
+ export interface DedicatedSection {
196
+ section_name: string;
197
+ skill: string;
198
+ findings: ReviewFinding[];
199
+ }
200
+
201
+ export interface ReviewSummaryCounts {
202
+ critical: number;
203
+ minor: number;
204
+ preexisting: number;
205
+ resolved_from_prior: number;
206
+ still_open: number;
207
+ }
208
+
209
+ export type ReviewStatusHeader = 'critical findings' | 'clean' | 'bare';
210
+
211
+ export interface ReviewData {
212
+ status_header: ReviewStatusHeader | string;
213
+ summary_counts: ReviewSummaryCounts;
214
+ per_skill_scan: PerSkillScanItem[];
215
+ critical_findings: ReviewFinding[];
216
+ minor_findings: ReviewFinding[];
217
+ preexisting_findings: ReviewFinding[];
218
+ skills_referenced: string[];
219
+ last_reviewed_sha: string;
220
+ dedicated_sections?: DedicatedSection[];
221
+ diagnostics?: string[];
222
+ }