@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.
Files changed (102) hide show
  1. package/README.md +46 -0
  2. package/dist/arena.d.ts +45 -0
  3. package/dist/arena.js +362 -0
  4. package/dist/capability.d.ts +43 -0
  5. package/dist/capability.js +453 -0
  6. package/dist/context/context-tools.d.ts +16 -0
  7. package/dist/context/context-tools.js +272 -0
  8. package/dist/context/within-root.d.ts +7 -0
  9. package/dist/context/within-root.js +15 -0
  10. package/dist/detect-mode.d.ts +20 -0
  11. package/dist/detect-mode.js +78 -0
  12. package/dist/digest-builder.d.ts +25 -0
  13. package/dist/digest-builder.js +120 -0
  14. package/dist/index.d.ts +25 -0
  15. package/dist/index.js +23 -0
  16. package/dist/index.runtime.d.ts +13 -0
  17. package/dist/index.runtime.js +11 -0
  18. package/dist/iterate/convergence.d.ts +25 -0
  19. package/dist/iterate/convergence.js +103 -0
  20. package/dist/iterate/formats/index.d.ts +22 -0
  21. package/dist/iterate/formats/index.js +283 -0
  22. package/dist/iterate/index.d.ts +11 -0
  23. package/dist/iterate/index.js +9 -0
  24. package/dist/iterate/iterative-arena.d.ts +31 -0
  25. package/dist/iterate/iterative-arena.js +252 -0
  26. package/dist/iterate/parse.d.ts +42 -0
  27. package/dist/iterate/parse.js +123 -0
  28. package/dist/iterate/phases/argue.d.ts +22 -0
  29. package/dist/iterate/phases/argue.js +165 -0
  30. package/dist/iterate/phases/revise.d.ts +16 -0
  31. package/dist/iterate/phases/revise.js +62 -0
  32. package/dist/iterate/phases/tournament.d.ts +34 -0
  33. package/dist/iterate/phases/tournament.js +113 -0
  34. package/dist/iterate/tools/web-tools.d.ts +13 -0
  35. package/dist/iterate/tools/web-tools.js +54 -0
  36. package/dist/iterate/types.d.ts +152 -0
  37. package/dist/iterate/types.js +8 -0
  38. package/dist/ledger.d.ts +47 -0
  39. package/dist/ledger.js +159 -0
  40. package/dist/lenses/architecture.d.ts +5 -0
  41. package/dist/lenses/architecture.js +22 -0
  42. package/dist/lenses/engineering.d.ts +5 -0
  43. package/dist/lenses/engineering.js +22 -0
  44. package/dist/lenses/general.d.ts +5 -0
  45. package/dist/lenses/general.js +20 -0
  46. package/dist/lenses/index.d.ts +16 -0
  47. package/dist/lenses/index.js +47 -0
  48. package/dist/lenses/product.d.ts +5 -0
  49. package/dist/lenses/product.js +22 -0
  50. package/dist/model-presets.d.ts +23 -0
  51. package/dist/model-presets.js +44 -0
  52. package/dist/phases/adjudication.d.ts +25 -0
  53. package/dist/phases/adjudication.js +142 -0
  54. package/dist/phases/build-consensus.d.ts +30 -0
  55. package/dist/phases/build-consensus.js +85 -0
  56. package/dist/phases/claim-registry.d.ts +26 -0
  57. package/dist/phases/claim-registry.js +60 -0
  58. package/dist/phases/cross-review.d.ts +47 -0
  59. package/dist/phases/cross-review.js +224 -0
  60. package/dist/phases/debate-rounds.d.ts +28 -0
  61. package/dist/phases/debate-rounds.js +161 -0
  62. package/dist/phases/participant-research.d.ts +39 -0
  63. package/dist/phases/participant-research.js +322 -0
  64. package/dist/phases/planning-detail-expansion.d.ts +39 -0
  65. package/dist/phases/planning-detail-expansion.js +120 -0
  66. package/dist/planner.d.ts +28 -0
  67. package/dist/planner.js +312 -0
  68. package/dist/providers/docs.d.ts +7 -0
  69. package/dist/providers/docs.js +111 -0
  70. package/dist/providers/git.d.ts +8 -0
  71. package/dist/providers/git.js +174 -0
  72. package/dist/providers/index.d.ts +32 -0
  73. package/dist/providers/index.js +132 -0
  74. package/dist/providers/none.d.ts +7 -0
  75. package/dist/providers/none.js +11 -0
  76. package/dist/providers/repo.d.ts +7 -0
  77. package/dist/providers/repo.js +258 -0
  78. package/dist/render/session.d.ts +17 -0
  79. package/dist/render/session.js +190 -0
  80. package/dist/render/terminal.d.ts +34 -0
  81. package/dist/render/terminal.js +286 -0
  82. package/dist/strategies/discussion.d.ts +25 -0
  83. package/dist/strategies/discussion.js +143 -0
  84. package/dist/strategies/index.d.ts +15 -0
  85. package/dist/strategies/index.js +28 -0
  86. package/dist/strategies/language-wrapper.d.ts +17 -0
  87. package/dist/strategies/language-wrapper.js +102 -0
  88. package/dist/strategies/lens-wrapper.d.ts +16 -0
  89. package/dist/strategies/lens-wrapper.js +236 -0
  90. package/dist/strategies/planning.d.ts +30 -0
  91. package/dist/strategies/planning.js +225 -0
  92. package/dist/strategies/review.d.ts +26 -0
  93. package/dist/strategies/review.js +168 -0
  94. package/dist/strategies/utils.d.ts +36 -0
  95. package/dist/strategies/utils.js +603 -0
  96. package/dist/tools/selector.d.ts +17 -0
  97. package/dist/tools/selector.js +61 -0
  98. package/dist/transitions.d.ts +53 -0
  99. package/dist/transitions.js +97 -0
  100. package/dist/types.d.ts +518 -0
  101. package/dist/types.js +27 -0
  102. package/package.json +45 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tournament v1 phase β€” every participant writes a candidate, then the
3
+ * author merges anonymized candidates into v1.
4
+ */
5
+ import type { ArenaParticipant } from "../../types.js";
6
+ import type { FormatPack } from "../formats/index.js";
7
+ import type { Draft, DraftCandidate, IterateProgressEvent, IterateSubject } from "../types.js";
8
+ /** Run the v1 tournament: parallel candidate generation. */
9
+ export declare function runTournamentCandidates(args: {
10
+ subject: IterateSubject;
11
+ format: FormatPack;
12
+ participants: ArenaParticipant[];
13
+ minDraftLength: number;
14
+ signal?: AbortSignal;
15
+ onProgress?: (e: IterateProgressEvent) => void;
16
+ }): Promise<DraftCandidate[]>;
17
+ /** Merge candidates into v1 using the author. */
18
+ export declare function mergeCandidatesToV1(args: {
19
+ subject: IterateSubject;
20
+ format: FormatPack;
21
+ author: ArenaParticipant;
22
+ candidates: DraftCandidate[];
23
+ minDraftLength: number;
24
+ signal?: AbortSignal;
25
+ onProgress?: (e: IterateProgressEvent) => void;
26
+ }): Promise<Draft>;
27
+ /** Single-author v1 (tournament disabled) β€” author writes alone. */
28
+ export declare function singleAuthorV1(args: {
29
+ subject: IterateSubject;
30
+ format: FormatPack;
31
+ author: ArenaParticipant;
32
+ minDraftLength: number;
33
+ signal?: AbortSignal;
34
+ }): Promise<Draft>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Tournament v1 phase β€” every participant writes a candidate, then the
3
+ * author merges anonymized candidates into v1.
4
+ */
5
+ import { createLLMClient } from "@cjhyy/code-shell-core/extension";
6
+ import { logger } from "@cjhyy/code-shell-core/extension";
7
+ import { parseMergeResponse } from "../parse.js";
8
+ /** Run the v1 tournament: parallel candidate generation. */
9
+ export async function runTournamentCandidates(args) {
10
+ const { subject, format, participants, minDraftLength, signal, onProgress } = args;
11
+ onProgress?.({ type: "v1_tournament_start", data: { count: participants.length } });
12
+ const tasks = participants.map(async (p, idx) => {
13
+ signal?.throwIfAborted();
14
+ const client = await createLLMClient(p.llm, p.clientDefaults);
15
+ const prompt = format.draftPrompt(subject, minDraftLength);
16
+ // Long-form generation needs a high output ceiling; the default 8k from
17
+ // LLMClientBase is too low for a substantive PRD or design doc and
18
+ // results in mid-sentence truncation that breaks downstream parsing.
19
+ const resp = await client.createMessage({
20
+ systemPrompt: prompt,
21
+ messages: [{ role: "user", content: `Write your draft now.` }],
22
+ tools: [],
23
+ maxTokens: 32_000,
24
+ signal,
25
+ });
26
+ const content = resp.text.trim();
27
+ const candidate = {
28
+ author: p.name,
29
+ anonymousLabel: `Draft ${String.fromCharCode(65 + idx)}`, // A, B, C...
30
+ content,
31
+ };
32
+ onProgress?.({
33
+ type: "v1_candidate_done",
34
+ participant: p.name,
35
+ data: { length: content.length, anonymousLabel: candidate.anonymousLabel },
36
+ });
37
+ logger.info("arena.iterate.v1_candidate", {
38
+ participant: p.name,
39
+ anonymousLabel: candidate.anonymousLabel,
40
+ length: content.length,
41
+ });
42
+ return candidate;
43
+ });
44
+ // Tolerate individual failures β€” we'd rather merge fewer drafts than abort.
45
+ const settled = await Promise.allSettled(tasks);
46
+ const candidates = [];
47
+ for (const s of settled) {
48
+ if (s.status === "fulfilled")
49
+ candidates.push(s.value);
50
+ else
51
+ logger.warn("arena.iterate.v1_candidate_failed", { error: s.reason?.message });
52
+ }
53
+ if (candidates.length === 0) {
54
+ throw new Error("All v1 tournament candidates failed");
55
+ }
56
+ // Shuffle so anonymous labels don't correlate with participants[] order.
57
+ for (let i = candidates.length - 1; i > 0; i--) {
58
+ const j = Math.floor(Math.random() * (i + 1));
59
+ [candidates[i], candidates[j]] = [candidates[j], candidates[i]];
60
+ candidates[i].anonymousLabel = `Draft ${String.fromCharCode(65 + i)}`;
61
+ }
62
+ candidates[0].anonymousLabel = `Draft A`;
63
+ return candidates;
64
+ }
65
+ /** Merge candidates into v1 using the author. */
66
+ export async function mergeCandidatesToV1(args) {
67
+ const { subject, format, author, candidates, minDraftLength, signal, onProgress } = args;
68
+ onProgress?.({ type: "v1_merge_start", participant: author.name });
69
+ const client = await createLLMClient(author.llm, author.clientDefaults);
70
+ const prompt = format.mergePrompt(subject, candidates, minDraftLength);
71
+ const resp = await client.createMessage({
72
+ systemPrompt: prompt,
73
+ messages: [{ role: "user", content: `Produce v1 now.` }],
74
+ tools: [],
75
+ maxTokens: 32_000,
76
+ signal,
77
+ });
78
+ const { content, rationale } = parseMergeResponse(resp.text);
79
+ const draft = {
80
+ version: 1,
81
+ author: author.name,
82
+ format: format.format,
83
+ content,
84
+ draftCandidates: candidates,
85
+ mergeRationale: rationale,
86
+ };
87
+ onProgress?.({ type: "v1_merge_done", participant: author.name, data: { length: content.length } });
88
+ logger.info("arena.iterate.v1_merge", {
89
+ author: author.name,
90
+ candidateCount: candidates.length,
91
+ length: content.length,
92
+ });
93
+ return draft;
94
+ }
95
+ /** Single-author v1 (tournament disabled) β€” author writes alone. */
96
+ export async function singleAuthorV1(args) {
97
+ const { subject, format, author, minDraftLength, signal } = args;
98
+ const client = await createLLMClient(author.llm, author.clientDefaults);
99
+ const prompt = format.draftPrompt(subject, minDraftLength);
100
+ const resp = await client.createMessage({
101
+ systemPrompt: prompt,
102
+ messages: [{ role: "user", content: `Write your draft now.` }],
103
+ tools: [],
104
+ maxTokens: 32_000,
105
+ signal,
106
+ });
107
+ return {
108
+ version: 1,
109
+ author: author.name,
110
+ format: format.format,
111
+ content: resp.text.trim(),
112
+ };
113
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Web tools for arena iterate critics β€” fabrication detection support.
3
+ *
4
+ * Wraps the existing builtin webSearchTool / webFetchTool so critics can
5
+ * verify factual claims during the argue phase.
6
+ */
7
+ import type { ToolDefinition } from "@cjhyy/code-shell-core/extension";
8
+ export declare const webSearchToolDef: ToolDefinition;
9
+ export declare const webFetchToolDef: ToolDefinition;
10
+ export declare const ITERATE_WEB_TOOLS: ToolDefinition[];
11
+ export declare function executeIterateWebTool(toolName: string, args: Record<string, unknown>): Promise<string>;
12
+ /** Probe env to determine if any web-search provider is configured. */
13
+ export declare function hasWebSearchProvider(): boolean;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Web tools for arena iterate critics β€” fabrication detection support.
3
+ *
4
+ * Wraps the existing builtin webSearchTool / webFetchTool so critics can
5
+ * verify factual claims during the argue phase.
6
+ */
7
+ import { webSearchTool } from "@cjhyy/code-shell-core/extension";
8
+ import { webFetchTool } from "@cjhyy/code-shell-core/extension";
9
+ export const webSearchToolDef = {
10
+ name: "web_search",
11
+ description: "Search the web (Google via Serper / Tavily / SearXNG depending on env). " +
12
+ "Use this to verify any specific number, citation, URL, library version, " +
13
+ "API name, or external claim made in the draft you are reviewing. " +
14
+ "If a claim cannot be verified by web_search, mark it as a fabrication " +
15
+ "critique with severity = blocker.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ query: { type: "string", description: "Search query (English usually works best)." },
20
+ num_results: { type: "number", description: "Default 5, max 20." },
21
+ },
22
+ required: ["query"],
23
+ },
24
+ };
25
+ export const webFetchToolDef = {
26
+ name: "web_fetch",
27
+ description: "Fetch a URL and return its text content (HTML stripped). Use after " +
28
+ "web_search to read the actual page content of a search result and " +
29
+ "confirm a specific claim, version number, or quoted figure.",
30
+ inputSchema: {
31
+ type: "object",
32
+ properties: {
33
+ url: { type: "string", description: "Full http(s) URL to fetch." },
34
+ max_length: { type: "number", description: "Default 50000 characters." },
35
+ },
36
+ required: ["url"],
37
+ },
38
+ };
39
+ export const ITERATE_WEB_TOOLS = [webSearchToolDef, webFetchToolDef];
40
+ export async function executeIterateWebTool(toolName, args) {
41
+ if (toolName === "web_search") {
42
+ return webSearchTool(args);
43
+ }
44
+ if (toolName === "web_fetch") {
45
+ return webFetchTool(args);
46
+ }
47
+ return `Error: unknown tool "${toolName}"`;
48
+ }
49
+ /** Probe env to determine if any web-search provider is configured. */
50
+ export function hasWebSearchProvider() {
51
+ return Boolean(process.env.SERPER_API_KEY ||
52
+ process.env.TAVILY_API_KEY ||
53
+ process.env.SEARXNG_URL);
54
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Iterate mode β€” multi-model authoring loop.
3
+ *
4
+ * Author writes β†’ critics argue β†’ author revises β†’ ... β†’ final draft.
5
+ * v1 uses a tournament: every participant writes a candidate, then the
6
+ * author merges (anonymized inputs) into a single v1.
7
+ */
8
+ import type { ArenaParticipant } from "../types.js";
9
+ export type IterateFormat = "code" | "document";
10
+ export interface IterateSubject {
11
+ /** What the author is producing β€” short descriptive label. */
12
+ label: string;
13
+ /** Full instruction to the author / critics. */
14
+ description: string;
15
+ /** Optional source artifacts (file paths, urls) to ground the work. */
16
+ sources?: string[];
17
+ }
18
+ /** A v1 candidate produced by one participant during the tournament. */
19
+ export interface DraftCandidate {
20
+ author: string;
21
+ anonymousLabel: string;
22
+ content: string;
23
+ }
24
+ export interface Draft {
25
+ version: number;
26
+ author: string;
27
+ format: IterateFormat;
28
+ content: string;
29
+ /**
30
+ * For v1 with tournament strategy: the candidates that were merged.
31
+ * For v2+: undefined.
32
+ */
33
+ draftCandidates?: DraftCandidate[];
34
+ /** Author's explanation of how candidates were merged into v1. */
35
+ mergeRationale?: string;
36
+ /**
37
+ * For v2+: which critique IDs were addressed and how.
38
+ */
39
+ acceptedCritiques?: string[];
40
+ rejectedCritiques?: Array<{
41
+ id: string;
42
+ reason: string;
43
+ }>;
44
+ /** Free-form note from author: what changed and why. */
45
+ changelog?: string;
46
+ }
47
+ export type CritiqueSeverity = "blocker" | "major" | "minor" | "nit" | "praise";
48
+ export type CritiqueCategory = "correctness" | "completeness" | "clarity" | "evidence" | "structure" | "style" | "fabrication" | "other";
49
+ export interface CritiqueEvidence {
50
+ url: string;
51
+ snippet?: string;
52
+ }
53
+ export interface Critique {
54
+ id: string;
55
+ critic: string;
56
+ /** 5-15 word quote from the draft for precise location. */
57
+ anchor: string;
58
+ severity: CritiqueSeverity;
59
+ category: CritiqueCategory;
60
+ comment: string;
61
+ /** Optional concrete suggestion. */
62
+ suggestion?: string;
63
+ /** Optional URLs the critic consulted to back up the critique. */
64
+ evidence?: CritiqueEvidence[];
65
+ }
66
+ export interface ConvergenceSignal {
67
+ blockerCount: number;
68
+ majorCount: number;
69
+ totalCritiques: number;
70
+ /** Levenshtein-ratio-like 0..1, where 0 = identical, 1 = totally different. */
71
+ diffFromPrevious: number;
72
+ shouldStop: boolean;
73
+ reason: "blockers_zero_and_stable" | "all_minor_or_praise" | "max_rounds" | "user_stop" | "user_force_continue" | "diff_below_threshold" | "running";
74
+ }
75
+ export interface Round {
76
+ round: number;
77
+ draft: Draft;
78
+ critiques: Critique[];
79
+ convergence: ConvergenceSignal;
80
+ }
81
+ export type AuthorRotation = "fixed" | "round-robin" | "best-critic";
82
+ export interface CheckpointContext {
83
+ round: number;
84
+ draft: Draft;
85
+ critiques: Critique[];
86
+ convergence: ConvergenceSignal;
87
+ }
88
+ export type CheckpointAction = "continue" | "stop" | "force-continue";
89
+ export type CheckpointFn = (ctx: CheckpointContext) => Promise<CheckpointAction>;
90
+ export interface IterateProgressEvent {
91
+ type: "v1_tournament_start" | "v1_candidate_done" | "v1_merge_start" | "v1_merge_done" | "round_start" | "argue_start" | "argue_done" | "revise_start" | "revise_done" | "round_done" | "checkpoint_pause" | "iterate_complete";
92
+ round?: number;
93
+ participant?: string;
94
+ data?: Record<string, unknown>;
95
+ }
96
+ export interface IterateConfig {
97
+ subject: IterateSubject;
98
+ format: IterateFormat;
99
+ /** The participant who merges v1 and main-authors v2+. Required. */
100
+ author: ArenaParticipant;
101
+ /** Critic participants. Each must have unique `name`. */
102
+ critics: ArenaParticipant[];
103
+ /** Default 5. */
104
+ maxRounds?: number;
105
+ /**
106
+ * v1 strategy.
107
+ * - "tournament" (default): author + all critics each write a candidate,
108
+ * author merges anonymized candidates into v1.
109
+ * - "single": author writes v1 alone (no tournament).
110
+ */
111
+ v1Strategy?: "tournament" | "single";
112
+ /** Default "fixed". */
113
+ authorRotation?: AuthorRotation;
114
+ /**
115
+ * If provided, called after each round; arena pauses for the response.
116
+ * Default: undefined (no checkpoints).
117
+ */
118
+ humanCheckpoint?: CheckpointFn;
119
+ /**
120
+ * Convergence rules. "default" applies built-in heuristics.
121
+ * Custom function returns true β†’ stop after this round.
122
+ */
123
+ convergenceStrategy?: "default" | ((ctx: {
124
+ round: number;
125
+ critiques: Critique[];
126
+ draft: Draft;
127
+ previousDraft?: Draft;
128
+ }) => boolean);
129
+ /** Minimum content length per draft (chars). Used both as length budget hint and to retry-on-shrinkage. Default 800 for document, 200 for code. */
130
+ minDraftLength?: number;
131
+ /**
132
+ * Whether critics get web_search/web_fetch tools during the argue phase.
133
+ * Requires SERPER_API_KEY / TAVILY_API_KEY / SEARXNG_URL in env. Default false.
134
+ * When true, each argue call becomes a tool-use loop (critics may search
135
+ * before producing critiques) β€” significantly more tokens and time, but
136
+ * dramatically reduces fabrication.
137
+ */
138
+ enableWebSearch?: boolean;
139
+ /** Cap tool-use rounds per critic per argue phase. Default 8. */
140
+ maxArgueToolRounds?: number;
141
+ signal?: AbortSignal;
142
+ onProgress?: (event: IterateProgressEvent) => void;
143
+ }
144
+ export type StoppedReason = "converged" | "max_rounds" | "user_stop" | "errored" | "aborted";
145
+ export interface IterateResult {
146
+ subject: IterateSubject;
147
+ format: IterateFormat;
148
+ rounds: Round[];
149
+ finalDraft: Draft;
150
+ stoppedAt: StoppedReason;
151
+ durationMs: number;
152
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Iterate mode β€” multi-model authoring loop.
3
+ *
4
+ * Author writes β†’ critics argue β†’ author revises β†’ ... β†’ final draft.
5
+ * v1 uses a tournament: every participant writes a candidate, then the
6
+ * author merges (anonymized inputs) into a single v1.
7
+ */
8
+ export {};
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SharedResearchLedger β€” append-only shared state across all arena rounds.
3
+ *
4
+ * Design constraints (from architecture doc):
5
+ * - All objects have stable IDs
6
+ * - Append-only: history preserved, never overwritten
7
+ * - Query layer supports latest-view aggregation
8
+ * - Supports by-id, by-claim, by-participant, by-round filtering
9
+ */
10
+ import type { SharedResearchLedger, ResearchDossier, EvidencePacket, ClaimRecord, ClaimChallenge, RequestedCheck, ClaimAdjudication } from "./types.js";
11
+ /**
12
+ * Manages the shared research ledger β€” the single source of truth
13
+ * for all evidence, claims, challenges, and adjudications in an arena session.
14
+ */
15
+ export declare class ArenaLedger {
16
+ private ledger;
17
+ private index;
18
+ constructor();
19
+ /** Check if ledger is approaching memory limits and log warnings */
20
+ private checkGrowth;
21
+ appendDossier(dossier: ResearchDossier): void;
22
+ appendEvidencePacket(packet: EvidencePacket): void;
23
+ appendClaim(claim: ClaimRecord): void;
24
+ appendChallenge(challenge: ClaimChallenge): void;
25
+ appendRequestedCheck(check: RequestedCheck): void;
26
+ appendAdjudication(adjudication: ClaimAdjudication): void;
27
+ /**
28
+ * Return a read-only reference to the full ledger.
29
+ *
30
+ * Note: `Readonly<>` is shallow β€” inner arrays are still mutable at the
31
+ * type level. Callers MUST NOT mutate the returned object; use the
32
+ * append* methods instead. A deep-freeze or structural clone can be
33
+ * added later if external consumers need a truly immutable snapshot.
34
+ */
35
+ getSnapshot(): Readonly<SharedResearchLedger>;
36
+ getClaimById(claimId: string): ClaimRecord | undefined;
37
+ getPacketById(packetId: string): EvidencePacket | undefined;
38
+ getAllClaims(): ClaimRecord[];
39
+ getClaimsByStatus(...statuses: ClaimRecord["status"][]): ClaimRecord[];
40
+ getClaimsByOwner(owner: string): ClaimRecord[];
41
+ getChallengesForClaim(claimId: string): ClaimChallenge[];
42
+ getPendingChecks(): RequestedCheck[];
43
+ getPendingChecksForClaim(claimId: string): RequestedCheck[];
44
+ getPacketsForClaim(claimId: string): EvidencePacket[];
45
+ getDossiers(): ResearchDossier[];
46
+ getDossierByParticipant(participant: string): ResearchDossier | undefined;
47
+ }
package/dist/ledger.js ADDED
@@ -0,0 +1,159 @@
1
+ /**
2
+ * SharedResearchLedger β€” append-only shared state across all arena rounds.
3
+ *
4
+ * Design constraints (from architecture doc):
5
+ * - All objects have stable IDs
6
+ * - Append-only: history preserved, never overwritten
7
+ * - Query layer supports latest-view aggregation
8
+ * - Supports by-id, by-claim, by-participant, by-round filtering
9
+ */
10
+ import { logger } from "@cjhyy/code-shell-core/extension";
11
+ /** Thresholds for memory warnings */
12
+ const WARN_CLAIMS = 50;
13
+ const WARN_PACKETS = 200;
14
+ const WARN_CHALLENGES = 100;
15
+ /**
16
+ * Manages the shared research ledger β€” the single source of truth
17
+ * for all evidence, claims, challenges, and adjudications in an arena session.
18
+ */
19
+ export class ArenaLedger {
20
+ ledger;
21
+ index;
22
+ constructor() {
23
+ this.ledger = {
24
+ dossiers: [],
25
+ evidencePackets: [],
26
+ toolTraces: [],
27
+ claims: [],
28
+ challenges: [],
29
+ requestedChecks: [],
30
+ adjudications: [],
31
+ };
32
+ this.index = {
33
+ claimsById: new Map(),
34
+ packetsById: new Map(),
35
+ requestsById: new Map(),
36
+ };
37
+ }
38
+ // ─── Append operations ──────────────────────────────────────
39
+ /** Check if ledger is approaching memory limits and log warnings */
40
+ checkGrowth() {
41
+ const claims = this.ledger.claims.length;
42
+ const packets = this.ledger.evidencePackets.length;
43
+ const challenges = this.ledger.challenges.length;
44
+ if (claims === WARN_CLAIMS || packets === WARN_PACKETS || challenges === WARN_CHALLENGES) {
45
+ logger.warn("arena.ledger_growth", { claims, packets, challenges, toolTraces: this.ledger.toolTraces.length });
46
+ }
47
+ }
48
+ appendDossier(dossier) {
49
+ this.ledger.dossiers.push(dossier);
50
+ for (const packet of dossier.evidencePackets) {
51
+ this.appendEvidencePacket(packet);
52
+ }
53
+ for (const trace of dossier.toolTrace) {
54
+ this.ledger.toolTraces.push(trace);
55
+ }
56
+ }
57
+ appendEvidencePacket(packet) {
58
+ // Deduplicate by packetId β€” reuse existing if same ID
59
+ if (!this.index.packetsById.has(packet.packetId)) {
60
+ this.ledger.evidencePackets.push(packet);
61
+ this.index.packetsById.set(packet.packetId, packet);
62
+ }
63
+ }
64
+ appendClaim(claim) {
65
+ this.ledger.claims.push(claim);
66
+ this.index.claimsById.set(claim.claimId, claim);
67
+ this.checkGrowth();
68
+ }
69
+ appendChallenge(challenge) {
70
+ this.ledger.challenges.push(challenge);
71
+ // Also add to the claim's challenges array. If the claimId doesn't resolve
72
+ // the challenge still lives in ledger.challenges but is invisible to any
73
+ // code reading claim.challenges directly β€” surface that inconsistency.
74
+ const claim = this.index.claimsById.get(challenge.claimId);
75
+ if (claim) {
76
+ claim.challenges.push(challenge);
77
+ }
78
+ else {
79
+ logger.warn("arena.challenge_for_unknown_claim", { claimId: challenge.claimId });
80
+ }
81
+ this.checkGrowth();
82
+ // Register any requested checks
83
+ if (challenge.requestedChecks) {
84
+ for (const check of challenge.requestedChecks) {
85
+ this.appendRequestedCheck(check);
86
+ }
87
+ }
88
+ }
89
+ appendRequestedCheck(check) {
90
+ if (!this.index.requestsById.has(check.requestId)) {
91
+ this.ledger.requestedChecks.push(check);
92
+ this.index.requestsById.set(check.requestId, check);
93
+ }
94
+ }
95
+ appendAdjudication(adjudication) {
96
+ this.ledger.adjudications.push(adjudication);
97
+ // Also attach to the claim
98
+ const claim = this.index.claimsById.get(adjudication.claimId);
99
+ if (claim) {
100
+ claim.adjudication = adjudication;
101
+ }
102
+ else {
103
+ logger.warn("arena.adjudication_for_unknown_claim", { claimId: adjudication.claimId });
104
+ }
105
+ }
106
+ // ─── Query operations ───────────────────────────────────────
107
+ /**
108
+ * Return a read-only reference to the full ledger.
109
+ *
110
+ * Note: `Readonly<>` is shallow β€” inner arrays are still mutable at the
111
+ * type level. Callers MUST NOT mutate the returned object; use the
112
+ * append* methods instead. A deep-freeze or structural clone can be
113
+ * added later if external consumers need a truly immutable snapshot.
114
+ */
115
+ getSnapshot() {
116
+ return this.ledger;
117
+ }
118
+ getClaimById(claimId) {
119
+ return this.index.claimsById.get(claimId);
120
+ }
121
+ getPacketById(packetId) {
122
+ return this.index.packetsById.get(packetId);
123
+ }
124
+ getAllClaims() {
125
+ return this.ledger.claims;
126
+ }
127
+ getClaimsByStatus(...statuses) {
128
+ const set = new Set(statuses);
129
+ return this.ledger.claims.filter((c) => set.has(c.status));
130
+ }
131
+ getClaimsByOwner(owner) {
132
+ return this.ledger.claims.filter((c) => c.owner === owner);
133
+ }
134
+ getChallengesForClaim(claimId) {
135
+ return this.ledger.challenges.filter((c) => c.claimId === claimId);
136
+ }
137
+ getPendingChecks() {
138
+ return this.ledger.requestedChecks;
139
+ }
140
+ getPendingChecksForClaim(claimId) {
141
+ return this.ledger.requestedChecks.filter((c) => c.claimId === claimId);
142
+ }
143
+ getPacketsForClaim(claimId) {
144
+ const claim = this.index.claimsById.get(claimId);
145
+ if (!claim)
146
+ return [];
147
+ return claim.evidencePacketIds
148
+ .map((id) => this.index.packetsById.get(id))
149
+ .filter((p) => p !== undefined);
150
+ }
151
+ getDossiers() {
152
+ return this.ledger.dossiers;
153
+ }
154
+ getDossierByParticipant(participant) {
155
+ // Return the latest dossier for this participant (supports multi-round)
156
+ const dossiers = this.ledger.dossiers.filter((d) => d.participant === participant);
157
+ return dossiers[dossiers.length - 1];
158
+ }
159
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Architecture lens β€” system boundaries, modularity, evolution path.
3
+ */
4
+ import type { ArenaLens } from "../types.js";
5
+ export declare const architectureLens: ArenaLens;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Architecture lens β€” system boundaries, modularity, evolution path.
3
+ */
4
+ export const architectureLens = {
5
+ name: "architecture",
6
+ label: "Architecture",
7
+ participantRole: "a system architect focused on boundaries, modularity, and evolution paths",
8
+ reviewerRole: "an architecture reviewer evaluating structural decisions and trade-offs",
9
+ moderatorRole: "a chief architect synthesizing architectural perspectives into design guidance",
10
+ summaryLabel: "Architecture Assessment",
11
+ criteria: [
12
+ "Module boundaries and responsibility separation",
13
+ "Coupling and cohesion",
14
+ "Extensibility and evolution path",
15
+ "Dependency management",
16
+ "Data flow and state management",
17
+ "API surface area and abstraction levels",
18
+ "Migration and backward compatibility strategy",
19
+ "Operational concerns (monitoring, debugging, deployment)",
20
+ ],
21
+ preferredFindingKinds: ["improvement", "risk", "question", "strength"],
22
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Engineering lens β€” code quality, correctness, performance, maintainability.
3
+ */
4
+ import type { ArenaLens } from "../types.js";
5
+ export declare const engineeringLens: ArenaLens;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Engineering lens β€” code quality, correctness, performance, maintainability.
3
+ */
4
+ export const engineeringLens = {
5
+ name: "engineering",
6
+ label: "Engineering",
7
+ participantRole: "a software engineer focused on code quality, correctness, and maintainability",
8
+ reviewerRole: "an engineering peer reviewer evaluating technical rigor and code health",
9
+ moderatorRole: "a tech lead synthesizing engineering perspectives into actionable conclusions",
10
+ summaryLabel: "Engineering Assessment",
11
+ criteria: [
12
+ "Code correctness and edge case handling",
13
+ "Error handling and resilience",
14
+ "Performance and scalability implications",
15
+ "API design and interface clarity",
16
+ "Test coverage and testability",
17
+ "Maintainability and readability",
18
+ "Security considerations",
19
+ "Backward compatibility",
20
+ ],
21
+ preferredFindingKinds: ["risk", "improvement", "strength", "question"],
22
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * General lens β€” broad analysis without a specific domain focus.
3
+ */
4
+ import type { ArenaLens } from "../types.js";
5
+ export declare const generalLens: ArenaLens;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * General lens β€” broad analysis without a specific domain focus.
3
+ */
4
+ export const generalLens = {
5
+ name: "general",
6
+ label: "General",
7
+ participantRole: "an analyst providing a broad, balanced perspective",
8
+ reviewerRole: "a peer reviewer evaluating clarity, logic, and completeness",
9
+ moderatorRole: "a neutral moderator synthesizing diverse perspectives into balanced conclusions",
10
+ summaryLabel: "General Assessment",
11
+ criteria: [
12
+ "Logical coherence and consistency",
13
+ "Completeness of analysis",
14
+ "Trade-off identification",
15
+ "Assumption clarity",
16
+ "Evidence quality",
17
+ "Actionability of recommendations",
18
+ ],
19
+ preferredFindingKinds: ["strength", "risk", "question", "improvement"],
20
+ };