@goodandready/dsh-moa 0.2.70 → 0.2.72

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.
@@ -0,0 +1,233 @@
1
+ /**
2
+ * AST-Level Composite Merge Integration via dsh-context-lens (#248)
3
+ * Semantically merges candidate files at the AST/declaration level (deduplicating imports,
4
+ * combining type signatures, JSDoc comments, and function bodies) using dsh-context-lens
5
+ * when available via ctx.reflect / service(ctx, 'contextLens'), with a clean fallback
6
+ * to structured declaration merge and standard string composite directives.
7
+ */
8
+
9
+ import vm from 'node:vm'
10
+ import { service } from './services.js'
11
+ import { logger } from './logger.js'
12
+ import { buildCompositeBlockDirectives } from './moa-multi-judge.js'
13
+ import { extractFileBlocks } from './workspace/fs.js'
14
+
15
+ /**
16
+ * Checks whether dsh-context-lens service is available on the Cordis context.
17
+ */
18
+ export function resolveContextLensService(ctx, explicitLens = null) {
19
+ if (explicitLens) return explicitLens
20
+ const fromSvc = service(ctx, 'contextLens')
21
+ if (fromSvc) return fromSvc
22
+ try {
23
+ if (ctx?.reflect && typeof ctx.reflect.get === 'function') {
24
+ const refl = ctx.reflect.get('contextLens') || ctx.reflect.get('dsh-context-lens')
25
+ if (refl) return refl
26
+ }
27
+ } catch (err) {
28
+ logger.debug('[dsh-moa:ast] context-lens reflection error:', err?.message || err)
29
+ }
30
+ return null
31
+ }
32
+
33
+ /**
34
+ * Parses top-level imports, declarations, and JSDoc from JS/TS source code.
35
+ */
36
+ export function parseModuleDeclarations(source = '') {
37
+ const lines = String(source || '').split(/\r?\n/)
38
+ const imports = []
39
+ const bodyLines = []
40
+
41
+ for (const line of lines) {
42
+ const trimmed = line.trim()
43
+ if (/^import\s+/.test(trimmed) && (trimmed.endsWith(';') || trimmed.includes(' from '))) {
44
+ imports.push(trimmed.replace(/;$/, ''))
45
+ } else {
46
+ bodyLines.push(line)
47
+ }
48
+ }
49
+
50
+ const bodyText = bodyLines.join('\n')
51
+ const declarations = new Map()
52
+ const declRegex = /((?:\/\*\*[\s\S]*?\*\/\s*)?)(export\s+(?:async\s+)?(?:function|class|const|let|type|interface)\s+([A-Za-z0-9_$]+)[\s\S]*?)(?=(?:\n(?:\/\*\*[\s\S]*?\*\/\s*)?export\s+(?:async\s+)?(?:function|class|const|let|type|interface)\s+)|$)/g
53
+ let match
54
+ while ((match = declRegex.exec(bodyText)) !== null) {
55
+ const jsdoc = (match[1] || '').trim()
56
+ const code = (match[2] || '').trim()
57
+ const name = match[3]
58
+ if (name) {
59
+ declarations.set(name, { name, jsdoc, code })
60
+ }
61
+ }
62
+
63
+ return { imports, declarations, rawBody: bodyText.trim() }
64
+ }
65
+
66
+ /**
67
+ * Validates JavaScript syntax using node:vm compileFunction (ignoring TypeScript-only syntax).
68
+ */
69
+ export function isValidJsAstSyntax(code = '', filename = 'module.js') {
70
+ if (/\.tsx?$/i.test(filename)) return true
71
+ const stripped = String(code)
72
+ .replace(/^\s*import\s+[\s\S]*?from\s+['"][^'"]+['"];?\s*$/gm, '')
73
+ .replace(/^\s*import\s+['"][^'"]+['"];?\s*$/gm, '')
74
+ .replace(/^\s*export\s+default\s+/gm, 'const __default_export__ = ')
75
+ .replace(/^\s*export\s+(async\s+function|function|class|const|let|var)\b/gm, '$1')
76
+ .replace(/^\s*export\s*\{[^}]*\};?\s*$/gm, '')
77
+ try {
78
+ new vm.Script(stripped, { filename })
79
+ return true
80
+ } catch {
81
+ return false
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Merges multiple candidate versions of the same file at declaration/AST level:
87
+ * - Deduplicates all import statements without collisions
88
+ * - Combines the richest JSDoc documentation, type signature, and implementation per symbol
89
+ */
90
+ export async function mergeCandidateAstFiles(candidates = [], options = {}) {
91
+ const lens = resolveContextLensService(options.ctx, options.contextLens)
92
+ const byPath = new Map()
93
+
94
+ for (const cand of candidates) {
95
+ if (!cand || cand.ok === false) continue
96
+ const files = Array.isArray(cand.files) && cand.files.length > 0
97
+ ? cand.files
98
+ : extractFileBlocks(cand.text || '')
99
+ for (const f of files) {
100
+ const rel = f.relativePath || f.path
101
+ if (!rel || !f.content) continue
102
+ if (!byPath.has(rel)) byPath.set(rel, [])
103
+ byPath.get(rel).push({
104
+ candidateIndex: cand.index,
105
+ label: cand.label,
106
+ content: f.content,
107
+ })
108
+ }
109
+ }
110
+
111
+ const mergedFiles = []
112
+ for (const [relativePath, versions] of byPath.entries()) {
113
+ if (versions.length === 1) {
114
+ mergedFiles.push({
115
+ relativePath,
116
+ content: versions[0].content,
117
+ contributors: [versions[0].label || `Candidate ${versions[0].candidateIndex}`],
118
+ usedContextLens: Boolean(lens),
119
+ })
120
+ continue
121
+ }
122
+
123
+ // If dsh-context-lens provides a native mergeAst or analyzeAst method, use it first
124
+ if (lens && typeof lens.mergeAst === 'function') {
125
+ try {
126
+ const mergedCode = await lens.mergeAst({
127
+ filePath: relativePath,
128
+ versions: versions.map((v) => v.content),
129
+ })
130
+ if (mergedCode && typeof mergedCode === 'string' && isValidJsAstSyntax(mergedCode, relativePath)) {
131
+ mergedFiles.push({
132
+ relativePath,
133
+ content: mergedCode,
134
+ contributors: versions.map((v) => v.label || `Candidate ${v.candidateIndex}`),
135
+ usedContextLens: true,
136
+ })
137
+ continue
138
+ }
139
+ } catch (err) {
140
+ logger.warn(`[dsh-moa:ast-merge] contextLens.mergeAst failed for ${relativePath}, using declaration AST merger:`, err?.message || err)
141
+ }
142
+ }
143
+
144
+ // Declaration-level AST synthesis: deduplicate imports & merge JSDoc + bodies
145
+ const uniqueImports = new Set()
146
+ const mergedDecls = new Map()
147
+
148
+ for (const ver of versions) {
149
+ const parsed = parseModuleDeclarations(ver.content)
150
+ for (const imp of parsed.imports) uniqueImports.add(imp)
151
+
152
+ for (const [symName, decl] of parsed.declarations.entries()) {
153
+ if (!mergedDecls.has(symName)) {
154
+ mergedDecls.set(symName, { ...decl })
155
+ } else {
156
+ const existing = mergedDecls.get(symName)
157
+ // Keep the richest JSDoc comment from any candidate
158
+ if (decl.jsdoc && decl.jsdoc.length > (existing.jsdoc || '').length) {
159
+ existing.jsdoc = decl.jsdoc
160
+ }
161
+ // Prefer more comprehensive implementation if existing is a short stub
162
+ if (decl.code.length > existing.code.length && existing.code.length < 120) {
163
+ existing.code = decl.code
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ let assembled = ''
170
+ if (uniqueImports.size > 0) {
171
+ assembled += Array.from(uniqueImports).map((i) => `${i};`).join('\n') + '\n\n'
172
+ }
173
+ if (mergedDecls.size > 0) {
174
+ assembled += Array.from(mergedDecls.values())
175
+ .map((d) => (d.jsdoc ? `${d.jsdoc}\n${d.code}` : d.code))
176
+ .join('\n\n') + '\n'
177
+ } else {
178
+ assembled = versions[0].content
179
+ }
180
+
181
+ if (!isValidJsAstSyntax(assembled, relativePath)) {
182
+ assembled = versions[0].content
183
+ }
184
+
185
+ mergedFiles.push({
186
+ relativePath,
187
+ content: assembled,
188
+ contributors: versions.map((v) => v.label || `Candidate ${v.candidateIndex}`),
189
+ usedContextLens: Boolean(lens),
190
+ })
191
+ }
192
+
193
+ return mergedFiles
194
+ }
195
+
196
+ /**
197
+ * Builds the AST-level composite merge directive for the Judge/Curator.
198
+ * Gracefully falls back to standard buildCompositeBlockDirectives when dsh-context-lens is absent
199
+ * and no multi-version AST declarations are detected.
200
+ */
201
+ export async function buildAstCompositeMergeDirective(candidates = [], options = {}) {
202
+ const lens = resolveContextLensService(options.ctx, options.contextLens)
203
+ const mergedFiles = await mergeCandidateAstFiles(candidates, options)
204
+ const baseDirective = buildCompositeBlockDirectives(candidates, options)
205
+
206
+ if (!lens && mergedFiles.length === 0) {
207
+ return {
208
+ degraded: true,
209
+ usedContextLens: false,
210
+ mergedFiles: [],
211
+ directive: baseDirective,
212
+ }
213
+ }
214
+
215
+ const astBlocks = mergedFiles
216
+ .map((mf) => `#### AST Pre-Merged Preview: \`${mf.relativePath}\` (Contributors: ${mf.contributors.join(', ')})\n\`\`\`js path=${mf.relativePath}\n${mf.content.trim()}\n\`\`\``)
217
+ .join('\n\n')
218
+
219
+ const directive = [
220
+ baseDirective.trim(),
221
+ `\n### 🌳 AST-Level Semantic Composite Merge (${lens ? 'via `dsh-context-lens`' : 'Fallback Declaration Merger'}):`,
222
+ 'Imports have been deduplicated and declarations (JSDoc, type signatures, and function bodies) have been aligned across candidates:',
223
+ astBlocks,
224
+ ].join('\n\n')
225
+
226
+ return {
227
+ degraded: !lens,
228
+ usedContextLens: Boolean(lens),
229
+ mergedFiles,
230
+ directive,
231
+ }
232
+ }
233
+
package/lib/moa-schema.js CHANGED
@@ -72,6 +72,13 @@ export const PresetSchema = z.object({
72
72
  self_healing_command: z.string().default("npm test"),
73
73
  visual_moa_enabled: z.boolean().default(false),
74
74
  elo_ranking_enabled: z.boolean().default(true),
75
+ ast_composite_merge_enabled: z.boolean().default(false),
76
+ speculative_moa_enabled: z.boolean().default(false),
77
+ speculative_draft_model: ModelSlotSchema.default({ provider: "", model: "" }),
78
+ adversarial_tdd_enabled: z.boolean().default(false),
79
+ local_preflight_check: z.boolean().default(true),
80
+ early_exit_consensus_enabled: z.boolean().default(false),
81
+ early_exit_threshold: z.number().default(0.95),
75
82
  })
76
83
 
77
84
  export const Config = z.object({
@@ -90,6 +97,13 @@ export const Config = z.object({
90
97
  self_healing_command: z.string().default("npm test").volatile(),
91
98
  visual_moa_enabled: z.boolean().default(false).volatile(),
92
99
  elo_ranking_enabled: z.boolean().default(true).volatile(),
100
+ ast_composite_merge_enabled: z.boolean().default(false).volatile(),
101
+ speculative_moa_enabled: z.boolean().default(false).volatile(),
102
+ speculative_draft_model: ModelSlotSchema.default({ provider: "", model: "" }).volatile(),
103
+ adversarial_tdd_enabled: z.boolean().default(false).volatile(),
104
+ local_preflight_check: z.boolean().default(true).volatile(),
105
+ early_exit_consensus_enabled: z.boolean().default(false).volatile(),
106
+ early_exit_threshold: z.number().default(0.95).volatile(),
93
107
  smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
94
108
  prices: z.dict(PriceRow).default({}).volatile(),
95
109
  presets: z.array(PresetSchema).default([
@@ -151,3 +165,4 @@ export function plainConfig(value) {
151
165
  }
152
166
  return value
153
167
  }
168
+
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Speculative MoA with Fast-Draft Model and Multi-Auditor Diffs (#247)
3
+ * Generates a rapid initial draft using speculative_draft_model, dispatches the draft
4
+ * to reference models in parallel as diff auditors (producing targeted SEARCH/REPLACE
5
+ * or unified diff hunks), applies valid hunks to the draft, and gracefully falls back
6
+ * to standard parallel MoA if the draft model fails.
7
+ */
8
+
9
+ import { logger } from './logger.js'
10
+ import { slotLabel } from './moa-prompts.js'
11
+ import { estimateTokenCost } from './pricing.js'
12
+
13
+ /**
14
+ * Applies SEARCH/REPLACE or unified diff patches onto a base draft text.
15
+ *
16
+ * @param {string} draftText
17
+ * @param {string} patchText
18
+ * @returns {{ patchedText: string, appliedCount: number }}
19
+ */
20
+ export function applyAuditorPatchHunks(draftText = '', patchText = '') {
21
+ let result = String(draftText || '')
22
+ let appliedCount = 0
23
+
24
+ // 1. Support SEARCH / REPLACE blocks:
25
+ // <<<<<<< SEARCH
26
+ // ...
27
+ // =======
28
+ // ...
29
+ // >>>>>>> REPLACE
30
+ const searchReplaceRegex = /<{5,9}\s*SEARCH\r?\n([\s\S]*?)\r?\n={5,9}\r?\n([\s\S]*?)\r?\n>{5,9}\s*REPLACE/g
31
+ let srMatch
32
+ while ((srMatch = searchReplaceRegex.exec(patchText)) !== null) {
33
+ const searchBlock = srMatch[1]
34
+ const replaceBlock = srMatch[2]
35
+ if (searchBlock && result.includes(searchBlock)) {
36
+ result = result.replace(searchBlock, replaceBlock)
37
+ appliedCount++
38
+ }
39
+ }
40
+
41
+ // 2. Support unified diff hunks (- oldLine / + newLine)
42
+ if (appliedCount === 0 && /```diff[\r\n]+([\s\S]*?)```/i.test(patchText)) {
43
+ const diffMatch = /```diff[\r\n]+([\s\S]*?)```/i.exec(patchText)
44
+ const diffLines = diffMatch[1].split(/\r?\n/)
45
+ const removed = []
46
+ const added = []
47
+ for (const line of diffLines) {
48
+ if (line.startsWith('---') || line.startsWith('+++') || line.startsWith('@@')) continue
49
+ if (line.startsWith('-')) removed.push(line.slice(1))
50
+ else if (line.startsWith('+')) added.push(line.slice(1))
51
+ }
52
+ if (removed.length > 0) {
53
+ const targetChunk = removed.join('\n')
54
+ const replacementChunk = added.join('\n')
55
+ if (result.includes(targetChunk)) {
56
+ result = result.replace(targetChunk, replacementChunk)
57
+ appliedCount++
58
+ }
59
+ }
60
+ }
61
+
62
+ return { patchedText: result, appliedCount }
63
+ }
64
+
65
+ /**
66
+ * Executes Speculative MoA:
67
+ * 1. Calls fast draft model (`speculative_draft_model` or first reference slot).
68
+ * 2. Dispatches draft to reference models in parallel as diff auditors.
69
+ * 3. Applies valid diff hunks onto draft so each auditor produces a refined candidate output.
70
+ * 4. If the draft model fails, returns `{ fallbackToStandard: true }` so the caller runs standard parallel MoA.
71
+ */
72
+ export async function runSpeculativeMoa(references = [], messages = [], options = {}, callLlm, onProgress) {
73
+ const draftSlot = (options.draftModel?.provider && options.draftModel?.model)
74
+ ? options.draftModel
75
+ : references[0]
76
+
77
+ if (!draftSlot || !draftSlot.provider || !draftSlot.model) {
78
+ return { fallbackToStandard: true, reason: 'No valid speculative draft model configured' }
79
+ }
80
+
81
+ const draftLabel = slotLabel(draftSlot)
82
+ if (typeof onProgress === 'function') {
83
+ onProgress(`⚡ *Speculative MoA: Generating fast initial draft with \`${draftLabel}\`...*\n`)
84
+ }
85
+
86
+ const draftStart = Date.now()
87
+ let draftText = ''
88
+ let draftUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 }
89
+
90
+ try {
91
+ const draftRes = await callLlm({
92
+ provider: draftSlot.provider,
93
+ model: draftSlot.model,
94
+ messages,
95
+ systemPrompt: options.systemPrompt,
96
+ temperature: options.temperature ?? 0.3,
97
+ maxTokens: options.maxTokens ?? 4096,
98
+ signal: options.signal,
99
+ })
100
+ draftText = typeof draftRes === 'string' ? draftRes : (draftRes?.content || draftRes?.text || '')
101
+ if (!draftText || !draftText.trim()) {
102
+ throw new Error('Empty draft response from speculative draft model')
103
+ }
104
+ const rawUsage = (typeof draftRes === 'object' && draftRes?.usage) ? draftRes.usage : {
105
+ inputTokens: 100,
106
+ outputTokens: Math.max(1, Math.round(draftText.length / 4)),
107
+ }
108
+ draftUsage = estimateTokenCost(draftSlot, rawUsage, options.prices || {})
109
+ } catch (err) {
110
+ logger.warn(`[dsh-moa:speculative] Draft model ${draftLabel} failed (${err?.message || err}); falling back to standard parallel MoA.`)
111
+ if (typeof onProgress === 'function') {
112
+ onProgress(`⚠️ *Speculative draft model \`${draftLabel}\` failed; falling back to standard parallel MoA...*\n`)
113
+ }
114
+ return { fallbackToStandard: true, reason: err?.message || String(err) }
115
+ }
116
+
117
+ const draftDurationMs = Date.now() - draftStart
118
+ if (typeof onProgress === 'function') {
119
+ onProgress(`🔍 *Speculative MoA: Dispatching draft to ${references.length} parallel diff auditor(s)...*\n`)
120
+ }
121
+
122
+ const lastUserPrompt = messages[messages.length - 1]?.content || ''
123
+ const auditTasks = references.map(async (slot, idx) => {
124
+ const candidateIndex = idx + 1
125
+ const label = slotLabel(slot)
126
+ const auditStart = Date.now()
127
+
128
+ const auditPrompt = [
129
+ `You are Speculative Auditor #${candidateIndex} (${label}).`,
130
+ `Review the fast draft below for the user task and provide targeted diff fixes (logic bugs, edge cases, types, security) using SEARCH/REPLACE blocks or \`\`\`diff hunks.`,
131
+ `\n### Original User Task:\n${lastUserPrompt}`,
132
+ `\n### Fast Draft Solution (${draftLabel}):\n${draftText}`,
133
+ `\nOutput your audit notes and any SEARCH/REPLACE or \`\`\`diff blocks needed to perfect the draft.`,
134
+ ].join('\n')
135
+
136
+ try {
137
+ const auditRes = await callLlm({
138
+ provider: slot.provider,
139
+ model: slot.model,
140
+ messages: [{ role: 'user', content: auditPrompt }],
141
+ temperature: 0.2,
142
+ maxTokens: Math.min(options.maxTokens ?? 2048, 2048),
143
+ signal: options.signal,
144
+ })
145
+ const auditText = typeof auditRes === 'string' ? auditRes : (auditRes?.content || auditRes?.text || '')
146
+ const rawAuditUsage = (typeof auditRes === 'object' && auditRes?.usage) ? auditRes.usage : {
147
+ inputTokens: Math.max(1, Math.round(auditPrompt.length / 4)),
148
+ outputTokens: Math.max(1, Math.round(auditText.length / 4)),
149
+ }
150
+ const auditCost = estimateTokenCost(slot, rawAuditUsage, options.prices || {})
151
+
152
+ const { patchedText, appliedCount } = applyAuditorPatchHunks(draftText, auditText)
153
+ const combinedText = appliedCount > 0
154
+ ? `${patchedText}\n\n#### Speculative Auditor Diff Notes (${label}, ${appliedCount} hunk(s) applied):\n${auditText}`
155
+ : `${draftText}\n\n#### Speculative Auditor Review (${label}):\n${auditText}`
156
+
157
+ return {
158
+ index: candidateIndex,
159
+ slot,
160
+ label,
161
+ text: combinedText,
162
+ speculativeAppliedHunks: appliedCount,
163
+ speculativeDraftModel: draftLabel,
164
+ usage: {
165
+ inputTokens: auditCost.inputTokens + (idx === 0 ? draftUsage.inputTokens : 0),
166
+ outputTokens: auditCost.outputTokens + (idx === 0 ? draftUsage.outputTokens : 0),
167
+ totalTokens: auditCost.totalTokens + (idx === 0 ? draftUsage.totalTokens : 0),
168
+ },
169
+ costUsd: Number((auditCost.costUsd + (idx === 0 ? draftUsage.costUsd : 0)).toFixed(6)),
170
+ durationMs: (Date.now() - auditStart) + (idx === 0 ? draftDurationMs : 0),
171
+ ok: true,
172
+ }
173
+ } catch (err) {
174
+ return {
175
+ index: candidateIndex,
176
+ slot,
177
+ label,
178
+ text: draftText,
179
+ error: err?.message || String(err),
180
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
181
+ costUsd: 0,
182
+ durationMs: Date.now() - auditStart,
183
+ ok: false,
184
+ }
185
+ }
186
+ })
187
+
188
+ const referenceOutputs = await Promise.all(auditTasks)
189
+ return {
190
+ fallbackToStandard: false,
191
+ draftModel: draftLabel,
192
+ draftText,
193
+ referenceOutputs,
194
+ }
195
+ }
package/lib/moa-swarm.js CHANGED
@@ -114,17 +114,23 @@ export async function cleanupSwarmWorktrees(cwd, runId, worktrees = []) {
114
114
  if (wt.isGitWorktree) {
115
115
  try {
116
116
  await execFileAsync('git', ['worktree', 'remove', '--force', wt.worktreePath], { cwd, timeout: 8000 })
117
- } catch {}
117
+ } catch (err) {
118
+ logger.debug('[dsh-moa:swarm] cleanup worktree remove error:', err?.message || err)
119
+ }
118
120
  }
119
121
  try {
120
122
  await fs.rm(wt.worktreePath, { recursive: true, force: true })
121
- } catch {}
123
+ } catch (err) {
124
+ logger.debug('[dsh-moa:swarm] cleanup rm error:', err?.message || err)
125
+ }
122
126
  }
123
127
 
124
128
  if (await isGitRepository(cwd)) {
125
129
  try {
126
130
  await execFileAsync('git', ['worktree', 'prune'], { cwd, timeout: 5000 })
127
- } catch {}
131
+ } catch (err) {
132
+ logger.debug('[dsh-moa:swarm] cleanup prune error:', err?.message || err)
133
+ }
128
134
  }
129
135
 
130
136
  const safeRunId = String(runId || '').replace(/[^a-zA-Z0-9_-]/g, '')
@@ -132,7 +138,9 @@ export async function cleanupSwarmWorktrees(cwd, runId, worktrees = []) {
132
138
  const swarmRoot = path.join(cwd, '.moa', `swarm-${safeRunId}`)
133
139
  try {
134
140
  await fs.rm(swarmRoot, { recursive: true, force: true })
135
- } catch {}
141
+ } catch (err) {
142
+ logger.debug('[dsh-moa:swarm] cleanup swarmRoot error:', err?.message || err)
143
+ }
136
144
  }
137
145
  }
138
146
 
@@ -217,3 +225,4 @@ export async function runSubagentSwarm(references, messages, options = {}, callL
217
225
  await cleanupSwarmWorktrees(cwd, runId, worktrees)
218
226
  }
219
227
  }
228
+
@@ -51,6 +51,12 @@ export const COMPANION_PLUGINS = [
51
51
  url: 'https://github.com/GooDAnDReaDY/dsh-live-canvas',
52
52
  description: 'Isolated live HTML/CSS/SVG preview environment and screenshot capture',
53
53
  },
54
+ {
55
+ id: 'dsh-context-lens',
56
+ name: 'DSH Context Lens',
57
+ url: 'https://github.com/GooDAnDReaDY/dsh-context-lens',
58
+ description: 'Semantic AST parsing and symbol-level composite code merging',
59
+ },
54
60
  ]
55
61
 
56
62
  /**
@@ -1,3 +1,5 @@
1
+ import { runSpeculativeMoa } from '../moa-speculative.js'
2
+ import { runAdversarialTddLoop } from '../moa-adversarial-tdd.js'
1
3
  import { assignAdaptivePersonas } from '../moa-adaptive-personas.js'
2
4
  import { runSubagentSwarm } from '../moa-swarm.js'
3
5
  import { runReferencesParallel } from '../moa-candidates.js'
@@ -37,7 +39,29 @@ export async function stageCandidateRound1({
37
39
  ? assignAdaptivePersonas(effectiveReferences, typeof lastMsgText === 'string' ? lastMsgText : '').references
38
40
  : effectiveReferences
39
41
 
40
- const referenceOutputs = preset?.subagent_swarm_enabled
42
+ let referenceOutputs = null
43
+ if (preset?.speculative_moa_enabled && !preset?.subagent_swarm_enabled) {
44
+ const specRes = await runSpeculativeMoa(
45
+ adaptiveRefs,
46
+ enrichedMessages,
47
+ {
48
+ draftModel: preset.speculative_draft_model,
49
+ systemPrompt: candidateSystemPrompt,
50
+ temperature: refTemp,
51
+ maxTokens,
52
+ prices,
53
+ signal,
54
+ },
55
+ callLlm,
56
+ onProgress
57
+ )
58
+ if (!specRes.fallbackToStandard && Array.isArray(specRes.referenceOutputs)) {
59
+ referenceOutputs = specRes.referenceOutputs
60
+ }
61
+ }
62
+
63
+ if (!referenceOutputs) {
64
+ referenceOutputs = preset?.subagent_swarm_enabled
41
65
  ? await runSubagentSwarm(adaptiveRefs, enrichedMessages, { temperature: refTemp, maxTokens, signal, cwd, runId }, callLlm, onProgress)
42
66
  : await runReferencesParallel(
43
67
  adaptiveRefs,
@@ -59,6 +83,7 @@ export async function stageCandidateRound1({
59
83
  callLlm,
60
84
  onProgress
61
85
  )
86
+ }
62
87
 
63
88
  if (signal?.aborted) {
64
89
  await cleanMoaWorkspaces(cwd, { runId })
@@ -104,5 +129,20 @@ export async function stageCandidateRound1({
104
129
  onProgress,
105
130
  })
106
131
 
107
- return { ok: true, referenceOutputs, successfulRefs }
132
+ let adversarialTddResult = null
133
+ if (preset?.adversarial_tdd_enabled) {
134
+ adversarialTddResult = await runAdversarialTddLoop({
135
+ cwd,
136
+ runId,
137
+ userPrompt: typeof lastMsgText === "string" ? lastMsgText : "",
138
+ referenceOutputs,
139
+ preset,
140
+ callLlm,
141
+ execFn,
142
+ onProgress,
143
+ signal,
144
+ })
145
+ }
146
+
147
+ return { ok: true, referenceOutputs, successfulRefs, adversarialTddResult }
108
148
  }
@@ -38,6 +38,9 @@ export async function stageProjectContext({
38
38
  const isBlindEvaluation = Boolean(preset?.blind_evaluation)
39
39
  const isPeerCritiqueEnabled = Boolean(preset?.peer_critique_enabled)
40
40
  const allowCandidateOverride = Boolean(preset?.allow_candidate_override)
41
+ const localPreflightCheck = preset?.local_preflight_check !== false
42
+ const earlyExitConsensusEnabled = Boolean(preset?.early_exit_consensus_enabled)
43
+ const earlyExitThreshold = typeof preset?.early_exit_threshold === 'number' ? preset.early_exit_threshold : 0.95
41
44
 
42
45
  const collectedCtx = await collectProjectContext(cwd, 16000)
43
46
  const isRefinement = Boolean(collectedCtx?.files?.length > 0 && isRefinementTask(userPrompt, collectedCtx.files))
@@ -123,5 +126,7 @@ export async function stageProjectContext({
123
126
  allowCandidateOverride, collectedCtx, isRefinement, projectContext,
124
127
  effectiveReferences, quotaSubstitutions, priorTurnBaseline, candidateSystemPrompt,
125
128
  needsQuestions, enrichedMessages,
129
+ localPreflightCheck, earlyExitConsensusEnabled, earlyExitThreshold,
126
130
  }
127
131
  }
132
+