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
+ import { readFile, readdir, stat } from 'node:fs/promises';
2
+ import { join, extname } from 'node:path';
3
+ // Lookup tables for the project-shape detectors. Each map is `as const` so
4
+ // downstream consumers can rely on the value types narrowing to the literal
5
+ // strings rather than `string` — `_internal.EXT_TO_LANG['.ts']` resolves to
6
+ // `'typescript'` for IDE navigation, not just `string`.
7
+ export const EXT_TO_LANG = {
8
+ '.ts': 'typescript', '.tsx': 'typescript',
9
+ '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
10
+ '.py': 'python',
11
+ '.go': 'go',
12
+ '.rs': 'rust',
13
+ '.rb': 'ruby',
14
+ '.java': 'java', '.kt': 'kotlin',
15
+ '.swift': 'swift',
16
+ '.php': 'php',
17
+ '.cs': 'csharp',
18
+ '.c': 'c', '.h': 'c',
19
+ '.cpp': 'cpp', '.cc': 'cpp', '.hpp': 'cpp',
20
+ };
21
+ // Dependency name → search term hint passed to skills.sh.
22
+ // Only well-known frameworks; obscure packages get filtered out so the
23
+ // skills.sh query doesn't get drowned in noise.
24
+ export const DEP_TO_TERM = {
25
+ 'next': 'nextjs', 'react': 'react', 'vue': 'vue', 'svelte': 'svelte',
26
+ '@angular/core': 'angular', 'solid-js': 'solid',
27
+ 'express': 'express', 'fastify': 'fastify', 'koa': 'koa', 'hono': 'hono',
28
+ 'prisma': 'prisma', '@prisma/client': 'prisma', 'drizzle-orm': 'drizzle',
29
+ 'mongoose': 'mongodb', 'mongodb': 'mongodb',
30
+ 'tailwindcss': 'tailwind',
31
+ 'vitest': 'vitest', 'jest': 'jest', 'playwright': 'playwright',
32
+ '@playwright/test': 'playwright',
33
+ 'typescript': 'typescript',
34
+ };
35
+ export const PY_DEP_TO_TERM = {
36
+ 'django': 'django', 'flask': 'flask', 'fastapi': 'fastapi',
37
+ 'click': 'click', 'typer': 'typer',
38
+ 'pytest': 'pytest', 'sqlalchemy': 'sqlalchemy',
39
+ 'pydantic': 'pydantic', 'numpy': 'numpy', 'pandas': 'pandas',
40
+ };
41
+ async function fileExists(path) {
42
+ try {
43
+ await stat(path);
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ async function readJsonSafe(path) {
51
+ try {
52
+ return JSON.parse(await readFile(path, 'utf8'));
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ async function readTextSafe(path) {
59
+ try {
60
+ return await readFile(path, 'utf8');
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ async function detectFromPackageJson(root) {
67
+ const pkg = await readJsonSafe(join(root, 'package.json'));
68
+ if (!pkg)
69
+ return null;
70
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
71
+ const terms = new Set();
72
+ for (const dep of Object.keys(deps)) {
73
+ const term = DEP_TO_TERM[dep];
74
+ if (term)
75
+ terms.add(term);
76
+ }
77
+ return {
78
+ name: pkg.name ?? null,
79
+ description: pkg.description || null,
80
+ languages: ['javascript', ...(deps.typescript || pkg.devDependencies?.typescript ? ['typescript'] : [])],
81
+ terms: [...terms],
82
+ };
83
+ }
84
+ async function detectFromPyproject(root) {
85
+ const text = await readTextSafe(join(root, 'pyproject.toml'));
86
+ if (!text)
87
+ return null;
88
+ const terms = new Set();
89
+ for (const [dep, term] of Object.entries(PY_DEP_TO_TERM)) {
90
+ // crude but adequate match — full TOML parse would be overkill for the
91
+ // dependency-name lookup we actually need
92
+ if (new RegExp(`["']${dep}[><=~ "']`, 'i').test(text))
93
+ terms.add(term);
94
+ }
95
+ const nameMatch = text.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
96
+ const descMatch = text.match(/^\s*description\s*=\s*["']([^"']+)["']/m);
97
+ return {
98
+ name: nameMatch?.[1] ?? null,
99
+ description: descMatch?.[1] ?? null,
100
+ languages: ['python'],
101
+ terms: [...terms],
102
+ };
103
+ }
104
+ async function detectFromRequirements(root) {
105
+ const text = await readTextSafe(join(root, 'requirements.txt'));
106
+ if (!text)
107
+ return null;
108
+ const terms = new Set();
109
+ for (const line of text.split('\n')) {
110
+ const dep = (line.split(/[<>=~ #]/)[0] ?? '').trim().toLowerCase();
111
+ const term = PY_DEP_TO_TERM[dep];
112
+ if (term)
113
+ terms.add(term);
114
+ }
115
+ return { name: null, description: null, languages: ['python'], terms: [...terms] };
116
+ }
117
+ async function detectFromGoMod(root) {
118
+ const text = await readTextSafe(join(root, 'go.mod'));
119
+ if (!text)
120
+ return null;
121
+ const moduleMatch = text.match(/^module\s+(\S+)/m);
122
+ return {
123
+ name: moduleMatch?.[1]?.split('/').pop() ?? null,
124
+ description: null,
125
+ languages: ['go'],
126
+ terms: [],
127
+ };
128
+ }
129
+ async function detectFromCargo(root) {
130
+ const text = await readTextSafe(join(root, 'Cargo.toml'));
131
+ if (!text)
132
+ return null;
133
+ const nameMatch = text.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
134
+ const descMatch = text.match(/^\s*description\s*=\s*["']([^"']+)["']/m);
135
+ return {
136
+ name: nameMatch?.[1] ?? null,
137
+ description: descMatch?.[1] ?? null,
138
+ languages: ['rust'],
139
+ terms: [],
140
+ };
141
+ }
142
+ async function detectFromGemfile(root) {
143
+ const text = await readTextSafe(join(root, 'Gemfile'));
144
+ if (!text)
145
+ return null;
146
+ return { name: null, description: null, languages: ['ruby'], terms: [] };
147
+ }
148
+ async function fileHistogram(root) {
149
+ const counts = {};
150
+ async function walk(dir, depth) {
151
+ if (depth > 3)
152
+ return;
153
+ let entries;
154
+ try {
155
+ entries = await readdir(dir, { withFileTypes: true });
156
+ }
157
+ catch {
158
+ return;
159
+ }
160
+ for (const entry of entries) {
161
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' ||
162
+ entry.name === 'dist' || entry.name === 'build' ||
163
+ entry.name === '__pycache__' || entry.name === 'target')
164
+ continue;
165
+ const full = join(dir, entry.name);
166
+ if (entry.isDirectory()) {
167
+ await walk(full, depth + 1);
168
+ }
169
+ else {
170
+ const lang = EXT_TO_LANG[extname(entry.name)];
171
+ if (lang)
172
+ counts[lang] = (counts[lang] || 0) + 1;
173
+ }
174
+ }
175
+ }
176
+ await walk(root, 0);
177
+ return counts;
178
+ }
179
+ function firstParagraph(readme) {
180
+ if (!readme)
181
+ return null;
182
+ const lines = readme.split('\n').slice(0, 200);
183
+ const paragraphs = lines.join('\n').split(/\n\s*\n/);
184
+ for (const p of paragraphs) {
185
+ const cleaned = p.replace(/^#+\s*/, '').replace(/[*_`]/g, '').trim();
186
+ if (cleaned.length > 40)
187
+ return cleaned.slice(0, 500);
188
+ }
189
+ return null;
190
+ }
191
+ export async function detect(root) {
192
+ const detectors = [
193
+ detectFromPackageJson, detectFromPyproject, detectFromRequirements,
194
+ detectFromGoMod, detectFromCargo, detectFromGemfile,
195
+ ];
196
+ const results = (await Promise.all(detectors.map(d => d(root)))).filter((r) => r !== null);
197
+ const histogram = await fileHistogram(root);
198
+ const readme = await readTextSafe(join(root, 'README.md'))
199
+ || await readTextSafe(join(root, 'README'));
200
+ const languages = new Set();
201
+ const terms = new Set();
202
+ let name = null;
203
+ let description = null;
204
+ for (const r of results) {
205
+ for (const lang of r.languages)
206
+ languages.add(lang);
207
+ for (const term of r.terms)
208
+ terms.add(term);
209
+ if (!name && r.name)
210
+ name = r.name;
211
+ if (!description && r.description)
212
+ description = r.description;
213
+ }
214
+ for (const lang of Object.keys(histogram))
215
+ languages.add(lang);
216
+ if (!description)
217
+ description = firstParagraph(readme);
218
+ // Prefer the language with the most files when picking a primary
219
+ const sortedLangs = [...languages].sort((a, b) => (histogram[b] || 0) - (histogram[a] || 0));
220
+ return {
221
+ name,
222
+ description,
223
+ languages: sortedLangs,
224
+ histogram,
225
+ searchTerms: [...new Set([...terms, ...sortedLangs.slice(0, 2)])],
226
+ primaryLanguage: sortedLangs[0] ?? null,
227
+ };
228
+ }
229
+ export function buildDescriptionLine(signals) {
230
+ const parts = [];
231
+ if (signals.name)
232
+ parts.push(`This project is "${signals.name}".`);
233
+ if (signals.description) {
234
+ // v0.6.25 / issue #89: when signals.description comes from a
235
+ // README first paragraph or similar multi-paragraph source, the
236
+ // raw `\n` characters survive into the rendered YAML's
237
+ // APPEND_SYSTEM_PROMPT value. The renderer's indent-aware
238
+ // substitution preserves layout but a literal `\n` inside a YAML
239
+ // double-quoted string is interpreted as a newline, breaking
240
+ // the YAML. Collapse any whitespace run (including newlines + tabs)
241
+ // to a single space before further processing.
242
+ const desc = signals.description.replace(/\s+/g, ' ').trim();
243
+ parts.push(/[.!?]$/.test(desc) ? desc : `${desc}.`);
244
+ }
245
+ if (signals.primaryLanguage) {
246
+ const frameworks = [...new Set(signals.searchTerms || [])].filter((t) => !['typescript', 'javascript', 'python', 'go', 'rust', 'ruby'].includes(t));
247
+ const frameworkPart = frameworks.length ? ` using ${frameworks.join(', ')}` : '';
248
+ parts.push(`It's primarily ${signals.primaryLanguage}${frameworkPart}.`);
249
+ }
250
+ if (parts.length === 0)
251
+ return 'Project context unavailable — review on the merits of the diff alone.';
252
+ return parts.join(' ');
253
+ }
254
+ // Architect's anti-pattern fix (Phase 2): the JS source used a single
255
+ // `export const _internal = { … }` namespace as a test seam. The TS port
256
+ // promotes the table exports (EXT_TO_LANG, DEP_TO_TERM, PY_DEP_TO_TERM)
257
+ // to direct top-level exports, and exposes the two helper functions
258
+ // fileHistogram + firstParagraph as direct named exports too. Tests now
259
+ // import each symbol by name. No `_internal` re-export — that pattern is
260
+ // gone.
261
+ export { fileHistogram, firstParagraph };
262
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/core/detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /** One parsed finding from a prior `docs/reviews/PR-<n>.md`. */
2
+ export type ParsedFinding = {
3
+ /** Relative path. Falls back to '(unknown file)' when the prior file
4
+ * itself rendered the unknown-file marker; we keep it intact for
5
+ * identity stability. */
6
+ file: string;
7
+ /**
8
+ * 1-indexed line number. Zero when the prior file had no `:N` suffix
9
+ * (cross-cutting findings). Zero participates in identity as-is.
10
+ */
11
+ line: number;
12
+ severity: 'critical' | 'minor' | 'preexisting';
13
+ skillName: string;
14
+ summary: string;
15
+ };
16
+ export type ParsedReview = {
17
+ findings: ParsedFinding[];
18
+ };
19
+ /**
20
+ * Parse a `docs/reviews/PR-<n>.md` markdown back into structured findings.
21
+ *
22
+ * Robust to:
23
+ * - `null` / `undefined` input → returns `null`
24
+ * - empty / whitespace-only markdown → returns `null`
25
+ * - missing severity sections → those sections contribute 0
26
+ * - lines that don't match the SPEC bullet shape → silently dropped
27
+ * - mixed unknown-file markers (`(unknown file)` vs absent)
28
+ *
29
+ * Parsing strategy: walk top-down, switch active severity on each
30
+ * `### <emoji> <Label>` header (one of the three SPEC §1.8.1 buckets),
31
+ * then collect every line starting with `- **` until the next header
32
+ * (or `---` end marker). Each `- **<file>:<line>** — <skill>: <summary>`
33
+ * line is split into its 4 fields; `:<line>` is optional (cross-cutting).
34
+ *
35
+ * `(unknown file)` files are preserved verbatim — they participate in
36
+ * identity, so the same cross-cutting finding can still be diffed across
37
+ * rounds even when neither pass has a line anchor.
38
+ */
39
+ export declare function parsePriorReviewFile(markdown: string | null | undefined): ParsedReview | null;
40
+ /**
41
+ * Diff prior vs current.
42
+ *
43
+ * `resolvedFindings`: findings that appeared in `prior` but NOT in
44
+ * `current`. The PR author (or auto-fix) addressed them.
45
+ *
46
+ * `stillOpenFindings`: findings that appeared in BOTH `prior` and
47
+ * `current`. These are persistent — the PR author hasn't fixed them
48
+ * (or the bot still considers them findings post-fix-push).
49
+ *
50
+ * Findings unique to `current` (newly raised this round) appear in
51
+ * neither list — those are surfaced directly by the renderer's normal
52
+ * severity-bucket emission.
53
+ *
54
+ * Order is preserved from `prior` for stability across rounds.
55
+ */
56
+ export declare function diffFindings(prior: ParsedReview | null, current: {
57
+ critical_findings?: Array<{
58
+ skill: string;
59
+ file?: string;
60
+ line?: number;
61
+ summary: string;
62
+ }>;
63
+ minor_findings?: Array<{
64
+ skill: string;
65
+ file?: string;
66
+ line?: number;
67
+ summary: string;
68
+ }>;
69
+ preexisting_findings?: Array<{
70
+ skill: string;
71
+ file?: string;
72
+ line?: number;
73
+ summary: string;
74
+ }>;
75
+ }): {
76
+ resolvedFindings: ParsedFinding[];
77
+ stillOpenFindings: ParsedFinding[];
78
+ };
79
+ /**
80
+ * Stable identity for a finding. Used for diffing prior vs current
81
+ * across review rounds AND (by intention) shareable with the
82
+ * clud-bug-app inline-thread anchor hash so future cross-feature
83
+ * surfaces (e.g. "the auto-fix that resolved this thread also resolves
84
+ * this doc-file finding") align without re-computing.
85
+ *
86
+ * Exposed for tests + downstream callers that want to align their own
87
+ * finding storage on the same scheme.
88
+ */
89
+ export declare function findingIdentity(f: {
90
+ file: string;
91
+ line: number;
92
+ severity: 'critical' | 'minor' | 'preexisting';
93
+ skillName: string;
94
+ summary: string;
95
+ }): string;
96
+ //# sourceMappingURL=diff-findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-findings.d.ts","sourceRoot":"","sources":["../../src/core/diff-findings.ts"],"names":[],"mappings":"AAgCA,gEAAgE;AAChE,MAAM,MAAM,aAAa,GAAG;IAC1B;;8BAE0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,OAAO,GAAG,aAAa,CAAC;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,YAAY,GAAG,IAAI,CA+DrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,GAAG,IAAI,EAC1B,OAAO,EAAE;IACP,iBAAiB,CAAC,EAAE,KAAK,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAC3B,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ,GACA;IACD,gBAAgB,EAAE,aAAa,EAAE,CAAC;IAClC,iBAAiB,EAAE,aAAa,EAAE,CAAC;CACpC,CAgDA;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,OAAO,GAAG,aAAa,CAAC;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAST"}
@@ -0,0 +1,261 @@
1
+ // SPEC §1.8.1 multi-pass diff: prior `docs/reviews/PR-<n>.md` vs current
2
+ // review findings. Produces the `**Resolved this round:**` /
3
+ // `**Still open:**` lists that `renderReviewFile` consumes.
4
+ //
5
+ // Identity model
6
+ // --------------
7
+ // Each finding's identity is a stable hash of:
8
+ //
9
+ // `${file}:${line}:${severity}:${skillName}:${summary.slice(0, 100)}`
10
+ //
11
+ // A 100-character truncation on `summary` is enough to discriminate
12
+ // distinct findings without being so long that whitespace/punctuation
13
+ // drift between passes breaks identity. A severity-bucket change (e.g.
14
+ // the same skill flags the same line as 🔴 then 🟡 the next round)
15
+ // produces a DIFFERENT identity — by design — so a severity downgrade
16
+ // counts as one resolved finding + one new finding (the user gets
17
+ // credit for the fix AND can see the bot still has a concern, just at
18
+ // a lower severity).
19
+ //
20
+ // The same identity shape is what auto-fix / auto-resolve in
21
+ // clud-bug-app uses for thread anchoring; keeping it identical means a
22
+ // future per-finding cross-reference between the doc-file diff and the
23
+ // inline-thread surface is trivial.
24
+ import { flattenFindings, } from './review-schema-zod.js';
25
+ import { SEVERITY_EMOJI } from './review-writeback.js';
26
+ /**
27
+ * Parse a `docs/reviews/PR-<n>.md` markdown back into structured findings.
28
+ *
29
+ * Robust to:
30
+ * - `null` / `undefined` input → returns `null`
31
+ * - empty / whitespace-only markdown → returns `null`
32
+ * - missing severity sections → those sections contribute 0
33
+ * - lines that don't match the SPEC bullet shape → silently dropped
34
+ * - mixed unknown-file markers (`(unknown file)` vs absent)
35
+ *
36
+ * Parsing strategy: walk top-down, switch active severity on each
37
+ * `### <emoji> <Label>` header (one of the three SPEC §1.8.1 buckets),
38
+ * then collect every line starting with `- **` until the next header
39
+ * (or `---` end marker). Each `- **<file>:<line>** — <skill>: <summary>`
40
+ * line is split into its 4 fields; `:<line>` is optional (cross-cutting).
41
+ *
42
+ * `(unknown file)` files are preserved verbatim — they participate in
43
+ * identity, so the same cross-cutting finding can still be diffed across
44
+ * rounds even when neither pass has a line anchor.
45
+ */
46
+ export function parsePriorReviewFile(markdown) {
47
+ if (markdown == null)
48
+ return null;
49
+ if (markdown.trim() === '')
50
+ return null;
51
+ const lines = markdown.split('\n');
52
+ const out = [];
53
+ let current = null;
54
+ for (const raw of lines) {
55
+ // Strip trailing whitespace; leading indent is meaningful for
56
+ // sub-lines (Reasoning, attribution) which we deliberately ignore.
57
+ const line = raw.replace(/\s+$/, '');
58
+ // The SPEC §1.8.1 trailing `---` separator ends the findings region.
59
+ // Anything after it (the [Link to PR] line) is metadata and parsed
60
+ // by short-circuit so we don't mistake the literal `---` for a
61
+ // missing header.
62
+ if (line === '---') {
63
+ current = null;
64
+ continue;
65
+ }
66
+ // Switch active severity on every `### <emoji> <Label>` heading.
67
+ // We pattern-match on the emoji codepoint (not the label text) so a
68
+ // future SPEC tweak to "Critical" → "Blocking" doesn't break us.
69
+ if (line.startsWith('### ')) {
70
+ if (line.includes(SEVERITY_EMOJI.critical)) {
71
+ current = 'critical';
72
+ }
73
+ else if (line.includes(SEVERITY_EMOJI.minor)) {
74
+ current = 'minor';
75
+ }
76
+ else if (line.includes(SEVERITY_EMOJI.preexisting)) {
77
+ current = 'preexisting';
78
+ }
79
+ else {
80
+ current = null;
81
+ }
82
+ continue;
83
+ }
84
+ // Resolved / Still-open blocks under SPEC §1.8.1 — these list
85
+ // findings from PRIOR rounds, NOT this-round findings. Skip them
86
+ // so a multi-round PR doesn't double-count its own history.
87
+ // (`**Resolved this round:**` / `**Still open:**` headings.)
88
+ if (line.startsWith('**Resolved this round:') || line.startsWith('**Still open:')) {
89
+ current = null;
90
+ continue;
91
+ }
92
+ if (current == null)
93
+ continue;
94
+ // SPEC §1.8.1 bullet: `- **<file>:<line>** — <skill>: <summary>`
95
+ // OR `- **<file>** — <skill>: <summary>`
96
+ // OR `- **(unknown file)** — <skill>: <summary>`
97
+ //
98
+ // The multi-pass renderer prepends `[Pass N — Role · model]` to the
99
+ // bullet; we accept that prefix and discard it for parsing.
100
+ const parsed = parseFindingBullet(line);
101
+ if (parsed) {
102
+ out.push({ ...parsed, severity: current });
103
+ }
104
+ }
105
+ if (out.length === 0)
106
+ return null;
107
+ return { findings: out };
108
+ }
109
+ /**
110
+ * Diff prior vs current.
111
+ *
112
+ * `resolvedFindings`: findings that appeared in `prior` but NOT in
113
+ * `current`. The PR author (or auto-fix) addressed them.
114
+ *
115
+ * `stillOpenFindings`: findings that appeared in BOTH `prior` and
116
+ * `current`. These are persistent — the PR author hasn't fixed them
117
+ * (or the bot still considers them findings post-fix-push).
118
+ *
119
+ * Findings unique to `current` (newly raised this round) appear in
120
+ * neither list — those are surfaced directly by the renderer's normal
121
+ * severity-bucket emission.
122
+ *
123
+ * Order is preserved from `prior` for stability across rounds.
124
+ */
125
+ export function diffFindings(prior, current) {
126
+ if (prior === null || prior.findings.length === 0) {
127
+ return { resolvedFindings: [], stillOpenFindings: [] };
128
+ }
129
+ // Build identity set for the current round. We do not need the
130
+ // current-round ParsedFinding objects — we only need to know which
131
+ // identities are still present.
132
+ //
133
+ // Cast through `Review` shape — the schema's three arrays mirror our
134
+ // input slice exactly (skill / file / line / summary), so we can lean
135
+ // on `flattenFindings` to produce a tagged list.
136
+ const currentReview = {
137
+ status_header: 'clean',
138
+ summary_counts: {
139
+ critical: 0,
140
+ minor: 0,
141
+ preexisting: 0,
142
+ resolved_from_prior: 0,
143
+ still_open: 0,
144
+ },
145
+ per_skill_scan: [],
146
+ critical_findings: current.critical_findings ?? [],
147
+ minor_findings: current.minor_findings ?? [],
148
+ preexisting_findings: current.preexisting_findings ?? [],
149
+ skills_referenced: [],
150
+ last_reviewed_sha: '',
151
+ };
152
+ const currentFlat = flattenFindings(currentReview);
153
+ const currentIds = new Set(currentFlat.map((f) => findingIdentity({
154
+ file: f.file ?? '(unknown file)',
155
+ line: f.line ?? 0,
156
+ severity: f.severity,
157
+ skillName: f.skill,
158
+ summary: f.summary,
159
+ })));
160
+ const resolved = [];
161
+ const stillOpen = [];
162
+ for (const f of prior.findings) {
163
+ const id = findingIdentity(f);
164
+ if (currentIds.has(id)) {
165
+ stillOpen.push(f);
166
+ }
167
+ else {
168
+ resolved.push(f);
169
+ }
170
+ }
171
+ return { resolvedFindings: resolved, stillOpenFindings: stillOpen };
172
+ }
173
+ /**
174
+ * Stable identity for a finding. Used for diffing prior vs current
175
+ * across review rounds AND (by intention) shareable with the
176
+ * clud-bug-app inline-thread anchor hash so future cross-feature
177
+ * surfaces (e.g. "the auto-fix that resolved this thread also resolves
178
+ * this doc-file finding") align without re-computing.
179
+ *
180
+ * Exposed for tests + downstream callers that want to align their own
181
+ * finding storage on the same scheme.
182
+ */
183
+ export function findingIdentity(f) {
184
+ // Truncate summary to 100 chars to absorb whitespace/punctuation
185
+ // drift between rounds without losing discrimination between
186
+ // genuinely-distinct findings (the SPEC §1.8.1 summary line is
187
+ // user-visible so it tends to be stable; 100 chars is enough for any
188
+ // realistic distinct summary while tolerating "fix the X" → "fix X"
189
+ // drift).
190
+ const summaryPart = f.summary.slice(0, 100);
191
+ return `${f.file}:${f.line}:${f.severity}:${f.skillName}:${summaryPart}`;
192
+ }
193
+ // ---------------------------------------------------------------------------
194
+ // Parsing internals
195
+ // ---------------------------------------------------------------------------
196
+ /**
197
+ * Parse one SPEC §1.8.1 bullet line into a {file, line, skill, summary}
198
+ * tuple. Returns null for any line that doesn't match the SPEC shape.
199
+ *
200
+ * Accepts both shapes:
201
+ * `- **<file>:<line>** — <skill>: <summary>`
202
+ * `- **<file>** — <skill>: <summary>`
203
+ *
204
+ * Also tolerates the multi-pass attribution prefix (D.2.5):
205
+ * `- [Pass 1 — Role · model] **<file>:<line>** — <skill>: <summary>`
206
+ *
207
+ * Em-dash recognition: the SPEC pins U+2014 EM DASH between location
208
+ * and skill. Some downstream tools have been observed using `--` or
209
+ * regular hyphens; we accept both for resilience.
210
+ */
211
+ function parseFindingBullet(line) {
212
+ // Strip the leading `- ` bullet marker.
213
+ if (!line.startsWith('- '))
214
+ return null;
215
+ let rest = line.slice(2);
216
+ // Strip optional `[Pass N — ...] ` attribution prefix (D.2.5).
217
+ if (rest.startsWith('[')) {
218
+ const closeIdx = rest.indexOf('] ');
219
+ if (closeIdx === -1)
220
+ return null;
221
+ rest = rest.slice(closeIdx + 2);
222
+ }
223
+ // Expect `**<location>** ` next.
224
+ if (!rest.startsWith('**'))
225
+ return null;
226
+ const locEnd = rest.indexOf('**', 2);
227
+ if (locEnd === -1)
228
+ return null;
229
+ const location = rest.slice(2, locEnd);
230
+ rest = rest.slice(locEnd + 2);
231
+ // Strip the location separator. SPEC pins ` — ` (U+2014 surrounded by
232
+ // spaces). We accept hyphen-minus variants as a courtesy.
233
+ // The separator may be ` — `, ` -- `, or ` - `.
234
+ const sepMatch = rest.match(/^\s+(?:—|--|-)\s+/);
235
+ if (!sepMatch)
236
+ return null;
237
+ rest = rest.slice(sepMatch[0].length);
238
+ // `<skill>: <summary>` — split on the first `: `.
239
+ const sepIdx = rest.indexOf(': ');
240
+ if (sepIdx === -1)
241
+ return null;
242
+ const skillName = rest.slice(0, sepIdx).trim();
243
+ const summary = rest.slice(sepIdx + 2).trim();
244
+ if (skillName === '' || summary === '')
245
+ return null;
246
+ // Split location into file + optional line. Walk RIGHTWARD from the
247
+ // last `:` so file names containing colons (Windows-style, rare) on
248
+ // the LHS don't confuse us.
249
+ const colonIdx = location.lastIndexOf(':');
250
+ let file = location;
251
+ let lineNum = 0;
252
+ if (colonIdx !== -1) {
253
+ const tail = location.slice(colonIdx + 1);
254
+ if (/^\d+$/.test(tail)) {
255
+ file = location.slice(0, colonIdx);
256
+ lineNum = Number(tail);
257
+ }
258
+ }
259
+ return { file, line: lineNum, skillName, summary };
260
+ }
261
+ //# sourceMappingURL=diff-findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-findings.js","sourceRoot":"","sources":["../../src/core/diff-findings.ts"],"names":[],"mappings":"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"}