aiwcli 0.12.1 → 0.12.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.
- package/dist/templates/_shared/hooks-ts/session_start.ts +21 -15
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +20 -8
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +151 -29
- package/dist/templates/_shared/scripts/resume_handoff.ts +25 -0
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -7
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-EVOLUTION.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-PATTERNS.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-STRUCTURE.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ASSUMPTION-TRACER.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CLARITY-AUDITOR.md +53 -54
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-GAPS.md +70 -71
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-ORDERING.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -73
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -65
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DEVILS-ADVOCATE.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -87
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HANDOFF-READINESS.md +59 -60
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -59
- package/dist/templates/cc-native/_cc-native/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-DEPENDENCY.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-FMEA.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-PREMORTEM.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-REVERSIBILITY.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SCOPE-BOUNDARY.md +77 -78
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SKEPTIC.md +68 -69
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-COSTS.md +67 -68
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -66
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-COVERAGE.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-STRENGTH.md +69 -70
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +19 -2
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +28 -1010
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +1 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -821
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +36 -13
- package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +1 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +489 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +51 -17
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +40 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,823 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Review artifact writing and formatting.
|
|
3
|
-
*
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Markdown Formatting
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Format review results as markdown (legacy compat format).
|
|
28
|
-
*/
|
|
29
|
-
export function formatReviewMarkdown(
|
|
30
|
-
results: ReviewerResult[],
|
|
31
|
-
overall: string,
|
|
32
|
-
title = "CC-Native Plan Review",
|
|
33
|
-
settings?: Record<string, unknown>,
|
|
34
|
-
): string {
|
|
35
|
-
const display = resolveDisplay(settings);
|
|
36
|
-
|
|
37
|
-
const lines: string[] = [];
|
|
38
|
-
lines.push(`# ${title}\n`);
|
|
39
|
-
lines.push(`**Overall verdict:** \`${overall.toUpperCase()}\`\n`);
|
|
40
|
-
|
|
41
|
-
for (const r of results) {
|
|
42
|
-
const displayName = r.name === r.name.toLowerCase() ? titleCase(r.name) : r.name;
|
|
43
|
-
lines.push(`## ${displayName}\n`);
|
|
44
|
-
lines.push(`- ok: \`${r.ok}\``);
|
|
45
|
-
lines.push(`- verdict: \`${r.verdict}\``);
|
|
46
|
-
|
|
47
|
-
if (r.data && Object.keys(r.data).length > 0) {
|
|
48
|
-
const summary = String(r.data.summary ?? "").trim();
|
|
49
|
-
if (r.data.summary_source === "default") {
|
|
50
|
-
lines.push(`- summary: ⚠️ ${summary} *(reviewer did not return summary)*`);
|
|
51
|
-
} else {
|
|
52
|
-
lines.push(`- summary: ${summary}`);
|
|
53
|
-
}
|
|
54
|
-
appendReviewDetails(lines, r.data, display);
|
|
55
|
-
} else {
|
|
56
|
-
lines.push(`- note: ${r.err || "no structured output"}`);
|
|
57
|
-
}
|
|
58
|
-
lines.push("");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return lines.join("\n").trim() + "\n";
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Format combined review result as a single markdown document.
|
|
66
|
-
*/
|
|
67
|
-
export function formatCombinedMarkdown(
|
|
68
|
-
result: CombinedReviewResult,
|
|
69
|
-
settings?: Record<string, unknown>,
|
|
70
|
-
corroboration?: CorroborationResult,
|
|
71
|
-
): string {
|
|
72
|
-
const display = resolveDisplay(settings);
|
|
73
|
-
|
|
74
|
-
const lines: string[] = [];
|
|
75
|
-
lines.push("# CC-Native Plan Review\n");
|
|
76
|
-
lines.push(`**Overall Verdict:** \`${result.overall_verdict.toUpperCase()}\``);
|
|
77
|
-
lines.push(`**Plan Hash:** \`${result.plan_hash}\`\n`);
|
|
78
|
-
|
|
79
|
-
// Corroboration summary
|
|
80
|
-
if (corroboration) {
|
|
81
|
-
lines.push("## Corroboration Analysis\n");
|
|
82
|
-
if (corroboration.blocking.length > 0) {
|
|
83
|
-
lines.push("### Blocking Dimensions\n");
|
|
84
|
-
for (const group of corroboration.blocking) {
|
|
85
|
-
lines.push(`- **${group.dimension}**: ${group.issues.length} issues from ${group.agentCount} agents (threshold: ≥${group.threshold})`);
|
|
86
|
-
}
|
|
87
|
-
lines.push("");
|
|
88
|
-
}
|
|
89
|
-
if (corroboration.solo.length > 0) {
|
|
90
|
-
lines.push("### Solo Dimensions (informational)\n");
|
|
91
|
-
for (const s of corroboration.solo) {
|
|
92
|
-
lines.push(`- **${s.dimension}**: ${s.issues.length} issues from ${s.agentCount} agents (threshold: >${s.threshold}, not exceeded)`);
|
|
93
|
-
}
|
|
94
|
-
lines.push("");
|
|
95
|
-
}
|
|
96
|
-
if (corroboration.unclassified.length > 0) {
|
|
97
|
-
lines.push(`> ${corroboration.unclassified.length} issue(s) without dimension classification (unclassified, not blocking)\n`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
lines.push("---\n");
|
|
102
|
-
|
|
103
|
-
// Orchestration section
|
|
104
|
-
if (result.orchestration) {
|
|
105
|
-
lines.push("---\n");
|
|
106
|
-
lines.push("## Orchestration\n");
|
|
107
|
-
lines.push(`- **Complexity:** \`${result.orchestration.complexity}\``);
|
|
108
|
-
lines.push(`- **Category:** \`${result.orchestration.category}\``);
|
|
109
|
-
const agentsStr =
|
|
110
|
-
result.orchestration.selected_agents.length > 0
|
|
111
|
-
? result.orchestration.selected_agents.join(", ")
|
|
112
|
-
: "None";
|
|
113
|
-
lines.push(`- **Agents Selected:** ${agentsStr}`);
|
|
114
|
-
lines.push(`- **Reasoning:** ${result.orchestration.reasoning}`);
|
|
115
|
-
if (result.orchestration.skip_reason) {
|
|
116
|
-
lines.push(`- **Skip Reason:** ${result.orchestration.skip_reason}`);
|
|
117
|
-
}
|
|
118
|
-
if (result.orchestration.error) {
|
|
119
|
-
lines.push(`- **Error:** ${result.orchestration.error}`);
|
|
120
|
-
}
|
|
121
|
-
lines.push("");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Agent Reviews section
|
|
125
|
-
if (Object.keys(result.agents).length > 0) {
|
|
126
|
-
lines.push("---\n");
|
|
127
|
-
lines.push("## Agent Reviews\n");
|
|
128
|
-
for (const [name, r] of Object.entries(result.agents)) {
|
|
129
|
-
lines.push(`### ${name}\n`);
|
|
130
|
-
lines.push(`- verdict: \`${r.verdict}\``);
|
|
131
|
-
if (r.data && Object.keys(r.data).length > 0) {
|
|
132
|
-
appendSummaryLine(lines, r.data);
|
|
133
|
-
appendReviewDetails(lines, r.data, display);
|
|
134
|
-
} else if (r.err) {
|
|
135
|
-
lines.push(`- error: ${r.err}`);
|
|
136
|
-
}
|
|
137
|
-
lines.push("");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return lines.join("\n").trim() + "\n";
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
// Inline Summaries
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Build compact inline summary of HIGH-severity findings for additionalContext.
|
|
150
|
-
* When corroboration data is provided, annotates issues as [CORROBORATED] or [perspective].
|
|
151
|
-
*/
|
|
152
|
-
export function buildInlineReviewSummary(
|
|
153
|
-
combined: CombinedReviewResult,
|
|
154
|
-
maxIssues = 5,
|
|
155
|
-
maxChars = 800,
|
|
156
|
-
corroboration?: CorroborationResult,
|
|
157
|
-
): string {
|
|
158
|
-
const allReviewers = Object.values(combined.agents);
|
|
159
|
-
|
|
160
|
-
// Build set of blocking dimensions for annotation
|
|
161
|
-
const blockingDims = new Set(
|
|
162
|
-
corroboration?.blocking.map(g => g.dimension) ?? [],
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const highIssues: Array<Record<string, unknown>> = [];
|
|
166
|
-
for (const r of allReviewers) {
|
|
167
|
-
if (!r.data) continue;
|
|
168
|
-
const issues = r.data.issues as Array<Record<string, unknown>> | undefined;
|
|
169
|
-
if (!issues) continue;
|
|
170
|
-
for (const issue of issues) {
|
|
171
|
-
if (issue.severity === "high") {
|
|
172
|
-
highIssues.push({ ...issue, _reviewer: r.name });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const parts: string[] = [];
|
|
178
|
-
|
|
179
|
-
// Overall verdict line
|
|
180
|
-
const issueCount = highIssues.length;
|
|
181
|
-
const countSuffix =
|
|
182
|
-
issueCount > 0
|
|
183
|
-
? ` (${issueCount} high-severity issue${issueCount !== 1 ? "s" : ""})`
|
|
184
|
-
: "";
|
|
185
|
-
parts.push(`**Plan Review: ${combined.overall_verdict.toUpperCase()}**${countSuffix}`);
|
|
186
|
-
|
|
187
|
-
// Corroboration summary if available
|
|
188
|
-
if (corroboration) {
|
|
189
|
-
const blockCount = corroboration.blocking.length;
|
|
190
|
-
const soloCount = corroboration.solo.length;
|
|
191
|
-
if (blockCount > 0) {
|
|
192
|
-
parts.push(`**Corroboration:** ${blockCount} dimension${blockCount !== 1 ? "s" : ""} exceeded threshold (blocking), ${soloCount} solo (informational)`);
|
|
193
|
-
} else {
|
|
194
|
-
parts.push(`**Corroboration:** No dimensions exceeded threshold — all ${soloCount} solo (informational)`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// High-severity issue bullets
|
|
199
|
-
for (const issue of highIssues.slice(0, maxIssues)) {
|
|
200
|
-
const cat = (issue.category as string) ?? "general";
|
|
201
|
-
const text = (issue.issue as string) ?? "";
|
|
202
|
-
const fix = (issue.suggested_fix as string) ?? "";
|
|
203
|
-
const reviewer = (issue._reviewer as string) ?? "unknown";
|
|
204
|
-
const dim = issue.dimension as string | undefined;
|
|
205
|
-
|
|
206
|
-
let annotation = "";
|
|
207
|
-
if (corroboration && dim) {
|
|
208
|
-
const group = corroboration.blocking.find(g => g.dimension === dim);
|
|
209
|
-
if (group) {
|
|
210
|
-
annotation = ` [CORROBORATED — ${group.issues.length} issues from ${group.agentCount} agents exceeds threshold ${group.threshold}]`;
|
|
211
|
-
} else {
|
|
212
|
-
annotation = " [perspective]";
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
let line = `- [${cat}] ${text}`;
|
|
217
|
-
if (fix) line += ` \u2192 ${fix}`;
|
|
218
|
-
line += ` (${reviewer})${annotation}`;
|
|
219
|
-
parts.push(line);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const remaining = highIssues.length - maxIssues;
|
|
223
|
-
if (remaining > 0) {
|
|
224
|
-
parts.push(` ...and ${remaining} more`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
let result = parts.join("\n");
|
|
228
|
-
if (result.length > maxChars) {
|
|
229
|
-
result = result.slice(0, maxChars - 3) + "...";
|
|
230
|
-
}
|
|
231
|
-
return result;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Extract top issues as compact text for permissionDecisionReason.
|
|
236
|
-
*/
|
|
237
|
-
export function extractTopIssuesText(
|
|
238
|
-
combined: CombinedReviewResult,
|
|
239
|
-
maxCount = 3,
|
|
240
|
-
severity = "high",
|
|
241
|
-
): string {
|
|
242
|
-
const allReviewers = Object.values(combined.agents);
|
|
243
|
-
|
|
244
|
-
const issues: string[] = [];
|
|
245
|
-
for (const r of allReviewers) {
|
|
246
|
-
if (!r.data) continue;
|
|
247
|
-
const issueList = r.data.issues as Array<Record<string, unknown>> | undefined;
|
|
248
|
-
if (!issueList) continue;
|
|
249
|
-
for (const issue of issueList) {
|
|
250
|
-
if (issue.severity === severity) {
|
|
251
|
-
const text = String(issue.issue ?? "").trim();
|
|
252
|
-
if (text) {
|
|
253
|
-
issues.push(`[${r.name}] ${text}`);
|
|
254
|
-
break; // first high issue per reviewer only
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (issues.length >= maxCount) break;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (issues.length === 0) return "Review found critical issues";
|
|
262
|
-
return issues.join("; ");
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Build markdown document containing high-severity issues.
|
|
267
|
-
* When corroboration data is provided, only includes corroborated (blocking) issues.
|
|
268
|
-
*/
|
|
269
|
-
export function buildHighIssuesDocument(
|
|
270
|
-
combined: CombinedReviewResult,
|
|
271
|
-
corroboration?: CorroborationResult,
|
|
272
|
-
): string {
|
|
273
|
-
// When corroboration is available, only show blocking groups
|
|
274
|
-
if (corroboration && corroboration.blocking.length > 0) {
|
|
275
|
-
const lines = ["# Corroborated High-Severity Issues\n"];
|
|
276
|
-
lines.push("> Only issues from dimensions where the total count exceeded the proportional threshold are shown.\n");
|
|
277
|
-
|
|
278
|
-
for (const group of corroboration.blocking) {
|
|
279
|
-
lines.push(`## ${group.dimension} (${group.issues.length} issues from ${group.agentCount} agents, threshold: ${group.threshold})\n`);
|
|
280
|
-
for (const { agent, issue } of group.issues) {
|
|
281
|
-
const cat = issue.category ?? "general";
|
|
282
|
-
const text = String(issue.issue ?? "").trim();
|
|
283
|
-
const fix = String(issue.suggested_fix ?? "").trim();
|
|
284
|
-
lines.push(`- **[${cat}]** ${text} *(${agent})*`);
|
|
285
|
-
if (fix) lines.push(` - Fix: ${fix}`);
|
|
286
|
-
}
|
|
287
|
-
lines.push("");
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (corroboration.solo.length > 0) {
|
|
291
|
-
lines.push("---\n");
|
|
292
|
-
lines.push(`> ${corroboration.solo.length} dimension${corroboration.solo.length !== 1 ? "s" : ""} had issues below threshold (not blocking): ${corroboration.solo.map(s => `${s.dimension} (${s.issues.length}/${s.threshold})`).join(", ")}\n`);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return lines.join("\n");
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Fallback: no corroboration data — show all high-severity issues
|
|
299
|
-
const lines = ["# High-Severity Issues\n"];
|
|
300
|
-
const allReviewers = Object.values(combined.agents);
|
|
301
|
-
|
|
302
|
-
let foundAny = false;
|
|
303
|
-
for (const r of allReviewers) {
|
|
304
|
-
if (!r.data) continue;
|
|
305
|
-
const issues = r.data.issues as Array<Record<string, unknown>> | undefined;
|
|
306
|
-
if (!issues) continue;
|
|
307
|
-
|
|
308
|
-
const highIssues = issues.filter((i) => i.severity === "high");
|
|
309
|
-
if (highIssues.length === 0) continue;
|
|
310
|
-
|
|
311
|
-
foundAny = true;
|
|
312
|
-
lines.push(`## ${r.name} (${r.verdict})\n`);
|
|
313
|
-
for (const issue of highIssues) {
|
|
314
|
-
const cat = (issue.category as string) ?? "general";
|
|
315
|
-
const text = String(issue.issue ?? "").trim();
|
|
316
|
-
const fix = String(issue.suggested_fix ?? "").trim();
|
|
317
|
-
lines.push(`- **[${cat}]** ${text}`);
|
|
318
|
-
if (fix) lines.push(` - Fix: ${fix}`);
|
|
319
|
-
}
|
|
320
|
-
lines.push("");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!foundAny) {
|
|
324
|
-
lines.push("No high-severity issues found.\n");
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return lines.join("\n");
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ---------------------------------------------------------------------------
|
|
331
|
-
// Index Generation
|
|
332
|
-
// ---------------------------------------------------------------------------
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Generate index.md for a review folder.
|
|
336
|
-
*/
|
|
337
|
-
export function generateReviewIndex(
|
|
338
|
-
result: CombinedReviewResult,
|
|
339
|
-
iteration?: number,
|
|
340
|
-
_settings?: Record<string, unknown>,
|
|
341
|
-
): string {
|
|
342
|
-
const now = new Date();
|
|
343
|
-
|
|
344
|
-
const lines = [
|
|
345
|
-
"---",
|
|
346
|
-
`type: review`,
|
|
347
|
-
`plan_hash: ${result.plan_hash}`,
|
|
348
|
-
`overall_verdict: ${result.overall_verdict}`,
|
|
349
|
-
`created_at: ${result.timestamp}`,
|
|
350
|
-
];
|
|
351
|
-
if (iteration) lines.push(`iteration: ${iteration}`);
|
|
352
|
-
lines.push(
|
|
353
|
-
"---",
|
|
354
|
-
"",
|
|
355
|
-
`# Plan Review - ${formatDate(now)}`,
|
|
356
|
-
"",
|
|
357
|
-
`**Overall Verdict:** \`${result.overall_verdict.toUpperCase()}\``,
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
if (iteration) lines.push(`**Iteration:** ${iteration}`);
|
|
361
|
-
lines.push(`**Plan Hash:** \`${result.plan_hash}\``, "");
|
|
362
|
-
|
|
363
|
-
// Summary from orchestrator
|
|
364
|
-
if (result.orchestration) {
|
|
365
|
-
lines.push(
|
|
366
|
-
"## Analysis",
|
|
367
|
-
`- **Complexity:** \`${result.orchestration.complexity}\``,
|
|
368
|
-
`- **Category:** \`${result.orchestration.category}\``,
|
|
369
|
-
`- **Reasoning:** ${result.orchestration.reasoning}`,
|
|
370
|
-
"",
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Navigation table
|
|
375
|
-
lines.push(
|
|
376
|
-
"## Review Files",
|
|
377
|
-
"",
|
|
378
|
-
"| File | Description |",
|
|
379
|
-
"|------|-------------|",
|
|
380
|
-
"| [review.md](./review.md) | Full review details |",
|
|
381
|
-
"| [review.json](./review.json) | Structured review data |",
|
|
382
|
-
"| [plan.md](./plan.md) | Plan snapshot at review time |",
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
for (const name of Object.keys(result.agents)) {
|
|
386
|
-
const safeName = sanitizeFilename(name);
|
|
387
|
-
lines.push(
|
|
388
|
-
`| [${safeName}.json](./reviewer-output/${safeName}.json) | ${name} agent output |`,
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
lines.push(
|
|
393
|
-
"",
|
|
394
|
-
"## Verdicts Summary",
|
|
395
|
-
"",
|
|
396
|
-
"| Reviewer | Verdict |",
|
|
397
|
-
"|----------|---------|",
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
for (const [name, r] of Object.entries(result.agents)) {
|
|
401
|
-
lines.push(`| ${name} | \`${r.verdict}\` |`);
|
|
402
|
-
}
|
|
403
|
-
lines.push("");
|
|
404
|
-
|
|
405
|
-
return lines.join("\n");
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// ---------------------------------------------------------------------------
|
|
409
|
-
// JSON Output
|
|
410
|
-
// ---------------------------------------------------------------------------
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Build combined JSON output structure.
|
|
414
|
-
*/
|
|
415
|
-
export function buildCombinedJson(
|
|
416
|
-
result: CombinedReviewResult,
|
|
417
|
-
): Record<string, unknown> {
|
|
418
|
-
const output: Record<string, unknown> = {
|
|
419
|
-
metadata: {
|
|
420
|
-
timestamp: result.timestamp,
|
|
421
|
-
plan_hash: result.plan_hash,
|
|
422
|
-
},
|
|
423
|
-
overall: {
|
|
424
|
-
verdict: result.overall_verdict,
|
|
425
|
-
},
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// Orchestration
|
|
429
|
-
if (result.orchestration) {
|
|
430
|
-
output.orchestration = {
|
|
431
|
-
complexity: result.orchestration.complexity,
|
|
432
|
-
category: result.orchestration.category,
|
|
433
|
-
selectedAgents: result.orchestration.selected_agents,
|
|
434
|
-
reasoning: result.orchestration.reasoning,
|
|
435
|
-
skipReason: result.orchestration.skip_reason ?? null,
|
|
436
|
-
error: result.orchestration.error ?? null,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Agents
|
|
441
|
-
if (Object.keys(result.agents).length > 0) {
|
|
442
|
-
const agents: Record<string, unknown> = {};
|
|
443
|
-
output.agents = agents;
|
|
444
|
-
for (const [name, r] of Object.entries(result.agents)) {
|
|
445
|
-
agents[name] = {
|
|
446
|
-
verdict: r.verdict,
|
|
447
|
-
summary: r.data?.summary ?? null,
|
|
448
|
-
summarySource: r.data?.summary_source ?? null,
|
|
449
|
-
issues: r.data
|
|
450
|
-
? ((r.data.issues as Array<Record<string, unknown>>) ?? []).filter(
|
|
451
|
-
(i) => i.severity !== "low",
|
|
452
|
-
)
|
|
453
|
-
: [],
|
|
454
|
-
missing_sections: r.data?.missing_sections ?? [],
|
|
455
|
-
questions: r.data?.questions ?? [],
|
|
456
|
-
ok: r.ok,
|
|
457
|
-
error: r.err || null,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return output;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// ---------------------------------------------------------------------------
|
|
466
|
-
// Artifact Writing
|
|
467
|
-
// ---------------------------------------------------------------------------
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Write combined review artifacts to context reviews folder.
|
|
471
|
-
* Uses atomic writes for critical files when ENABLE_ROBUST_PLAN_WRITES is true.
|
|
472
|
-
*/
|
|
473
|
-
export function writeCombinedArtifacts(
|
|
474
|
-
base: string,
|
|
475
|
-
plan: string,
|
|
476
|
-
result: CombinedReviewResult,
|
|
477
|
-
payload: Record<string, unknown>,
|
|
478
|
-
settings?: Record<string, unknown>,
|
|
479
|
-
contextReviewsDir?: string,
|
|
480
|
-
reviewFolder?: string,
|
|
481
|
-
iteration?: number,
|
|
482
|
-
corroboration?: CorroborationResult,
|
|
483
|
-
): string {
|
|
484
|
-
const outDir = reviewFolder ?? contextReviewsDir;
|
|
485
|
-
if (!outDir) {
|
|
486
|
-
throw new Error("Either contextReviewsDir or reviewFolder is required");
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
logDebug("utils", `Using review folder: ${outDir}`);
|
|
490
|
-
|
|
491
|
-
// Create directory
|
|
492
|
-
try {
|
|
493
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
494
|
-
} catch (error: unknown) {
|
|
495
|
-
logError("utils", `Cannot create directory ${outDir}: ${error}`);
|
|
496
|
-
throw error;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// JSON write
|
|
500
|
-
const jsonPath = path.join(outDir, "review.json");
|
|
501
|
-
const jsonData = buildCombinedJson(result);
|
|
502
|
-
writeFile(jsonPath, JSON.stringify(jsonData, null, 2));
|
|
503
|
-
|
|
504
|
-
// Markdown write
|
|
505
|
-
const mdPath = path.join(outDir, "review.md");
|
|
506
|
-
const mdContent = formatCombinedMarkdown(result, settings, corroboration);
|
|
507
|
-
writeFile(mdPath, mdContent);
|
|
508
|
-
|
|
509
|
-
// Individual reviewer writes (non-critical) — in reviewer-output/ subfolder
|
|
510
|
-
const reviewerOutputDir = path.join(outDir, "reviewer-output");
|
|
511
|
-
try {
|
|
512
|
-
fs.mkdirSync(reviewerOutputDir, { recursive: true });
|
|
513
|
-
} catch {
|
|
514
|
-
// Best-effort — non-critical
|
|
515
|
-
}
|
|
516
|
-
for (const [name, r] of Object.entries(result.agents)) {
|
|
517
|
-
if (r.data) {
|
|
518
|
-
writeFileNonCritical(
|
|
519
|
-
path.join(reviewerOutputDir, `${sanitizeFilename(name)}.json`),
|
|
520
|
-
JSON.stringify(r.data, null, 2),
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Generate index.md for folder-based reviews
|
|
526
|
-
if (reviewFolder) {
|
|
527
|
-
const indexContent = generateReviewIndex(result, iteration, settings);
|
|
528
|
-
writeFileNonCritical(path.join(outDir, "index.md"), indexContent);
|
|
529
|
-
return path.join(outDir, "index.md");
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return mdPath;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// ---------------------------------------------------------------------------
|
|
536
|
-
// Helpers
|
|
537
|
-
// ---------------------------------------------------------------------------
|
|
538
|
-
|
|
539
|
-
function resolveDisplay(
|
|
540
|
-
settings?: Record<string, unknown>,
|
|
541
|
-
): DisplaySettings {
|
|
542
|
-
if (!settings) return { ...DEFAULT_DISPLAY };
|
|
543
|
-
const display = (settings.display as Partial<DisplaySettings>) ?? {};
|
|
544
|
-
return { ...DEFAULT_DISPLAY, ...display };
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function appendSummaryLine(lines: string[], data: Record<string, unknown>): void {
|
|
548
|
-
const summary = String(data.summary ?? "").trim();
|
|
549
|
-
if (data.summary_source === "default") {
|
|
550
|
-
lines.push(`- summary: ⚠️ ${summary} *(reviewer did not return summary)*`);
|
|
551
|
-
} else {
|
|
552
|
-
lines.push(`- summary: ${summary}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function appendReviewDetails(
|
|
557
|
-
lines: string[],
|
|
558
|
-
data: Record<string, unknown>,
|
|
559
|
-
display: DisplaySettings,
|
|
560
|
-
): void {
|
|
561
|
-
const issues = ((data.issues as Array<Record<string, unknown>>) ?? []).filter(
|
|
562
|
-
(i) => i.severity !== "low",
|
|
563
|
-
);
|
|
564
|
-
if (issues.length > 0) {
|
|
565
|
-
lines.push("\n**Issues:**");
|
|
566
|
-
for (const it of issues.slice(0, display.maxIssues)) {
|
|
567
|
-
const sev = (it.severity as string) ?? "medium";
|
|
568
|
-
const cat = (it.category as string) ?? "general";
|
|
569
|
-
const issue = (it.issue as string) ?? "";
|
|
570
|
-
const fix = (it.suggested_fix as string) ?? "";
|
|
571
|
-
lines.push(`- **[${sev}] ${cat}**: ${issue}`);
|
|
572
|
-
if (fix) lines.push(` - fix: ${fix}`);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const missing = (data.missing_sections as string[]) ?? [];
|
|
577
|
-
if (missing.length > 0) {
|
|
578
|
-
lines.push("\n**Missing Sections:**");
|
|
579
|
-
for (const m of missing.slice(0, display.maxMissingSections)) {
|
|
580
|
-
lines.push(`- ${m}`);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const qs = (data.questions as string[]) ?? [];
|
|
585
|
-
if (qs.length > 0) {
|
|
586
|
-
lines.push("\n**Questions:**");
|
|
587
|
-
for (const q of qs.slice(0, display.maxQuestions)) {
|
|
588
|
-
lines.push(`- ${q}`);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function writeFile(filePath: string, content: string): void {
|
|
594
|
-
try {
|
|
595
|
-
if (ENABLE_ROBUST_PLAN_WRITES) {
|
|
596
|
-
const [success, error] = atomicWrite(filePath, content);
|
|
597
|
-
if (!success) throw new Error(`Atomic write failed: ${error}`);
|
|
598
|
-
} else {
|
|
599
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
600
|
-
}
|
|
601
|
-
} catch (error: unknown) {
|
|
602
|
-
logError("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
|
|
603
|
-
throw error;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function writeFileNonCritical(filePath: string, content: string): void {
|
|
608
|
-
try {
|
|
609
|
-
if (ENABLE_ROBUST_PLAN_WRITES) {
|
|
610
|
-
const [success, error] = atomicWrite(filePath, content);
|
|
611
|
-
if (!success) {
|
|
612
|
-
logWarn("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
|
|
613
|
-
}
|
|
614
|
-
} else {
|
|
615
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
616
|
-
}
|
|
617
|
-
} catch (error: unknown) {
|
|
618
|
-
logWarn("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function titleCase(s: string): string {
|
|
623
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function formatDate(d: Date): string {
|
|
627
|
-
const year = d.getFullYear();
|
|
628
|
-
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
629
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
630
|
-
const hours = String(d.getHours()).padStart(2, "0");
|
|
631
|
-
const minutes = String(d.getMinutes()).padStart(2, "0");
|
|
632
|
-
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// ---------------------------------------------------------------------------
|
|
636
|
-
// Review Tracker
|
|
637
|
-
// ---------------------------------------------------------------------------
|
|
638
|
-
|
|
639
|
-
export interface ReviewTrackerEntry {
|
|
640
|
-
iteration: number;
|
|
641
|
-
timestamp: string;
|
|
642
|
-
planHash: string;
|
|
643
|
-
verdict: string;
|
|
644
|
-
decision: string;
|
|
645
|
-
score: number;
|
|
646
|
-
topIssues: string[];
|
|
647
|
-
reviewFolder: string;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Build or update the review-tracker.md in the cc-native reviews directory.
|
|
652
|
-
* This file provides a human-readable summary of all review iterations,
|
|
653
|
-
* making it easy to see whether feedback was acted on.
|
|
654
|
-
*/
|
|
655
|
-
export function writeReviewTracker(
|
|
656
|
-
ccNativeReviewsDir: string,
|
|
657
|
-
entry: ReviewTrackerEntry,
|
|
658
|
-
): void {
|
|
659
|
-
const trackerPath = path.join(ccNativeReviewsDir, "review-tracker.md");
|
|
660
|
-
|
|
661
|
-
// Read existing tracker entries if present
|
|
662
|
-
let existingContent = "";
|
|
663
|
-
try {
|
|
664
|
-
if (fs.existsSync(trackerPath)) {
|
|
665
|
-
existingContent = fs.readFileSync(trackerPath, "utf-8");
|
|
666
|
-
}
|
|
667
|
-
} catch {
|
|
668
|
-
// Fresh start
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Parse existing entries to detect plan changes
|
|
672
|
-
const previousHashes = extractPreviousHashes(existingContent);
|
|
673
|
-
const hashChanged = previousHashes.length > 0 &&
|
|
674
|
-
previousHashes.at(-1) !== entry.planHash;
|
|
675
|
-
|
|
676
|
-
// Build the new entry section
|
|
677
|
-
const lines: string[] = [];
|
|
678
|
-
const verdictEmoji = entry.decision === "allow" ? "\u2705" : "\u274C";
|
|
679
|
-
const changeNote = previousHashes.length > 0
|
|
680
|
-
? (hashChanged ? "\u2705 Plan was revised (hash changed)" : "\u26A0\uFE0F Plan unchanged since last review")
|
|
681
|
-
: "Initial review";
|
|
682
|
-
|
|
683
|
-
lines.push(`## Iteration ${entry.iteration} \u2014 ${entry.timestamp} \u2014 ${verdictEmoji} ${entry.verdict.toUpperCase()}`);
|
|
684
|
-
lines.push("");
|
|
685
|
-
lines.push(`- **Decision:** ${entry.decision}`);
|
|
686
|
-
lines.push(`- **Score:** ${entry.score.toFixed(2)}`);
|
|
687
|
-
lines.push(`- **Plan hash:** \`${entry.planHash}\``);
|
|
688
|
-
lines.push(`- **Status:** ${changeNote}`);
|
|
689
|
-
lines.push(`- **Full review:** [\`${path.basename(entry.reviewFolder)}/\`](${path.basename(entry.reviewFolder)}/index.md)`);
|
|
690
|
-
|
|
691
|
-
if (entry.topIssues.length > 0) {
|
|
692
|
-
lines.push("");
|
|
693
|
-
lines.push("**Top issues:**");
|
|
694
|
-
for (const issue of entry.topIssues) {
|
|
695
|
-
lines.push(`- ${issue}`);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
lines.push("");
|
|
699
|
-
|
|
700
|
-
// Build full file
|
|
701
|
-
let output: string;
|
|
702
|
-
if (!existingContent || !existingContent.includes("# Plan Review Tracker")) {
|
|
703
|
-
// New file
|
|
704
|
-
output = [
|
|
705
|
-
"# Plan Review Tracker",
|
|
706
|
-
"",
|
|
707
|
-
"> Auto-generated by plan review hook. Shows review lifecycle across iterations.",
|
|
708
|
-
"> Check `plan.md` in each iteration folder to diff plan changes.",
|
|
709
|
-
"",
|
|
710
|
-
...lines,
|
|
711
|
-
].join("\n");
|
|
712
|
-
} else {
|
|
713
|
-
// Append to existing
|
|
714
|
-
output = existingContent.trimEnd() + "\n\n" + lines.join("\n");
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
try {
|
|
718
|
-
fs.writeFileSync(trackerPath, output, "utf-8");
|
|
719
|
-
} catch (error) {
|
|
720
|
-
logWarn("artifacts", `Failed to write review tracker: ${error}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function extractPreviousHashes(content: string): string[] {
|
|
725
|
-
const hashes: string[] = [];
|
|
726
|
-
const regex = /\*\*Plan hash:\*\* `([a-f0-9]+)`/g;
|
|
727
|
-
let match: RegExpExecArray | null;
|
|
728
|
-
while ((match = regex.exec(content)) !== null) {
|
|
729
|
-
hashes.push(match[1]!);
|
|
730
|
-
}
|
|
731
|
-
return hashes;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// ---------------------------------------------------------------------------
|
|
735
|
-
// Corroboration Report
|
|
736
|
-
// ---------------------------------------------------------------------------
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* Build a detailed markdown report of the corroboration analysis.
|
|
740
|
-
* Shows blocking vs solo findings with threshold comparison.
|
|
741
|
-
*/
|
|
742
|
-
export function buildCorroborationReport(
|
|
743
|
-
corroborationResult: CorroborationResult,
|
|
744
|
-
): string {
|
|
745
|
-
const lines: string[] = [
|
|
746
|
-
"# Corroboration Analysis",
|
|
747
|
-
"",
|
|
748
|
-
"## Verdict: " + corroborationResult.verdict.toUpperCase(),
|
|
749
|
-
"",
|
|
750
|
-
];
|
|
751
|
-
|
|
752
|
-
// Blocking groups table
|
|
753
|
-
if (corroborationResult.blocking.length > 0) {
|
|
754
|
-
lines.push("## Blocking Issues (Corroborated)");
|
|
755
|
-
lines.push("");
|
|
756
|
-
lines.push("| Dimension | Issues | Agents | Threshold | Status |");
|
|
757
|
-
lines.push("|-----------|--------|--------|-----------|--------|");
|
|
758
|
-
|
|
759
|
-
for (const group of corroborationResult.blocking) {
|
|
760
|
-
lines.push(
|
|
761
|
-
`| ${group.dimension} | ${group.issues.length} | ${group.agentCount} | ${group.threshold} | ⛔ EXCEEDED |`
|
|
762
|
-
);
|
|
763
|
-
}
|
|
764
|
-
lines.push("");
|
|
765
|
-
|
|
766
|
-
// Issue details
|
|
767
|
-
for (const group of corroborationResult.blocking) {
|
|
768
|
-
lines.push(`### ${group.dimension} (${group.issues.length} issues)`);
|
|
769
|
-
lines.push("");
|
|
770
|
-
for (const {agent, issue} of group.issues) {
|
|
771
|
-
lines.push(`- **[${agent}]** ${issue.description || issue.issue || "No description"}`);
|
|
772
|
-
}
|
|
773
|
-
lines.push("");
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// Solo findings table
|
|
778
|
-
if (corroborationResult.solo.length > 0) {
|
|
779
|
-
lines.push("## Solo Findings (Below Threshold)");
|
|
780
|
-
lines.push("");
|
|
781
|
-
lines.push("| Dimension | Issues | Agents | Threshold | Status |");
|
|
782
|
-
lines.push("|-----------|--------|--------|-----------|--------|");
|
|
783
|
-
|
|
784
|
-
for (const group of corroborationResult.solo) {
|
|
785
|
-
lines.push(
|
|
786
|
-
`| ${group.dimension} | ${group.issues.length} | ${group.agentCount} | ${group.threshold} | ℹ️ SOLO |`
|
|
787
|
-
);
|
|
788
|
-
}
|
|
789
|
-
lines.push("");
|
|
790
|
-
|
|
791
|
-
// Issue details
|
|
792
|
-
for (const group of corroborationResult.solo) {
|
|
793
|
-
lines.push(`### ${group.dimension} (${group.issues.length} issues)`);
|
|
794
|
-
lines.push("");
|
|
795
|
-
for (const {agent, issue} of group.issues) {
|
|
796
|
-
lines.push(`- **[${agent}]** ${issue.description || issue.issue || "No description"}`);
|
|
797
|
-
}
|
|
798
|
-
lines.push("");
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// Unclassified issues
|
|
803
|
-
if (corroborationResult.unclassified.length > 0) {
|
|
804
|
-
lines.push("## Unclassified Issues (No Dimension)");
|
|
805
|
-
lines.push("");
|
|
806
|
-
for (const {agent, issue} of corroborationResult.unclassified) {
|
|
807
|
-
lines.push(`- **[${agent}]** ${issue.description || issue.issue || "No description"}`);
|
|
808
|
-
}
|
|
809
|
-
lines.push("");
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Summary
|
|
813
|
-
lines.push("## Summary");
|
|
814
|
-
lines.push("");
|
|
815
|
-
lines.push(`- **Blocking groups**: ${corroborationResult.blocking.length}`);
|
|
816
|
-
lines.push(`- **Solo findings**: ${corroborationResult.solo.length}`);
|
|
817
|
-
lines.push(`- **Unclassified**: ${corroborationResult.unclassified.length}`);
|
|
818
|
-
lines.push(`- **Final verdict**: ${corroborationResult.verdict}`);
|
|
819
|
-
lines.push("");
|
|
820
|
-
lines.push("**Threshold rule**: Issues in a dimension block when count ≥ 2× distinct agents in that dimension.");
|
|
821
|
-
|
|
822
|
-
return lines.join("\n");
|
|
823
|
-
}
|
|
3
|
+
* Re-exports from artifacts/ subdirectory for backward compatibility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
formatReviewMarkdown,
|
|
8
|
+
formatCombinedMarkdown,
|
|
9
|
+
buildInlineReviewSummary,
|
|
10
|
+
extractTopIssuesText,
|
|
11
|
+
buildHighIssuesDocument,
|
|
12
|
+
buildCorroborationReport,
|
|
13
|
+
generateReviewIndex,
|
|
14
|
+
buildCombinedJson,
|
|
15
|
+
writeCombinedArtifacts,
|
|
16
|
+
writeFile,
|
|
17
|
+
writeFileNonCritical,
|
|
18
|
+
writeReviewTracker,
|
|
19
|
+
extractPreviousHashes,
|
|
20
|
+
} from "./artifacts/index.js";
|
|
21
|
+
export type { ReviewTrackerEntry } from "./artifacts/index.js";
|