@cjhyy/code-shell-arena 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/arena.d.ts +45 -0
- package/dist/arena.js +362 -0
- package/dist/capability.d.ts +43 -0
- package/dist/capability.js +453 -0
- package/dist/context/context-tools.d.ts +16 -0
- package/dist/context/context-tools.js +272 -0
- package/dist/context/within-root.d.ts +7 -0
- package/dist/context/within-root.js +15 -0
- package/dist/detect-mode.d.ts +20 -0
- package/dist/detect-mode.js +78 -0
- package/dist/digest-builder.d.ts +25 -0
- package/dist/digest-builder.js +120 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +23 -0
- package/dist/index.runtime.d.ts +13 -0
- package/dist/index.runtime.js +11 -0
- package/dist/iterate/convergence.d.ts +25 -0
- package/dist/iterate/convergence.js +103 -0
- package/dist/iterate/formats/index.d.ts +22 -0
- package/dist/iterate/formats/index.js +283 -0
- package/dist/iterate/index.d.ts +11 -0
- package/dist/iterate/index.js +9 -0
- package/dist/iterate/iterative-arena.d.ts +31 -0
- package/dist/iterate/iterative-arena.js +252 -0
- package/dist/iterate/parse.d.ts +42 -0
- package/dist/iterate/parse.js +123 -0
- package/dist/iterate/phases/argue.d.ts +22 -0
- package/dist/iterate/phases/argue.js +165 -0
- package/dist/iterate/phases/revise.d.ts +16 -0
- package/dist/iterate/phases/revise.js +62 -0
- package/dist/iterate/phases/tournament.d.ts +34 -0
- package/dist/iterate/phases/tournament.js +113 -0
- package/dist/iterate/tools/web-tools.d.ts +13 -0
- package/dist/iterate/tools/web-tools.js +54 -0
- package/dist/iterate/types.d.ts +152 -0
- package/dist/iterate/types.js +8 -0
- package/dist/ledger.d.ts +47 -0
- package/dist/ledger.js +159 -0
- package/dist/lenses/architecture.d.ts +5 -0
- package/dist/lenses/architecture.js +22 -0
- package/dist/lenses/engineering.d.ts +5 -0
- package/dist/lenses/engineering.js +22 -0
- package/dist/lenses/general.d.ts +5 -0
- package/dist/lenses/general.js +20 -0
- package/dist/lenses/index.d.ts +16 -0
- package/dist/lenses/index.js +47 -0
- package/dist/lenses/product.d.ts +5 -0
- package/dist/lenses/product.js +22 -0
- package/dist/model-presets.d.ts +23 -0
- package/dist/model-presets.js +44 -0
- package/dist/phases/adjudication.d.ts +25 -0
- package/dist/phases/adjudication.js +142 -0
- package/dist/phases/build-consensus.d.ts +30 -0
- package/dist/phases/build-consensus.js +85 -0
- package/dist/phases/claim-registry.d.ts +26 -0
- package/dist/phases/claim-registry.js +60 -0
- package/dist/phases/cross-review.d.ts +47 -0
- package/dist/phases/cross-review.js +224 -0
- package/dist/phases/debate-rounds.d.ts +28 -0
- package/dist/phases/debate-rounds.js +161 -0
- package/dist/phases/participant-research.d.ts +39 -0
- package/dist/phases/participant-research.js +322 -0
- package/dist/phases/planning-detail-expansion.d.ts +39 -0
- package/dist/phases/planning-detail-expansion.js +120 -0
- package/dist/planner.d.ts +28 -0
- package/dist/planner.js +312 -0
- package/dist/providers/docs.d.ts +7 -0
- package/dist/providers/docs.js +111 -0
- package/dist/providers/git.d.ts +8 -0
- package/dist/providers/git.js +174 -0
- package/dist/providers/index.d.ts +32 -0
- package/dist/providers/index.js +132 -0
- package/dist/providers/none.d.ts +7 -0
- package/dist/providers/none.js +11 -0
- package/dist/providers/repo.d.ts +7 -0
- package/dist/providers/repo.js +258 -0
- package/dist/render/session.d.ts +17 -0
- package/dist/render/session.js +190 -0
- package/dist/render/terminal.d.ts +34 -0
- package/dist/render/terminal.js +286 -0
- package/dist/strategies/discussion.d.ts +25 -0
- package/dist/strategies/discussion.js +143 -0
- package/dist/strategies/index.d.ts +15 -0
- package/dist/strategies/index.js +28 -0
- package/dist/strategies/language-wrapper.d.ts +17 -0
- package/dist/strategies/language-wrapper.js +102 -0
- package/dist/strategies/lens-wrapper.d.ts +16 -0
- package/dist/strategies/lens-wrapper.js +236 -0
- package/dist/strategies/planning.d.ts +30 -0
- package/dist/strategies/planning.js +225 -0
- package/dist/strategies/review.d.ts +26 -0
- package/dist/strategies/review.js +168 -0
- package/dist/strategies/utils.d.ts +36 -0
- package/dist/strategies/utils.js +603 -0
- package/dist/tools/selector.d.ts +17 -0
- package/dist/tools/selector.js +61 -0
- package/dist/transitions.d.ts +53 -0
- package/dist/transitions.js +97 -0
- package/dist/types.d.ts +518 -0
- package/dist/types.js +27 -0
- package/package.json +45 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal renderer — renders ArenaConsensus and ArenaResultV2.
|
|
3
|
+
*
|
|
4
|
+
* All render functions return strings instead of printing directly,
|
|
5
|
+
* so callers can decide where to display (ctx.addStatus vs console.log).
|
|
6
|
+
*
|
|
7
|
+
* Output sections:
|
|
8
|
+
* 1. Summary
|
|
9
|
+
* 2. Roadmap (surfaced early for planning scenes)
|
|
10
|
+
* 3. Consensus sections ordered by output emphasis
|
|
11
|
+
* 4. Next Actions
|
|
12
|
+
*/
|
|
13
|
+
import { NOOP_COLORIZER } from "@cjhyy/code-shell-core/extension";
|
|
14
|
+
/**
|
|
15
|
+
* Format the full arena result as a styled string.
|
|
16
|
+
*/
|
|
17
|
+
export function formatArenaResult(result, c = NOOP_COLORIZER) {
|
|
18
|
+
const modeLabel = result.mode.charAt(0).toUpperCase() + result.mode.slice(1);
|
|
19
|
+
const lines = [];
|
|
20
|
+
lines.push("");
|
|
21
|
+
lines.push(c.bold(` ═══ ${modeLabel} Conclusion ═══`));
|
|
22
|
+
lines.push("");
|
|
23
|
+
// Summary
|
|
24
|
+
if (result.consensus.summary) {
|
|
25
|
+
lines.push(` ${result.consensus.summary}`);
|
|
26
|
+
lines.push("");
|
|
27
|
+
}
|
|
28
|
+
// Roadmap first for planning scenes
|
|
29
|
+
if (result.mode === "planning") {
|
|
30
|
+
lines.push(...formatRoadmapSection(result.consensus.roadmap, c));
|
|
31
|
+
if (result.consensus.roadmapDetails && result.consensus.roadmapDetails.length > 0) {
|
|
32
|
+
lines.push(...formatRoadmapDetailsSection(result.consensus.roadmapDetails, c));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Consensus sections ordered by output emphasis
|
|
36
|
+
for (const section of getOrderedConsensusSections(result, c)) {
|
|
37
|
+
lines.push(...formatConsensusSection(section.title, section.items, section.color, section.icon, c));
|
|
38
|
+
}
|
|
39
|
+
// Roadmap after findings for non-planning scenes
|
|
40
|
+
if (result.mode !== "planning") {
|
|
41
|
+
lines.push(...formatRoadmapSection(result.consensus.roadmap, c));
|
|
42
|
+
}
|
|
43
|
+
// Next Actions
|
|
44
|
+
if (result.consensus.nextActions.length > 0) {
|
|
45
|
+
lines.push(c.boldWhite("\n Next Actions:"));
|
|
46
|
+
for (const action of result.consensus.nextActions) {
|
|
47
|
+
const pri = action.priority === "high"
|
|
48
|
+
? c.red("HIGH")
|
|
49
|
+
: action.priority === "medium"
|
|
50
|
+
? c.yellow("MED")
|
|
51
|
+
: c.dim("LOW");
|
|
52
|
+
lines.push(` ${c.white("→")} [${pri}${c.white("]")} ${action.title}`);
|
|
53
|
+
if (action.rationale) {
|
|
54
|
+
lines.push(c.dim(` ${action.rationale}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Footer
|
|
59
|
+
lines.push("");
|
|
60
|
+
const planInfo = result.plan
|
|
61
|
+
? ` | Lenses: ${result.plan.lenses.map((l) => l.name).join(", ")} | Sources: ${result.plan.sources.map((s) => s.kind).join(", ")}`
|
|
62
|
+
: "";
|
|
63
|
+
lines.push(c.dim(` Mode: ${modeLabel} | ` +
|
|
64
|
+
`Findings: ${countFindings(result)} | ` +
|
|
65
|
+
`Models: ${result.participants.join(", ")}` +
|
|
66
|
+
planInfo));
|
|
67
|
+
lines.push("");
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Print the full arena result directly to stdout.
|
|
72
|
+
* Used by the CLI `code-shell arena` command (non-REPL).
|
|
73
|
+
*/
|
|
74
|
+
export function printArenaResult(result, c = NOOP_COLORIZER) {
|
|
75
|
+
console.log(formatArenaResult(result, c));
|
|
76
|
+
}
|
|
77
|
+
function formatConsensusSection(title, items, color, icon, c) {
|
|
78
|
+
if (items.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const lines = [];
|
|
81
|
+
lines.push(color(`\n ${title}:`));
|
|
82
|
+
for (const item of items) {
|
|
83
|
+
lines.push(color(` ${icon} ${item.title}`));
|
|
84
|
+
if (item.summary) {
|
|
85
|
+
lines.push(c.dim(` ${item.summary}`));
|
|
86
|
+
}
|
|
87
|
+
if (item.challenge.length > 0) {
|
|
88
|
+
lines.push(c.dim(` Challenged by: ${item.challenge.join(", ")}`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
function countFindings(result) {
|
|
94
|
+
return result.reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
95
|
+
}
|
|
96
|
+
function formatRoadmapSection(phases, c) {
|
|
97
|
+
if (phases.length === 0)
|
|
98
|
+
return [];
|
|
99
|
+
const lines = [];
|
|
100
|
+
lines.push(c.boldWhite("\n Roadmap:"));
|
|
101
|
+
for (const [index, phase] of phases.entries()) {
|
|
102
|
+
const pri = phase.priority === "high"
|
|
103
|
+
? c.red("HIGH")
|
|
104
|
+
: phase.priority === "medium"
|
|
105
|
+
? c.yellow("MED")
|
|
106
|
+
: c.dim("LOW");
|
|
107
|
+
lines.push(` ${c.white("→")} Phase ${index + 1}: ${phase.title} [${pri}${c.white("]")}`);
|
|
108
|
+
if (phase.goal) {
|
|
109
|
+
lines.push(c.dim(` Goal: ${phase.goal}`));
|
|
110
|
+
}
|
|
111
|
+
if (phase.scope.length > 0) {
|
|
112
|
+
lines.push(c.dim(` Scope: ${phase.scope.join("; ")}`));
|
|
113
|
+
}
|
|
114
|
+
if (phase.deliverables.length > 0) {
|
|
115
|
+
lines.push(c.dim(` Deliverables: ${phase.deliverables.join("; ")}`));
|
|
116
|
+
}
|
|
117
|
+
if (phase.dependencies.length > 0) {
|
|
118
|
+
lines.push(c.dim(` Dependencies: ${phase.dependencies.join("; ")}`));
|
|
119
|
+
}
|
|
120
|
+
if (phase.risks.length > 0) {
|
|
121
|
+
lines.push(c.dim(` Risks: ${phase.risks.join("; ")}`));
|
|
122
|
+
}
|
|
123
|
+
if (phase.successCriteria.length > 0) {
|
|
124
|
+
lines.push(c.dim(` Success: ${phase.successCriteria.join("; ")}`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return lines;
|
|
128
|
+
}
|
|
129
|
+
function formatRoadmapDetailsSection(details, c) {
|
|
130
|
+
const lines = [];
|
|
131
|
+
lines.push(c.boldWhite("\n Implementation Details:"));
|
|
132
|
+
for (const detail of details) {
|
|
133
|
+
const effort = detail.effort === "large"
|
|
134
|
+
? c.red("L")
|
|
135
|
+
: detail.effort === "medium"
|
|
136
|
+
? c.yellow("M")
|
|
137
|
+
: c.green("S");
|
|
138
|
+
lines.push(`\n ${c.white("▸")} ${detail.phaseTitle} [${effort}]`);
|
|
139
|
+
if (detail.objective) {
|
|
140
|
+
lines.push(c.dim(` ${detail.objective}`));
|
|
141
|
+
}
|
|
142
|
+
if (detail.targetFiles.length > 0) {
|
|
143
|
+
lines.push(c.dim(` Files: ${detail.targetFiles.join(", ")}`));
|
|
144
|
+
}
|
|
145
|
+
if (detail.codeChanges.length > 0) {
|
|
146
|
+
for (const change of detail.codeChanges) {
|
|
147
|
+
lines.push(c.dim(` • ${change}`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (detail.interfaces.length > 0) {
|
|
151
|
+
lines.push(c.dim(` Interfaces: ${detail.interfaces.join("; ")}`));
|
|
152
|
+
}
|
|
153
|
+
if (detail.migrationSteps.length > 0) {
|
|
154
|
+
lines.push(c.dim(` Migration:`));
|
|
155
|
+
for (const [i, step] of detail.migrationSteps.entries()) {
|
|
156
|
+
lines.push(c.dim(` ${i + 1}. ${step}`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (detail.validation.length > 0) {
|
|
160
|
+
lines.push(c.dim(` Validation: ${detail.validation.join("; ")}`));
|
|
161
|
+
}
|
|
162
|
+
if (detail.blockers.length > 0) {
|
|
163
|
+
lines.push(c.red(` Blockers: ${detail.blockers.join("; ")}`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return lines;
|
|
167
|
+
}
|
|
168
|
+
function getOrderedConsensusSections(result, c) {
|
|
169
|
+
const VALID_KINDS = new Set(["strength", "improvement", "risk", "question"]);
|
|
170
|
+
const defaultOrder = ["risk", "improvement", "strength", "question"];
|
|
171
|
+
const emphasize = (result.plan?.outputShape?.emphasize ?? []).filter((k) => VALID_KINDS.has(k));
|
|
172
|
+
const orderedKinds = dedupeFindingKinds([...emphasize, ...defaultOrder]);
|
|
173
|
+
return orderedKinds
|
|
174
|
+
.map((kind) => {
|
|
175
|
+
switch (kind) {
|
|
176
|
+
case "strength":
|
|
177
|
+
return { kind, title: "Strengths", items: result.consensus.strengths, color: c.green.bind(c), icon: "✓" };
|
|
178
|
+
case "improvement":
|
|
179
|
+
return { kind, title: "Improvements", items: result.consensus.improvements, color: c.yellow.bind(c), icon: "→" };
|
|
180
|
+
case "risk":
|
|
181
|
+
return { kind, title: "Risks", items: result.consensus.risks, color: c.red.bind(c), icon: "⚠" };
|
|
182
|
+
case "question":
|
|
183
|
+
return { kind, title: "Open Questions", items: result.consensus.openQuestions, color: c.cyan.bind(c), icon: "?" };
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
.filter((section) => section != null)
|
|
187
|
+
.filter((section) => section.items.length > 0);
|
|
188
|
+
}
|
|
189
|
+
function dedupeFindingKinds(kinds) {
|
|
190
|
+
const seen = new Set();
|
|
191
|
+
const result = [];
|
|
192
|
+
for (const kind of kinds) {
|
|
193
|
+
if (!seen.has(kind)) {
|
|
194
|
+
seen.add(kind);
|
|
195
|
+
result.push(kind);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Create a progress renderer that sends output to a sink.
|
|
202
|
+
* For REPL: pass ctx.addStatus. For CLI: pass console.log.
|
|
203
|
+
*/
|
|
204
|
+
export function createProgressRenderer(sink, c = NOOP_COLORIZER) {
|
|
205
|
+
return (event) => {
|
|
206
|
+
switch (event.type) {
|
|
207
|
+
case "plan_resolved":
|
|
208
|
+
sink(c.dim(`Plan: ${event.plan.mode} | ` +
|
|
209
|
+
`lenses: ${event.plan.lenses.map((l) => l.name).join(", ")} | ` +
|
|
210
|
+
`sources: ${event.plan.sources.map((s) => s.kind).join(", ")}` +
|
|
211
|
+
(event.plan.confidence !== "high" ? ` (${event.plan.confidence} confidence)` : "")));
|
|
212
|
+
break;
|
|
213
|
+
case "evidence_started":
|
|
214
|
+
sink(c.dim(` · collecting from ${event.source}…`));
|
|
215
|
+
break;
|
|
216
|
+
case "evidence_source_done": {
|
|
217
|
+
const tag = event.timedOut ? c.yellow(" (timed out)") : "";
|
|
218
|
+
sink(c.dim(` · ${event.source}: ${event.count} artifacts in ${event.durationMs}ms${tag}`));
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "evidence_collected":
|
|
222
|
+
sink(c.dim(`Evidence: ${event.artifacts.length} artifacts collected`));
|
|
223
|
+
break;
|
|
224
|
+
case "research_start":
|
|
225
|
+
sink(c.dim(`⏳ ${event.participant} researching...`));
|
|
226
|
+
break;
|
|
227
|
+
case "research_done": {
|
|
228
|
+
const fCount = event.report.findings.length;
|
|
229
|
+
sink(`${event.participant}: ${c.green("done")} (${fCount} findings)`);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case "context_lookup":
|
|
233
|
+
for (const t of event.tools) {
|
|
234
|
+
sink(c.dim(` 🔍 ${event.participant}: ${t}`));
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case "claims_registered":
|
|
238
|
+
sink(c.dim(`${event.claimCount} claims registered`));
|
|
239
|
+
break;
|
|
240
|
+
case "cross_review_start":
|
|
241
|
+
sink(c.dim("── Cross Review ──"));
|
|
242
|
+
break;
|
|
243
|
+
case "cross_review_done":
|
|
244
|
+
sink(c.dim(`${event.reviews.length} peer reviews collected`));
|
|
245
|
+
break;
|
|
246
|
+
case "verification_start":
|
|
247
|
+
sink(c.dim("── Verification Review ──"));
|
|
248
|
+
break;
|
|
249
|
+
case "verification_done":
|
|
250
|
+
sink(c.dim(`${event.challengeCount} challenges raised`));
|
|
251
|
+
break;
|
|
252
|
+
case "debate_round_start":
|
|
253
|
+
sink(c.dim(`── Debate Round ${event.round} (${event.claims.length} claims) ──`));
|
|
254
|
+
break;
|
|
255
|
+
case "debate_round_done":
|
|
256
|
+
sink(c.dim(`Round ${event.round}: ${event.resolved} resolved`));
|
|
257
|
+
break;
|
|
258
|
+
case "adjudication_done":
|
|
259
|
+
sink(c.dim(`Adjudication: ${event.accepted} accepted, ${event.unresolved} unresolved`));
|
|
260
|
+
break;
|
|
261
|
+
case "planning_merge_review_start":
|
|
262
|
+
sink(c.dim("── Planning Merge Review ──"));
|
|
263
|
+
break;
|
|
264
|
+
case "planning_merge_review_done":
|
|
265
|
+
sink(c.dim(`${event.mergeCount} merge suggestions collected`));
|
|
266
|
+
break;
|
|
267
|
+
case "roadmap_expansion_start":
|
|
268
|
+
sink(c.dim(`── Expanding ${event.phaseCount} roadmap phases ──`));
|
|
269
|
+
break;
|
|
270
|
+
case "roadmap_expansion_done":
|
|
271
|
+
sink(c.dim(`${event.detailCount} phase details generated`));
|
|
272
|
+
break;
|
|
273
|
+
case "consensus_start":
|
|
274
|
+
sink(c.dim("⏳ Building consensus..."));
|
|
275
|
+
break;
|
|
276
|
+
case "consensus_done":
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Legacy: direct console.log progress renderer for CLI mode.
|
|
283
|
+
*/
|
|
284
|
+
export function renderProgress(event, c = NOOP_COLORIZER) {
|
|
285
|
+
createProgressRenderer((text) => console.log(` ${text}`), c)(event);
|
|
286
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discussion strategy — open-ended debate with structured findings.
|
|
3
|
+
*
|
|
4
|
+
* V2: focuses on trade-offs, competing viewpoints, and evidence-backed arguments.
|
|
5
|
+
*/
|
|
6
|
+
import type { ArenaStrategyV2, ArenaBaseContext, ParticipantReport, FindingReview, ArenaConsensus, FindingKind, ClaimRecord, ClaimChallenge, ClaimAdjudication, ClaimStatusSummary, DebateTurn, DebateRound, RoundResearchDigest } from "../types.js";
|
|
7
|
+
export declare class DiscussionStrategy implements ArenaStrategyV2 {
|
|
8
|
+
researchSystemPrompt(name: string): string;
|
|
9
|
+
researchUserPrompt(topic: string, baseContext: ArenaBaseContext): string;
|
|
10
|
+
parseResearchResponse(participant: string, text: string): ParticipantReport;
|
|
11
|
+
crossReviewSystemPrompt(reviewerName: string): string;
|
|
12
|
+
crossReviewUserPrompt(topic: string, myReport: ParticipantReport, otherReports: ParticipantReport[]): string;
|
|
13
|
+
parseCrossReviewResponse(reviewer: string, text: string): FindingReview[];
|
|
14
|
+
consensusSystemPrompt(): string;
|
|
15
|
+
consensusUserPrompt(topic: string, reports: ParticipantReport[], reviews: FindingReview[]): string;
|
|
16
|
+
parseConsensusResponse(text: string): ArenaConsensus;
|
|
17
|
+
preferredFindingKinds(): FindingKind[];
|
|
18
|
+
verificationReviewUserPrompt(topic: string, myReport: ParticipantReport, claimsToReview: ClaimRecord[], digest: RoundResearchDigest): string;
|
|
19
|
+
parseVerificationReviewResponse(reviewer: string, text: string): ClaimChallenge[];
|
|
20
|
+
debateTurnUserPrompt(topic: string, claim: ClaimRecord, priorTurns: DebateTurn[], digest: RoundResearchDigest): string;
|
|
21
|
+
parseDebateTurnResponse(participant: string, text: string): DebateTurn;
|
|
22
|
+
adjudicationUserPrompt(topic: string, claim: ClaimRecord, debateRounds: DebateRound[], digest: RoundResearchDigest): string;
|
|
23
|
+
parseAdjudicationResponse(text: string): ClaimAdjudication;
|
|
24
|
+
claimAwareConsensusUserPrompt(topic: string, reports: ParticipantReport[], reviews: FindingReview[], claimSummary: ClaimStatusSummary): string;
|
|
25
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discussion strategy — open-ended debate with structured findings.
|
|
3
|
+
*
|
|
4
|
+
* V2: focuses on trade-offs, competing viewpoints, and evidence-backed arguments.
|
|
5
|
+
*/
|
|
6
|
+
import { formatBaseContext, formatReports, formatFindingReviews, formatClaimsForReview, formatDebateHistory, formatClaimSummaryForConsensus, formatDigestForPrompt, parseReport, parseReviews, parseConsensus, parseChallenges, parseDebateTurn, parseAdjudication, } from "./utils.js";
|
|
7
|
+
export class DiscussionStrategy {
|
|
8
|
+
researchSystemPrompt(name) {
|
|
9
|
+
return (`You are ${name}, participating in a multi-model discussion arena.\n\n` +
|
|
10
|
+
`You may have access to read-only tools to gather evidence. ` +
|
|
11
|
+
`The base context is intentionally lean — use tools as needed.\n\n` +
|
|
12
|
+
`IMPORTANT RULES:\n` +
|
|
13
|
+
`- Limit yourself to 3-5 tool rounds. Do NOT exhaustively read every file.\n` +
|
|
14
|
+
`- Output as many findings as the topic warrants — typically 5-15 for a non-trivial subject. ` +
|
|
15
|
+
`Each finding's "summary" should be 80+ words with concrete evidence and rationale, not a one-liner. ` +
|
|
16
|
+
`Rank by confidence.\n` +
|
|
17
|
+
`- Focus on trade-offs, risks, and open questions. Strengths are optional.\n\n` +
|
|
18
|
+
`Respond ONLY with JSON (no markdown fences):\n` +
|
|
19
|
+
`{"contextSummary": "what you investigated and your overall take",` +
|
|
20
|
+
` "findings": [{"id": "unique-id", "kind": "risk|improvement|question|strength",` +
|
|
21
|
+
` "title": "short title", "summary": "detailed analysis with evidence",` +
|
|
22
|
+
` "severity": "high|medium|low", "confidence": 0.0-1.0,` +
|
|
23
|
+
` "evidence": [{"type": "file|diff|grep|git|doc", "ref": "path", "note": "what it shows"}],` +
|
|
24
|
+
` "affectedFiles": ["paths"], "suggestedChange": "optional"}]}`);
|
|
25
|
+
}
|
|
26
|
+
researchUserPrompt(topic, baseContext) {
|
|
27
|
+
return (`## Discussion Topic\n${topic}\n\n` +
|
|
28
|
+
`${formatBaseContext(baseContext)}\n\n` +
|
|
29
|
+
`Use tools to investigate, then output 3-6 highest-confidence findings as JSON.`);
|
|
30
|
+
}
|
|
31
|
+
parseResearchResponse(participant, text) {
|
|
32
|
+
return parseReport(participant, text);
|
|
33
|
+
}
|
|
34
|
+
crossReviewSystemPrompt(reviewerName) {
|
|
35
|
+
return (`You are ${reviewerName}, reviewing other participants' discussion findings.\n\n` +
|
|
36
|
+
`Engage thoughtfully: challenge weak arguments, acknowledge strong ones.\n` +
|
|
37
|
+
`Focus on finding the best answer, not winning.\n\n` +
|
|
38
|
+
`For each finding you want to address, provide a verdict:\n` +
|
|
39
|
+
`- "agree": you confirm this perspective\n` +
|
|
40
|
+
`- "refine": mostly agree but with nuances\n` +
|
|
41
|
+
`- "disagree": you have a different view with evidence\n` +
|
|
42
|
+
`- "needs_evidence": the claim lacks supporting evidence\n\n` +
|
|
43
|
+
`Respond ONLY with a JSON array:\n` +
|
|
44
|
+
`[{"findingId": "...", "verdict": "...", "reason": "...", "extraEvidence": ["optional"]}]`);
|
|
45
|
+
}
|
|
46
|
+
crossReviewUserPrompt(topic, myReport, otherReports) {
|
|
47
|
+
return (`## Topic: ${topic}\n\n` +
|
|
48
|
+
`## Your Analysis\n${formatReports([myReport])}\n\n` +
|
|
49
|
+
`## Other Participants' Analysis\n${formatReports(otherReports)}\n\n` +
|
|
50
|
+
`Review the others' findings. Where do you agree or disagree? What nuances are missing?`);
|
|
51
|
+
}
|
|
52
|
+
parseCrossReviewResponse(reviewer, text) {
|
|
53
|
+
return parseReviews(reviewer, text);
|
|
54
|
+
}
|
|
55
|
+
consensusSystemPrompt() {
|
|
56
|
+
return (`You are a neutral moderator synthesizing a multi-model discussion into a balanced conclusion.\n\n` +
|
|
57
|
+
`Capture key insights, agreements, and remaining disagreements. ` +
|
|
58
|
+
`Do NOT suppress minority viewpoints — preserve them as open questions.\n\n` +
|
|
59
|
+
`IMPORTANT: Start with a "subjectSummary" — a factual framing of the topic, current scope, or assumptions under discussion before moving into conclusions.\n\n` +
|
|
60
|
+
`Respond ONLY with JSON (no markdown fences):\n` +
|
|
61
|
+
`{\n` +
|
|
62
|
+
` "summary": "balanced synthesis",\n` +
|
|
63
|
+
` "subjectSummary": "factual problem framing or current scope of the discussion",\n` +
|
|
64
|
+
` "strengths": [{"title": "...", "summary": "...", "support": [], "challenge": [], "confidence": 0.0-1.0, "evidenceRefs": []}],\n` +
|
|
65
|
+
` "improvements": [same structure],\n` +
|
|
66
|
+
` "risks": [same structure],\n` +
|
|
67
|
+
` "openQuestions": [same structure — include still-debated points],\n` +
|
|
68
|
+
` "nextActions": [{"title": "...", "priority": "high|medium|low", "rationale": "...", "relatedFindings": []}]\n` +
|
|
69
|
+
`}`);
|
|
70
|
+
}
|
|
71
|
+
consensusUserPrompt(topic, reports, reviews) {
|
|
72
|
+
return (`## Topic\n${topic}\n\n` +
|
|
73
|
+
`## Participant Analyses\n${formatReports(reports)}\n\n` +
|
|
74
|
+
`## Peer Reviews\n${formatFindingReviews(reviews)}\n\n` +
|
|
75
|
+
`Synthesize a balanced conclusion. Start with a factual subjectSummary, then preserve disagreements as open questions.`);
|
|
76
|
+
}
|
|
77
|
+
parseConsensusResponse(text) {
|
|
78
|
+
return parseConsensus(text);
|
|
79
|
+
}
|
|
80
|
+
preferredFindingKinds() {
|
|
81
|
+
return ["strength", "risk", "question"];
|
|
82
|
+
}
|
|
83
|
+
// ─── V2: Verification Review ────────────────────────────────────
|
|
84
|
+
verificationReviewUserPrompt(topic, myReport, claimsToReview, digest) {
|
|
85
|
+
return (`## Topic: ${topic}\n\n` +
|
|
86
|
+
`## Your Analysis\n${formatReports([myReport])}\n\n` +
|
|
87
|
+
`## Claims to Verify\n${formatClaimsForReview(claimsToReview)}\n\n` +
|
|
88
|
+
`## Evidence Digest\n${formatDigestForPrompt(digest)}\n\n` +
|
|
89
|
+
`Engage critically with each claim. Challenge weak arguments, acknowledge strong ones. ` +
|
|
90
|
+
`Focus on finding the best answer, not winning.\n` +
|
|
91
|
+
`Respond ONLY with a JSON array:\n` +
|
|
92
|
+
`[{"claimId": "...", "verdict": "agree|refine|disagree|needs_evidence", "reason": "...", ` +
|
|
93
|
+
`"supportingEvidenceRefs": ["optional"], "requestedChecks": [{"description": "what to check", "priority": "high|medium|low"}]}]`);
|
|
94
|
+
}
|
|
95
|
+
parseVerificationReviewResponse(reviewer, text) {
|
|
96
|
+
return parseChallenges(reviewer, text);
|
|
97
|
+
}
|
|
98
|
+
// ─── V2: Debate ─────────────────────────────────────────────────
|
|
99
|
+
debateTurnUserPrompt(topic, claim, priorTurns, digest) {
|
|
100
|
+
return (`## Topic: ${topic}\n\n` +
|
|
101
|
+
`## Contested Claim\n` +
|
|
102
|
+
`[${claim.claimId}] ${claim.finding.title}\n${claim.finding.summary}\n\n` +
|
|
103
|
+
`## Prior Debate\n${formatDebateHistory(priorTurns)}\n\n` +
|
|
104
|
+
`## Evidence Digest\n${formatDigestForPrompt(digest)}\n\n` +
|
|
105
|
+
`Engage thoughtfully. If the evidence supports the claim, narrow toward agreement. ` +
|
|
106
|
+
`If not, explain why with evidence.\n` +
|
|
107
|
+
`Respond ONLY with JSON:\n` +
|
|
108
|
+
`{"stance": "support|oppose|narrow|uncertain", "summary": "your argument", "newEvidenceRefs": ["optional"]}`);
|
|
109
|
+
}
|
|
110
|
+
parseDebateTurnResponse(participant, text) {
|
|
111
|
+
return parseDebateTurn(participant, text);
|
|
112
|
+
}
|
|
113
|
+
// ─── V2: Adjudication ──────────────────────────────────────────
|
|
114
|
+
adjudicationUserPrompt(topic, claim, debateRounds, digest) {
|
|
115
|
+
const debateSummary = debateRounds.length > 0
|
|
116
|
+
? debateRounds.map((r) => `Round ${r.round}:\n${formatDebateHistory(r.participants)}\n${r.resolved ? "→ Resolved" : "→ Unresolved"}`).join("\n\n")
|
|
117
|
+
: "No debate rounds occurred.";
|
|
118
|
+
return (`## Topic: ${topic}\n\n` +
|
|
119
|
+
`## Contested Claim\n` +
|
|
120
|
+
`[${claim.claimId}] ${claim.finding.title}\n${claim.finding.summary}\n` +
|
|
121
|
+
`Owner: ${claim.owner}\n\n` +
|
|
122
|
+
`## Challenges\n` +
|
|
123
|
+
claim.challenges.map((c) => `[${c.reviewer}] ${c.verdict}: ${c.reason}`).join("\n") + "\n\n" +
|
|
124
|
+
`## Debate\n${debateSummary}\n\n` +
|
|
125
|
+
`## Evidence Digest\n${formatDigestForPrompt(digest)}\n\n` +
|
|
126
|
+
`As moderator, synthesize the debate. Preserve minority viewpoints where the evidence is genuinely ambiguous.\n` +
|
|
127
|
+
`Respond ONLY with JSON:\n` +
|
|
128
|
+
`{"outcome": "accepted|accepted_with_revision|rejected|unresolved", "rationale": "...", ` +
|
|
129
|
+
`"finalSummary": "revised claim summary", "supportingEvidenceRefs": ["..."]}`);
|
|
130
|
+
}
|
|
131
|
+
parseAdjudicationResponse(text) {
|
|
132
|
+
return parseAdjudication("", text);
|
|
133
|
+
}
|
|
134
|
+
// ─── V2: Claim-Aware Consensus ─────────────────────────────────
|
|
135
|
+
claimAwareConsensusUserPrompt(topic, reports, reviews, claimSummary) {
|
|
136
|
+
return (`## Topic\n${topic}\n\n` +
|
|
137
|
+
`## Claim Verification Status\n${formatClaimSummaryForConsensus(claimSummary)}\n\n` +
|
|
138
|
+
`## Participant Analyses\n${formatReports(reports)}\n\n` +
|
|
139
|
+
`## Peer Reviews\n${formatFindingReviews(reviews)}\n\n` +
|
|
140
|
+
`Synthesize a balanced conclusion. Verified claims are high-confidence. ` +
|
|
141
|
+
`Unresolved claims should be preserved as open questions — do NOT suppress minority viewpoints.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arena strategies — mode-specific behavior for the arena orchestration loop.
|
|
3
|
+
*/
|
|
4
|
+
export { ReviewStrategy } from "./review.js";
|
|
5
|
+
export { DiscussionStrategy } from "./discussion.js";
|
|
6
|
+
export { PlanningStrategy } from "./planning.js";
|
|
7
|
+
export { withLens } from "./lens-wrapper.js";
|
|
8
|
+
import type { ArenaMode, ArenaStrategy, ArenaPlan } from "../types.js";
|
|
9
|
+
/** Get the default strategy for a given arena mode. */
|
|
10
|
+
export declare function getStrategy(mode: ArenaMode): ArenaStrategy;
|
|
11
|
+
/**
|
|
12
|
+
* Get a strategy for a plan — mode strategy + lens wrapper.
|
|
13
|
+
* This is the new evidence-driven entry point.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getStrategyForPlan(plan: ArenaPlan): ArenaStrategy;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arena strategies — mode-specific behavior for the arena orchestration loop.
|
|
3
|
+
*/
|
|
4
|
+
export { ReviewStrategy } from "./review.js";
|
|
5
|
+
export { DiscussionStrategy } from "./discussion.js";
|
|
6
|
+
export { PlanningStrategy } from "./planning.js";
|
|
7
|
+
export { withLens } from "./lens-wrapper.js";
|
|
8
|
+
import { ReviewStrategy } from "./review.js";
|
|
9
|
+
import { DiscussionStrategy } from "./discussion.js";
|
|
10
|
+
import { PlanningStrategy } from "./planning.js";
|
|
11
|
+
import { withLens } from "./lens-wrapper.js";
|
|
12
|
+
const STRATEGY_MAP = {
|
|
13
|
+
review: () => new ReviewStrategy(),
|
|
14
|
+
discussion: () => new DiscussionStrategy(),
|
|
15
|
+
planning: () => new PlanningStrategy(),
|
|
16
|
+
};
|
|
17
|
+
/** Get the default strategy for a given arena mode. */
|
|
18
|
+
export function getStrategy(mode) {
|
|
19
|
+
return STRATEGY_MAP[mode]();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get a strategy for a plan — mode strategy + lens wrapper.
|
|
23
|
+
* This is the new evidence-driven entry point.
|
|
24
|
+
*/
|
|
25
|
+
export function getStrategyForPlan(plan) {
|
|
26
|
+
const base = STRATEGY_MAP[plan.mode]();
|
|
27
|
+
return withLens(base, plan);
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-aware strategy wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Detects the language of the user's query and injects a language instruction
|
|
5
|
+
* into all strategy system prompts so the arena output matches the query language.
|
|
6
|
+
*/
|
|
7
|
+
import type { ArenaStrategy } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Detect whether the topic is primarily non-English.
|
|
10
|
+
* Returns a language instruction string, or empty if English.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectLanguageInstruction(topic: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Wrap a strategy to inject language instructions into all system prompts.
|
|
15
|
+
* If the topic is English, returns the original strategy unchanged.
|
|
16
|
+
*/
|
|
17
|
+
export declare function withLanguage(strategy: ArenaStrategy, topic: string): ArenaStrategy;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-aware strategy wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Detects the language of the user's query and injects a language instruction
|
|
5
|
+
* into all strategy system prompts so the arena output matches the query language.
|
|
6
|
+
*/
|
|
7
|
+
import { isStrategyV2 } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Detect whether the topic is primarily non-English.
|
|
10
|
+
* Returns a language instruction string, or empty if English.
|
|
11
|
+
*/
|
|
12
|
+
export function detectLanguageInstruction(topic) {
|
|
13
|
+
// Simple heuristic: count CJK / Cyrillic / Arabic / Thai / Korean characters
|
|
14
|
+
// vs ASCII. If non-ASCII ratio is significant, infer the language.
|
|
15
|
+
const nonAscii = topic.replace(/[\x00-\x7F]/g, "");
|
|
16
|
+
const ratio = nonAscii.length / Math.max(topic.length, 1);
|
|
17
|
+
if (ratio < 0.15)
|
|
18
|
+
return ""; // predominantly English/ASCII
|
|
19
|
+
// Detect specific scripts
|
|
20
|
+
if (/[\u4e00-\u9fff\u3400-\u4dbf]/.test(topic)) {
|
|
21
|
+
return "IMPORTANT: The user's query is in Chinese. You MUST respond in Chinese (中文). All text output — summaries, findings, titles, descriptions, rationale — must be written in Chinese. JSON field values must be in Chinese.";
|
|
22
|
+
}
|
|
23
|
+
if (/[\u3040-\u309f\u30a0-\u30ff]/.test(topic)) {
|
|
24
|
+
return "IMPORTANT: The user's query is in Japanese. You MUST respond in Japanese (日本語). All text output must be in Japanese. JSON field values must be in Japanese.";
|
|
25
|
+
}
|
|
26
|
+
if (/[\uac00-\ud7af]/.test(topic)) {
|
|
27
|
+
return "IMPORTANT: The user's query is in Korean. You MUST respond in Korean (한국어). All text output must be in Korean. JSON field values must be in Korean.";
|
|
28
|
+
}
|
|
29
|
+
if (/[\u0400-\u04ff]/.test(topic)) {
|
|
30
|
+
return "IMPORTANT: The user's query is in Russian. You MUST respond in Russian (русский). All text output must be in Russian. JSON field values must be in Russian.";
|
|
31
|
+
}
|
|
32
|
+
if (/[\u0600-\u06ff]/.test(topic)) {
|
|
33
|
+
return "IMPORTANT: The user's query is in Arabic. You MUST respond in Arabic (العربية). All text output must be in Arabic. JSON field values must be in Arabic.";
|
|
34
|
+
}
|
|
35
|
+
// Generic fallback for other non-ASCII scripts
|
|
36
|
+
return "IMPORTANT: Respond in the same language as the user's query. All text output — summaries, findings, titles, descriptions — must match the query language.";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a strategy to inject language instructions into all system prompts.
|
|
40
|
+
* If the topic is English, returns the original strategy unchanged.
|
|
41
|
+
*/
|
|
42
|
+
export function withLanguage(strategy, topic) {
|
|
43
|
+
const langInstruction = detectLanguageInstruction(topic);
|
|
44
|
+
if (!langInstruction)
|
|
45
|
+
return strategy;
|
|
46
|
+
return {
|
|
47
|
+
researchSystemPrompt(name) {
|
|
48
|
+
return `${langInstruction}\n\n${strategy.researchSystemPrompt(name)}`;
|
|
49
|
+
},
|
|
50
|
+
researchUserPrompt(t, ctx) {
|
|
51
|
+
return strategy.researchUserPrompt(t, ctx);
|
|
52
|
+
},
|
|
53
|
+
parseResearchResponse(participant, text) {
|
|
54
|
+
return strategy.parseResearchResponse(participant, text);
|
|
55
|
+
},
|
|
56
|
+
crossReviewSystemPrompt(reviewerName) {
|
|
57
|
+
return `${langInstruction}\n\n${strategy.crossReviewSystemPrompt(reviewerName)}`;
|
|
58
|
+
},
|
|
59
|
+
crossReviewUserPrompt(t, my, others) {
|
|
60
|
+
return strategy.crossReviewUserPrompt(t, my, others);
|
|
61
|
+
},
|
|
62
|
+
parseCrossReviewResponse(reviewer, text) {
|
|
63
|
+
return strategy.parseCrossReviewResponse(reviewer, text);
|
|
64
|
+
},
|
|
65
|
+
consensusSystemPrompt() {
|
|
66
|
+
return `${langInstruction}\n\n${strategy.consensusSystemPrompt()}`;
|
|
67
|
+
},
|
|
68
|
+
consensusUserPrompt(t, reports, reviews) {
|
|
69
|
+
return strategy.consensusUserPrompt(t, reports, reviews);
|
|
70
|
+
},
|
|
71
|
+
parseConsensusResponse(text) {
|
|
72
|
+
return strategy.parseConsensusResponse(text);
|
|
73
|
+
},
|
|
74
|
+
preferredFindingKinds() {
|
|
75
|
+
return strategy.preferredFindingKinds();
|
|
76
|
+
},
|
|
77
|
+
// ─── V2 forwarding (conditional) ─────────────────────────────
|
|
78
|
+
...(isStrategyV2(strategy) ? {
|
|
79
|
+
verificationReviewUserPrompt(topic, myReport, claims, digest) {
|
|
80
|
+
return strategy.verificationReviewUserPrompt(topic, myReport, claims, digest);
|
|
81
|
+
},
|
|
82
|
+
parseVerificationReviewResponse(reviewer, text) {
|
|
83
|
+
return strategy.parseVerificationReviewResponse(reviewer, text);
|
|
84
|
+
},
|
|
85
|
+
debateTurnUserPrompt(topic, claim, priorTurns, digest) {
|
|
86
|
+
return strategy.debateTurnUserPrompt(topic, claim, priorTurns, digest);
|
|
87
|
+
},
|
|
88
|
+
parseDebateTurnResponse(participant, text) {
|
|
89
|
+
return strategy.parseDebateTurnResponse(participant, text);
|
|
90
|
+
},
|
|
91
|
+
adjudicationUserPrompt(topic, claim, rounds, digest) {
|
|
92
|
+
return strategy.adjudicationUserPrompt(topic, claim, rounds, digest);
|
|
93
|
+
},
|
|
94
|
+
parseAdjudicationResponse(text) {
|
|
95
|
+
return strategy.parseAdjudicationResponse(text);
|
|
96
|
+
},
|
|
97
|
+
claimAwareConsensusUserPrompt(topic, reports, reviews, claimSummary) {
|
|
98
|
+
return strategy.claimAwareConsensusUserPrompt(topic, reports, reviews, claimSummary);
|
|
99
|
+
},
|
|
100
|
+
} : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lens-aware strategy wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Composes a mode strategy with lens-specific prompts:
|
|
5
|
+
* systemPrompt = modeStrategy.systemPrompt() + lensPrompt + toolGuidance
|
|
6
|
+
*
|
|
7
|
+
* This achieves the separation described in the architecture doc:
|
|
8
|
+
* - Strategy (mode) → how to collaborate
|
|
9
|
+
* - Lens → from what perspective
|
|
10
|
+
*/
|
|
11
|
+
import type { ArenaStrategy, ArenaPlan } from "../types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Wrap a strategy to inject lens-specific role and criteria prompts.
|
|
14
|
+
* If no plan is provided, returns the original strategy unchanged.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withLens(strategy: ArenaStrategy, plan: ArenaPlan): ArenaStrategy;
|