clud-bug 0.6.34 → 0.7.0-rc.2

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 (110) hide show
  1. package/bin/clud-bug.js +10 -1353
  2. package/dist/cli/agents-md.d.ts +16 -0
  3. package/dist/cli/agents-md.d.ts.map +1 -0
  4. package/dist/cli/agents-md.js +226 -0
  5. package/dist/cli/agents-md.js.map +1 -0
  6. package/dist/cli/audit.d.ts +13 -0
  7. package/dist/cli/audit.d.ts.map +1 -0
  8. package/dist/cli/audit.js +90 -0
  9. package/dist/cli/audit.js.map +1 -0
  10. package/dist/cli/branch-protection.d.ts +57 -0
  11. package/dist/cli/branch-protection.d.ts.map +1 -0
  12. package/dist/cli/branch-protection.js +118 -0
  13. package/dist/cli/branch-protection.js.map +1 -0
  14. package/dist/cli/edit-workflow.d.ts +18 -0
  15. package/dist/cli/edit-workflow.d.ts.map +1 -0
  16. package/dist/cli/edit-workflow.js +43 -0
  17. package/dist/cli/edit-workflow.js.map +1 -0
  18. package/dist/cli/index.d.ts +8 -0
  19. package/dist/cli/index.d.ts.map +1 -0
  20. package/dist/cli/index.js +18 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/main.d.ts +3 -0
  23. package/dist/cli/main.d.ts.map +1 -0
  24. package/dist/cli/main.js +1336 -0
  25. package/dist/cli/main.js.map +1 -0
  26. package/dist/cli/skill-usage.d.ts +109 -0
  27. package/dist/cli/skill-usage.d.ts.map +1 -0
  28. package/dist/cli/skill-usage.js +380 -0
  29. package/dist/cli/skill-usage.js.map +1 -0
  30. package/dist/cli/skills.d.ts +56 -0
  31. package/dist/cli/skills.d.ts.map +1 -0
  32. package/dist/cli/skills.js +292 -0
  33. package/dist/cli/skills.js.map +1 -0
  34. package/dist/cli/update.d.ts +29 -0
  35. package/dist/cli/update.d.ts.map +1 -0
  36. package/dist/cli/update.js +186 -0
  37. package/dist/cli/update.js.map +1 -0
  38. package/dist/cli/usage.d.ts +142 -0
  39. package/dist/cli/usage.d.ts.map +1 -0
  40. package/dist/cli/usage.js +348 -0
  41. package/dist/cli/usage.js.map +1 -0
  42. package/dist/core/audit.d.ts +8 -0
  43. package/dist/core/audit.d.ts.map +1 -0
  44. package/dist/core/audit.js +47 -0
  45. package/dist/core/audit.js.map +1 -0
  46. package/dist/core/detect.d.ts +77 -0
  47. package/dist/core/detect.d.ts.map +1 -0
  48. package/dist/core/detect.js +262 -0
  49. package/dist/core/detect.js.map +1 -0
  50. package/dist/core/index.d.ts +11 -0
  51. package/dist/core/index.d.ts.map +1 -0
  52. package/dist/core/index.js +31 -0
  53. package/dist/core/index.js.map +1 -0
  54. package/dist/core/prompt-builder.d.ts +164 -0
  55. package/dist/core/prompt-builder.d.ts.map +1 -0
  56. package/dist/core/prompt-builder.js +419 -0
  57. package/dist/core/prompt-builder.js.map +1 -0
  58. package/dist/core/prompts.d.ts +9 -0
  59. package/dist/core/prompts.d.ts.map +1 -0
  60. package/dist/core/prompts.js +401 -0
  61. package/dist/core/prompts.js.map +1 -0
  62. package/dist/core/render-review.d.ts +6 -0
  63. package/dist/core/render-review.d.ts.map +1 -0
  64. package/dist/core/render-review.js +219 -0
  65. package/dist/core/render-review.js.map +1 -0
  66. package/dist/core/render.d.ts +13 -0
  67. package/dist/core/render.d.ts.map +1 -0
  68. package/dist/core/render.js +80 -0
  69. package/dist/core/render.js.map +1 -0
  70. package/dist/core/review-schema-zod.d.ts +240 -0
  71. package/dist/core/review-schema-zod.d.ts.map +1 -0
  72. package/dist/core/review-schema-zod.js +218 -0
  73. package/dist/core/review-schema-zod.js.map +1 -0
  74. package/dist/core/review-schema.d.ts +42 -0
  75. package/dist/core/review-schema.d.ts.map +1 -0
  76. package/dist/core/review-schema.js +156 -0
  77. package/dist/core/review-schema.js.map +1 -0
  78. package/dist/core/review-writeback.d.ts +139 -0
  79. package/dist/core/review-writeback.d.ts.map +1 -0
  80. package/dist/core/review-writeback.js +313 -0
  81. package/dist/core/review-writeback.js.map +1 -0
  82. package/dist/core/skills.d.ts +122 -0
  83. package/dist/core/skills.d.ts.map +1 -0
  84. package/dist/core/skills.js +636 -0
  85. package/dist/core/skills.js.map +1 -0
  86. package/package.json +30 -4
  87. package/{lib/agents-md.js → src/cli/agents-md.ts} +25 -14
  88. package/{lib/audit.js → src/cli/audit.ts} +37 -44
  89. package/{lib/branch-protection.js → src/cli/branch-protection.ts} +75 -11
  90. package/{lib/edit-workflow.js → src/cli/edit-workflow.ts} +32 -11
  91. package/src/cli/index.ts +101 -0
  92. package/src/cli/main.ts +1376 -0
  93. package/{lib/skill-usage.js → src/cli/skill-usage.ts} +168 -94
  94. package/src/cli/skills.ts +386 -0
  95. package/{lib/update.js → src/cli/update.ts} +68 -27
  96. package/{lib/usage.js → src/cli/usage.ts} +167 -76
  97. package/src/core/audit.ts +53 -0
  98. package/{lib/detect.js → src/core/detect.ts} +100 -47
  99. package/src/core/index.ts +155 -0
  100. package/src/core/prompt-builder.ts +561 -0
  101. package/{lib/prompts.js → src/core/prompts.ts} +16 -2
  102. package/{lib/render-review.js → src/core/render-review.ts} +57 -25
  103. package/{lib/render.js → src/core/render.ts} +36 -10
  104. package/src/core/review-schema-zod.ts +262 -0
  105. package/{lib/review-schema.js → src/core/review-schema.ts} +68 -5
  106. package/src/core/review-writeback.ts +446 -0
  107. package/{lib/skills.js → src/core/skills.ts} +339 -342
  108. package/templates/workflow-py.yml.tmpl +2 -2
  109. package/templates/workflow-ts.yml.tmpl +2 -2
  110. package/templates/workflow.yml.tmpl +17 -8
@@ -1,7 +1,11 @@
1
1
  import { readFile, readdir, stat } from 'node:fs/promises';
2
2
  import { join, extname } from 'node:path';
3
3
 
4
- const EXT_TO_LANG = {
4
+ // Lookup tables for the project-shape detectors. Each map is `as const` so
5
+ // downstream consumers can rely on the value types narrowing to the literal
6
+ // strings rather than `string` — `_internal.EXT_TO_LANG['.ts']` resolves to
7
+ // `'typescript'` for IDE navigation, not just `string`.
8
+ export const EXT_TO_LANG = {
5
9
  '.ts': 'typescript', '.tsx': 'typescript',
6
10
  '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
7
11
  '.py': 'python',
@@ -14,12 +18,12 @@ const EXT_TO_LANG = {
14
18
  '.cs': 'csharp',
15
19
  '.c': 'c', '.h': 'c',
16
20
  '.cpp': 'cpp', '.cc': 'cpp', '.hpp': 'cpp',
17
- };
21
+ } as const satisfies Record<string, string>;
18
22
 
19
23
  // Dependency name → search term hint passed to skills.sh.
20
24
  // Only well-known frameworks; obscure packages get filtered out so the
21
25
  // skills.sh query doesn't get drowned in noise.
22
- const DEP_TO_TERM = {
26
+ export const DEP_TO_TERM = {
23
27
  'next': 'nextjs', 'react': 'react', 'vue': 'vue', 'svelte': 'svelte',
24
28
  '@angular/core': 'angular', 'solid-js': 'solid',
25
29
  'express': 'express', 'fastify': 'fastify', 'koa': 'koa', 'hono': 'hono',
@@ -29,16 +33,45 @@ const DEP_TO_TERM = {
29
33
  'vitest': 'vitest', 'jest': 'jest', 'playwright': 'playwright',
30
34
  '@playwright/test': 'playwright',
31
35
  'typescript': 'typescript',
32
- };
36
+ } as const satisfies Record<string, string>;
33
37
 
34
- const PY_DEP_TO_TERM = {
38
+ export const PY_DEP_TO_TERM = {
35
39
  'django': 'django', 'flask': 'flask', 'fastapi': 'fastapi',
36
40
  'click': 'click', 'typer': 'typer',
37
41
  'pytest': 'pytest', 'sqlalchemy': 'sqlalchemy',
38
42
  'pydantic': 'pydantic', 'numpy': 'numpy', 'pandas': 'pandas',
39
- };
43
+ } as const satisfies Record<string, string>;
40
44
 
41
- async function fileExists(path) {
45
+ // Result of running every detector + post-processing — the data shape callers
46
+ // (bin/clud-bug.js, lib/update.js) read from. `description` is nullable
47
+ // because the README fallback may not produce anything.
48
+ export interface DetectedSignals {
49
+ name: string | null;
50
+ description: string | null;
51
+ languages: string[];
52
+ histogram: Record<string, number>;
53
+ searchTerms: string[];
54
+ primaryLanguage: string | null;
55
+ }
56
+
57
+ // Per-detector intermediate type — what each manifest-reader returns before
58
+ // we aggregate. `languages` is the languages each manifest implies (e.g.
59
+ // package.json implies ['javascript']) so we can union them in detect().
60
+ interface DetectorResult {
61
+ name: string | null;
62
+ description: string | null;
63
+ languages: string[];
64
+ terms: string[];
65
+ }
66
+
67
+ interface PackageJson {
68
+ name?: string;
69
+ description?: string;
70
+ dependencies?: Record<string, string>;
71
+ devDependencies?: Record<string, string>;
72
+ }
73
+
74
+ async function fileExists(path: string): Promise<boolean> {
42
75
  try {
43
76
  await stat(path);
44
77
  return true;
@@ -47,15 +80,15 @@ async function fileExists(path) {
47
80
  }
48
81
  }
49
82
 
50
- async function readJsonSafe(path) {
83
+ async function readJsonSafe<T = unknown>(path: string): Promise<T | null> {
51
84
  try {
52
- return JSON.parse(await readFile(path, 'utf8'));
85
+ return JSON.parse(await readFile(path, 'utf8')) as T;
53
86
  } catch {
54
87
  return null;
55
88
  }
56
89
  }
57
90
 
58
- async function readTextSafe(path) {
91
+ async function readTextSafe(path: string): Promise<string | null> {
59
92
  try {
60
93
  return await readFile(path, 'utf8');
61
94
  } catch {
@@ -63,26 +96,27 @@ async function readTextSafe(path) {
63
96
  }
64
97
  }
65
98
 
66
- async function detectFromPackageJson(root) {
67
- const pkg = await readJsonSafe(join(root, 'package.json'));
99
+ async function detectFromPackageJson(root: string): Promise<DetectorResult | null> {
100
+ const pkg = await readJsonSafe<PackageJson>(join(root, 'package.json'));
68
101
  if (!pkg) return null;
69
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
70
- const terms = new Set();
102
+ const deps: Record<string, string> = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
103
+ const terms = new Set<string>();
71
104
  for (const dep of Object.keys(deps)) {
72
- if (DEP_TO_TERM[dep]) terms.add(DEP_TO_TERM[dep]);
105
+ const term = (DEP_TO_TERM as Record<string, string>)[dep];
106
+ if (term) terms.add(term);
73
107
  }
74
108
  return {
75
- name: pkg.name,
109
+ name: pkg.name ?? null,
76
110
  description: pkg.description || null,
77
111
  languages: ['javascript', ...(deps.typescript || pkg.devDependencies?.typescript ? ['typescript'] : [])],
78
112
  terms: [...terms],
79
113
  };
80
114
  }
81
115
 
82
- async function detectFromPyproject(root) {
116
+ async function detectFromPyproject(root: string): Promise<DetectorResult | null> {
83
117
  const text = await readTextSafe(join(root, 'pyproject.toml'));
84
118
  if (!text) return null;
85
- const terms = new Set();
119
+ const terms = new Set<string>();
86
120
  for (const [dep, term] of Object.entries(PY_DEP_TO_TERM)) {
87
121
  // crude but adequate match — full TOML parse would be overkill for the
88
122
  // dependency-name lookup we actually need
@@ -91,58 +125,59 @@ async function detectFromPyproject(root) {
91
125
  const nameMatch = text.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
92
126
  const descMatch = text.match(/^\s*description\s*=\s*["']([^"']+)["']/m);
93
127
  return {
94
- name: nameMatch?.[1] || null,
95
- description: descMatch?.[1] || null,
128
+ name: nameMatch?.[1] ?? null,
129
+ description: descMatch?.[1] ?? null,
96
130
  languages: ['python'],
97
131
  terms: [...terms],
98
132
  };
99
133
  }
100
134
 
101
- async function detectFromRequirements(root) {
135
+ async function detectFromRequirements(root: string): Promise<DetectorResult | null> {
102
136
  const text = await readTextSafe(join(root, 'requirements.txt'));
103
137
  if (!text) return null;
104
- const terms = new Set();
138
+ const terms = new Set<string>();
105
139
  for (const line of text.split('\n')) {
106
- const dep = line.split(/[<>=~ #]/)[0].trim().toLowerCase();
107
- if (PY_DEP_TO_TERM[dep]) terms.add(PY_DEP_TO_TERM[dep]);
140
+ const dep = (line.split(/[<>=~ #]/)[0] ?? '').trim().toLowerCase();
141
+ const term = (PY_DEP_TO_TERM as Record<string, string>)[dep];
142
+ if (term) terms.add(term);
108
143
  }
109
144
  return { name: null, description: null, languages: ['python'], terms: [...terms] };
110
145
  }
111
146
 
112
- async function detectFromGoMod(root) {
147
+ async function detectFromGoMod(root: string): Promise<DetectorResult | null> {
113
148
  const text = await readTextSafe(join(root, 'go.mod'));
114
149
  if (!text) return null;
115
150
  const moduleMatch = text.match(/^module\s+(\S+)/m);
116
151
  return {
117
- name: moduleMatch?.[1]?.split('/').pop() || null,
152
+ name: moduleMatch?.[1]?.split('/').pop() ?? null,
118
153
  description: null,
119
154
  languages: ['go'],
120
155
  terms: [],
121
156
  };
122
157
  }
123
158
 
124
- async function detectFromCargo(root) {
159
+ async function detectFromCargo(root: string): Promise<DetectorResult | null> {
125
160
  const text = await readTextSafe(join(root, 'Cargo.toml'));
126
161
  if (!text) return null;
127
162
  const nameMatch = text.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
128
163
  const descMatch = text.match(/^\s*description\s*=\s*["']([^"']+)["']/m);
129
164
  return {
130
- name: nameMatch?.[1] || null,
131
- description: descMatch?.[1] || null,
165
+ name: nameMatch?.[1] ?? null,
166
+ description: descMatch?.[1] ?? null,
132
167
  languages: ['rust'],
133
168
  terms: [],
134
169
  };
135
170
  }
136
171
 
137
- async function detectFromGemfile(root) {
172
+ async function detectFromGemfile(root: string): Promise<DetectorResult | null> {
138
173
  const text = await readTextSafe(join(root, 'Gemfile'));
139
174
  if (!text) return null;
140
175
  return { name: null, description: null, languages: ['ruby'], terms: [] };
141
176
  }
142
177
 
143
- async function fileHistogram(root) {
144
- const counts = {};
145
- async function walk(dir, depth) {
178
+ async function fileHistogram(root: string): Promise<Record<string, number>> {
179
+ const counts: Record<string, number> = {};
180
+ async function walk(dir: string, depth: number): Promise<void> {
146
181
  if (depth > 3) return;
147
182
  let entries;
148
183
  try { entries = await readdir(dir, { withFileTypes: true }); } catch { return; }
@@ -154,7 +189,7 @@ async function fileHistogram(root) {
154
189
  if (entry.isDirectory()) {
155
190
  await walk(full, depth + 1);
156
191
  } else {
157
- const lang = EXT_TO_LANG[extname(entry.name)];
192
+ const lang = (EXT_TO_LANG as Record<string, string>)[extname(entry.name)];
158
193
  if (lang) counts[lang] = (counts[lang] || 0) + 1;
159
194
  }
160
195
  }
@@ -163,7 +198,7 @@ async function fileHistogram(root) {
163
198
  return counts;
164
199
  }
165
200
 
166
- function firstParagraph(readme) {
201
+ function firstParagraph(readme: string | null): string | null {
167
202
  if (!readme) return null;
168
203
  const lines = readme.split('\n').slice(0, 200);
169
204
  const paragraphs = lines.join('\n').split(/\n\s*\n/);
@@ -174,20 +209,22 @@ function firstParagraph(readme) {
174
209
  return null;
175
210
  }
176
211
 
177
- export async function detect(root) {
212
+ export async function detect(root: string): Promise<DetectedSignals> {
178
213
  const detectors = [
179
214
  detectFromPackageJson, detectFromPyproject, detectFromRequirements,
180
215
  detectFromGoMod, detectFromCargo, detectFromGemfile,
181
216
  ];
182
- const results = (await Promise.all(detectors.map(d => d(root)))).filter(Boolean);
217
+ const results = (await Promise.all(detectors.map(d => d(root)))).filter(
218
+ (r): r is DetectorResult => r !== null,
219
+ );
183
220
  const histogram = await fileHistogram(root);
184
221
  const readme = await readTextSafe(join(root, 'README.md'))
185
222
  || await readTextSafe(join(root, 'README'));
186
223
 
187
- const languages = new Set();
188
- const terms = new Set();
189
- let name = null;
190
- let description = null;
224
+ const languages = new Set<string>();
225
+ const terms = new Set<string>();
226
+ let name: string | null = null;
227
+ let description: string | null = null;
191
228
  for (const r of results) {
192
229
  for (const lang of r.languages) languages.add(lang);
193
230
  for (const term of r.terms) terms.add(term);
@@ -206,12 +243,22 @@ export async function detect(root) {
206
243
  languages: sortedLangs,
207
244
  histogram,
208
245
  searchTerms: [...new Set([...terms, ...sortedLangs.slice(0, 2)])],
209
- primaryLanguage: sortedLangs[0] || null,
246
+ primaryLanguage: sortedLangs[0] ?? null,
210
247
  };
211
248
  }
212
249
 
213
- export function buildDescriptionLine(signals) {
214
- const parts = [];
250
+ // Input shape for buildDescriptionLine — a subset of DetectedSignals. We
251
+ // don't reuse DetectedSignals directly because the callers (templates,
252
+ // LLM-flow tests) often hand-build a subset rather than running detect().
253
+ export interface DescriptionLineSignals {
254
+ name?: string | null;
255
+ description?: string | null;
256
+ primaryLanguage?: string | null;
257
+ searchTerms?: string[];
258
+ }
259
+
260
+ export function buildDescriptionLine(signals: DescriptionLineSignals): string {
261
+ const parts: string[] = [];
215
262
  if (signals.name) parts.push(`This project is "${signals.name}".`);
216
263
  if (signals.description) {
217
264
  // v0.6.25 / issue #89: when signals.description comes from a
@@ -226,7 +273,7 @@ export function buildDescriptionLine(signals) {
226
273
  parts.push(/[.!?]$/.test(desc) ? desc : `${desc}.`);
227
274
  }
228
275
  if (signals.primaryLanguage) {
229
- const frameworks = [...new Set(signals.searchTerms)].filter(t =>
276
+ const frameworks = [...new Set(signals.searchTerms || [])].filter((t) =>
230
277
  !['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'].includes(t));
231
278
  const frameworkPart = frameworks.length ? ` using ${frameworks.join(', ')}` : '';
232
279
  parts.push(`It's primarily ${signals.primaryLanguage}${frameworkPart}.`);
@@ -235,5 +282,11 @@ export function buildDescriptionLine(signals) {
235
282
  return parts.join(' ');
236
283
  }
237
284
 
238
- // Test seam: allow tests to inject the EXT_TO_LANG and DEP_TO_TERM tables
239
- export const _internal = { EXT_TO_LANG, DEP_TO_TERM, PY_DEP_TO_TERM, fileHistogram, firstParagraph };
285
+ // Architect's anti-pattern fix (Phase 2): the JS source used a single
286
+ // `export const _internal = { }` namespace as a test seam. The TS port
287
+ // promotes the table exports (EXT_TO_LANG, DEP_TO_TERM, PY_DEP_TO_TERM)
288
+ // to direct top-level exports, and exposes the two helper functions
289
+ // fileHistogram + firstParagraph as direct named exports too. Tests now
290
+ // import each symbol by name. No `_internal` re-export — that pattern is
291
+ // gone.
292
+ export { fileHistogram, firstParagraph };
@@ -0,0 +1,155 @@
1
+ // Public API surface for `clud-bug/core` (consumed via the package's
2
+ // `./core` exports map → `dist/core/index.js`).
3
+ //
4
+ // Each line re-exports one core module's public symbols. Modules are
5
+ // added incrementally as the v0.7.0 TypeScript migration converts each
6
+ // lib/* JS file.
7
+
8
+ export { reviewPrompt, type ReviewPromptOptions, type ReviewPromptLanguage } from './prompts.js';
9
+ export {
10
+ REVIEW_SCHEMA,
11
+ serializedReviewSchema,
12
+ type ReviewData,
13
+ type ReviewFinding,
14
+ type ReviewSummaryCounts,
15
+ type ReviewStatusHeader,
16
+ type FindingSeverity,
17
+ type PerSkillScanItem,
18
+ type DedicatedSection,
19
+ } from './review-schema.js';
20
+ export { renderReview, SEVERITY_LABEL } from './render-review.js';
21
+ export {
22
+ detect,
23
+ buildDescriptionLine,
24
+ EXT_TO_LANG,
25
+ DEP_TO_TERM,
26
+ PY_DEP_TO_TERM,
27
+ fileHistogram,
28
+ firstParagraph,
29
+ type DetectedSignals,
30
+ type DescriptionLineSignals,
31
+ } from './detect.js';
32
+ export {
33
+ render,
34
+ renderFile,
35
+ pickTemplate,
36
+ templateLanguage,
37
+ DEFAULTS,
38
+ type RenderDefaults,
39
+ type RenderVars,
40
+ type TemplateLanguage,
41
+ } from './render.js';
42
+ export {
43
+ durationToGitSince,
44
+ renderAuditHeader,
45
+ type AuditHeaderInput,
46
+ } from './audit.js';
47
+ // Zod-typed wire-shape review + internal-shape helpers for the
48
+ // AI-Gateway consumer (clud-bug-app). The CLI-shape JSON Schema +
49
+ // CLI summary-comment renderer above stay first-class; the Zod port
50
+ // below is additive.
51
+ //
52
+ // Naming: the wire-shape `FindingItem` collides with neither CLI nor
53
+ // App existing exports. The internal-shape `Finding` (FindingItem +
54
+ // severity) is re-exported as `ZodFinding` to disambiguate from the
55
+ // CLI's `ReviewFinding`.
56
+ export {
57
+ reviewSchema,
58
+ crossCheckSchema,
59
+ findingItemSchema,
60
+ findingSchema,
61
+ perSkillScanItemSchema,
62
+ dedicatedSectionSchema,
63
+ summaryCountsSchema,
64
+ severitySchema,
65
+ statusHeaderSchema,
66
+ crossCheckVerdictSchema,
67
+ severityValues,
68
+ statusHeaderValues,
69
+ flattenFindings,
70
+ unflattenFindings,
71
+ deriveSummaryCounts,
72
+ deriveSkillsReferenced,
73
+ buildReviewFromFindings,
74
+ type Review,
75
+ type CrossCheck,
76
+ type CrossCheckVerdictSchema,
77
+ type Severity,
78
+ type StatusHeader,
79
+ type SummaryCounts,
80
+ type FindingItem,
81
+ type PerSkillScanItem as ZodPerSkillScanItem,
82
+ type DedicatedSection as ZodDedicatedSection,
83
+ type Finding as ZodFinding,
84
+ } from './review-schema-zod.js';
85
+ // AI-Gateway prompt builder (App's review pass). The CLI-shape
86
+ // `reviewPrompt` workflow-string above stays; this is additive.
87
+ export {
88
+ buildReviewPrompt,
89
+ buildCrossCheckPrompt,
90
+ buildConsensusPrompt,
91
+ skillMatchesDiff,
92
+ globMatch,
93
+ truncatePatch,
94
+ sliceUtf8Bytes,
95
+ MAX_PATCH_BYTES_PER_FILE,
96
+ DEFAULT_MAX_SKILL_BYTES,
97
+ type BuildReviewPromptInput,
98
+ type BuildCrossCheckPromptInput,
99
+ type BuiltPrompt,
100
+ type ChangedFile,
101
+ type ChangedFileStatus,
102
+ type PullRequestDiff,
103
+ type PromptAppliesToRule,
104
+ type PromptSkillFrontmatter,
105
+ type PromptLoadedSkill,
106
+ } from './prompt-builder.js';
107
+ // SPEC §1.8.1 doc-file renderer (`docs/reviews/PR-<n>.md`). Renamed
108
+ // from the App's `renderReview` to `renderReviewFile` to disambiguate
109
+ // from the CLI's `renderReview` (summary PR-comment shape) above.
110
+ export {
111
+ renderReviewFile,
112
+ renderMultiPassMarkdown,
113
+ reviewFilePath,
114
+ reviewCommitMessage,
115
+ PROTOCOL_VERSION,
116
+ WRITTEN_BY,
117
+ SEVERITY_EMOJI as REVIEW_FILE_SEVERITY_EMOJI,
118
+ type RenderReviewFileInput,
119
+ type RenderMultiPassMarkdownInput,
120
+ type MultiPassReview,
121
+ type UnifiedFinding,
122
+ type PassAttribution,
123
+ type PassSource,
124
+ type ReviewPassMode,
125
+ type MultiPassVerdict,
126
+ } from './review-writeback.js';
127
+ export {
128
+ API_BASE,
129
+ MAX_SKILLS,
130
+ SkillsClient,
131
+ normalizeList,
132
+ rankAndCap,
133
+ readReviewMode,
134
+ readAppliesTo,
135
+ appliesToPr,
136
+ partitionByReviewMode,
137
+ extractPerSkillLine,
138
+ selectReviewHeader,
139
+ extractFirstReviewHeaderLine,
140
+ selectReviewBody,
141
+ extractStatsHeader,
142
+ isCriticalReviewHeader,
143
+ classifyPerSkillOutcome,
144
+ parseFrontmatter,
145
+ stripFrontmatter,
146
+ type SkillDescriptor,
147
+ type RankableSkill,
148
+ type AppliesToRule,
149
+ type SkillWithOptionalContent,
150
+ type PrComment,
151
+ type ReviewStatsHeader,
152
+ type SkillFrontmatter,
153
+ type SkillSource,
154
+ type SkillReviewMode,
155
+ } from './skills.js';