@goodandready/dsh-moa 0.2.10 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/docs/README.ru.md +2 -0
- package/docs/design/DESIGN.md +2 -0
- package/docs/plans/55-resilience-plan.md +33 -0
- package/lib/history.js +87 -35
- package/lib/index.js +5 -2
- package/lib/moa-parser.js +181 -0
- package/lib/moa-prompts.js +251 -0
- package/lib/moa-runner.js +146 -493
- package/package.json +1 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// lib/moa-prompts.js
|
|
2
|
+
// Prompts, evaluation rubrics, and message sanitizer for @goodandready/dsh-moa.
|
|
3
|
+
|
|
4
|
+
import { stripOrSummarizeCode } from './moa-parser.js'
|
|
5
|
+
|
|
6
|
+
export const SYSTEM_ROLE_PROPOSER = `You are a specialized expert developer agent participating as an independent advisor in a Mixture of Agents (MoA) ensemble.
|
|
7
|
+
Your task is to provide the highest-quality, robust, complete, production-ready solution to the user request.
|
|
8
|
+
Write clean, modern, fully functional code without placeholders or shortcuts.
|
|
9
|
+
When generating files for a project, explicitly specify file paths using fenced code blocks with file annotations, e.g.:
|
|
10
|
+
\`\`\`html file="index.html"
|
|
11
|
+
\`\`\`
|
|
12
|
+
\`\`\`javascript file="script.js"
|
|
13
|
+
\`\`\`
|
|
14
|
+
\`\`\`css file="style.css"
|
|
15
|
+
\`\`\``
|
|
16
|
+
|
|
17
|
+
export const ANTIPATTERNS_RUBRIC = `### 🚫 Strict Antipatterns Evaluation Checklist
|
|
18
|
+
Penalize and strictly downgrade candidates exhibiting any of the following flaws:
|
|
19
|
+
1. 🚫 Lazy Code & Placeholders:
|
|
20
|
+
- Phrases like "// ... rest of code unchanged", "/* TODO: implement */", incomplete functions or stubs returning null/mock without notice.
|
|
21
|
+
2. 🚫 Blind Mocking:
|
|
22
|
+
- Hardcoded dummy arrays instead of real dynamic logic, user input handling, or real API integration.
|
|
23
|
+
3. 🚫 Silent Failures & Missing Error Handling:
|
|
24
|
+
- Missing try/catch around async calls, fetch, JSON.parse; lack of user-facing fallback or retry states.
|
|
25
|
+
4. 🚫 AI Slop UI & Poor Ergonomics:
|
|
26
|
+
- Generic purple/cyan gradients on pure black backgrounds, blurry drop-shadows without borders, lack of typographic hierarchy, low contrast (e.g. light gray text on white).
|
|
27
|
+
5. 🚫 Missing UI States:
|
|
28
|
+
- Lack of loading state (spinner/skeleton), error display with retry action, or empty state when no data exists.
|
|
29
|
+
6. 🚫 Broken Layout & Mobile Incompatibility:
|
|
30
|
+
- Fixed pixel widths (e.g. width: 800px) overflowing small viewports; unscrollable modal dialogs.
|
|
31
|
+
7. 🚫 Monolithic God Objects & Overengineering:
|
|
32
|
+
- Dumping 1000+ lines into a single unmaintainable file, or building 10+ abstraction layers for a 2-function task.
|
|
33
|
+
8. 🚫 Context Amnesia & Regressions:
|
|
34
|
+
- Dropping or breaking previously functioning project features while adding new code.`
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Formats a provider + model slot into a readable string key.
|
|
38
|
+
*/
|
|
39
|
+
export function slotLabel(slot) {
|
|
40
|
+
if (!slot) return 'unknown'
|
|
41
|
+
if (typeof slot === 'string') return slot
|
|
42
|
+
if (slot.provider && slot.model) return `${slot.provider}:${slot.model}`
|
|
43
|
+
return slot.model || slot.provider || 'unknown'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extracts and sanitizes conversation history for candidate models.
|
|
48
|
+
* Robust against complex DSH message content types (strings, part arrays, objects, tool calls).
|
|
49
|
+
*/
|
|
50
|
+
export function cleanAdvisoryMessages(messages = [], maxCharBudget = 24000) {
|
|
51
|
+
if (!Array.isArray(messages)) return []
|
|
52
|
+
|
|
53
|
+
const trimmed = []
|
|
54
|
+
for (const msg of messages) {
|
|
55
|
+
if (!msg || typeof msg !== 'object') continue
|
|
56
|
+
const role = msg.role
|
|
57
|
+
if (role !== 'user' && role !== 'assistant') continue
|
|
58
|
+
|
|
59
|
+
let text = ''
|
|
60
|
+
if (typeof msg.content === 'string') {
|
|
61
|
+
text = msg.content
|
|
62
|
+
} else if (Array.isArray(msg.content)) {
|
|
63
|
+
text = msg.content
|
|
64
|
+
.filter((part) => part && typeof part === 'object' && part.type === 'text' && typeof part.text === 'string')
|
|
65
|
+
.map((part) => part.text)
|
|
66
|
+
.join('\n')
|
|
67
|
+
} else if (msg.content && typeof msg.content === 'object') {
|
|
68
|
+
if (typeof msg.content.text === 'string') {
|
|
69
|
+
text = msg.content.text
|
|
70
|
+
} else if (typeof msg.content.content === 'string') {
|
|
71
|
+
text = msg.content.content
|
|
72
|
+
}
|
|
73
|
+
} else if (typeof msg.text === 'string') {
|
|
74
|
+
text = msg.text
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
text = text.trim()
|
|
78
|
+
if (!text) continue
|
|
79
|
+
|
|
80
|
+
if (text.length > maxCharBudget) {
|
|
81
|
+
const head = text.slice(0, Math.floor(maxCharBudget * 0.7))
|
|
82
|
+
const tail = text.slice(-Math.floor(maxCharBudget * 0.3))
|
|
83
|
+
text = `${head}\n... [trimmed ${text.length - maxCharBudget} characters] ...\n${tail}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
trimmed.push({ role, content: text })
|
|
87
|
+
}
|
|
88
|
+
return trimmed
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Detects whether the user's prompt is a short/vague creative task requiring clarification.
|
|
93
|
+
*/
|
|
94
|
+
export function isBroadPromptRequiringQuestions(userPrompt = '', messages = []) {
|
|
95
|
+
if (!userPrompt || typeof userPrompt !== 'string') return false
|
|
96
|
+
const p = userPrompt.trim()
|
|
97
|
+
const wordCount = p.split(/\s+/).length
|
|
98
|
+
|
|
99
|
+
if (/^(да|нет|1|2|3|4|ок|погнали|давай|yes|no)\b/i.test(p) && wordCount <= 5) {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const hasRecentQuestion = messages.some((m) => {
|
|
104
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '')
|
|
105
|
+
return text.includes('Уточнение требований') || text.includes('опросник') || text.includes('Вариант 1')
|
|
106
|
+
})
|
|
107
|
+
if (hasRecentQuestion) {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const creationTriggers = [
|
|
112
|
+
'сделай', 'создай', 'напиши', 'разработай', 'придумай', 'реализуй',
|
|
113
|
+
'make', 'build', 'create', 'generate', 'develop',
|
|
114
|
+
]
|
|
115
|
+
const startsWithCreation = creationTriggers.some((t) => p.toLowerCase().startsWith(t))
|
|
116
|
+
|
|
117
|
+
if (startsWithCreation && wordCount <= 18) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const vagueNouns = ['приложение', 'игру', 'сервис', 'сайт', 'лендинг', 'калькулятор', 'виджет', 'дашборд', 'app', 'game', 'tool', 'website']
|
|
122
|
+
if (vagueNouns.some((n) => p.toLowerCase().includes(n))) {
|
|
123
|
+
if (wordCount <= 12) return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds the questionnaire prompt for broad/unclear tasks.
|
|
131
|
+
*/
|
|
132
|
+
export function buildQuestionSynthesisPrompt(userPrompt, referenceOutputs = []) {
|
|
133
|
+
const joined = referenceOutputs
|
|
134
|
+
.map((r, i) => `Advisor ${i + 1} (${r.label}):\n${r.text}`)
|
|
135
|
+
.join('\n\n')
|
|
136
|
+
|
|
137
|
+
return `You are the lead architect and judge in a Mixture of Agents (MoA) ensemble.
|
|
138
|
+
The user gave the task:
|
|
139
|
+
"${userPrompt}"
|
|
140
|
+
|
|
141
|
+
The advisors proposed the following decision points and clarifications:
|
|
142
|
+
${joined}
|
|
143
|
+
|
|
144
|
+
Your task is to synthesize a single, compact, friendly and structured questionnaire (2-4 questions) in the same language as the user's prompt.
|
|
145
|
+
Each question must offer 2-3 concrete recommended answer options (e.g.: 1. Format: single-file HTML/JS or React? 2. Style: minimalism, iOS or neubrutalism?).
|
|
146
|
+
At the end, add a note that the user can answer briefly (e.g.: "1, 2, dark theme") or trust the defaults.`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Builds the curator synthesis prompt with component analysis and model recommendation.
|
|
151
|
+
*/
|
|
152
|
+
export function buildCuratorSynthesisPrompt(userPrompt, referenceOutputs = [], judgeCriteria = '') {
|
|
153
|
+
const joined = referenceOutputs
|
|
154
|
+
.map((r, i) => {
|
|
155
|
+
const fileSummary = (r.files && r.files.length > 0)
|
|
156
|
+
? ` [Files created: ${r.files.map((f) => f.relativePath).join(', ')}]`
|
|
157
|
+
: ''
|
|
158
|
+
let textContent = r.text
|
|
159
|
+
if (referenceOutputs.length >= 3 && textContent.length > 3000) {
|
|
160
|
+
textContent = stripOrSummarizeCode(textContent)
|
|
161
|
+
}
|
|
162
|
+
return `Candidate ${i + 1} — ${r.label}:${fileSummary}\n${textContent}`
|
|
163
|
+
})
|
|
164
|
+
.join('\n\n')
|
|
165
|
+
|
|
166
|
+
const criteriaBlock = judgeCriteria && judgeCriteria.trim()
|
|
167
|
+
? `\n### 🎯 Additional evaluation criteria:\n${judgeCriteria.trim()}\n`
|
|
168
|
+
: ''
|
|
169
|
+
|
|
170
|
+
return `You are the expert Lead Technical Curator and Solution Architect in a Mixture of Agents (MoA) ensemble.
|
|
171
|
+
Your mission is not merely to select one candidate, but to synthesize the optimal solution by extracting the finest components from each candidate's response, identifying potential flaws using the strict antipatterns rubric, and selecting/advising which single agent model is best suited to assemble the final unified deliverable.
|
|
172
|
+
|
|
173
|
+
User Request:
|
|
174
|
+
${userPrompt}
|
|
175
|
+
${criteriaBlock}
|
|
176
|
+
Candidate proposals:
|
|
177
|
+
${joined}
|
|
178
|
+
|
|
179
|
+
${ANTIPATTERNS_RUBRIC}
|
|
180
|
+
|
|
181
|
+
Instructions:
|
|
182
|
+
Your response MUST be structured into three clear parts (respond in the same language as the user's prompt, e.g. Russian):
|
|
183
|
+
|
|
184
|
+
### 1. 🔍 Curator Analysis & Component Breakdown
|
|
185
|
+
- For EACH candidate, provide:
|
|
186
|
+
- ⭐ **Strongest aspects** (e.g. robust architecture, superior UI/CSS design, clean data validation).
|
|
187
|
+
- ⚠️ **Defects or Antipatterns** found from the checklist above.
|
|
188
|
+
- Highlight which candidate provides the best foundation for each component (e.g. Candidate 1 for core logic, Candidate 2 for visual UI).
|
|
189
|
+
|
|
190
|
+
### 2. 🧩 Assembly Recipe & Recommended Master Assembler
|
|
191
|
+
- Recommend the best single agent model to assemble and finalize the solution:
|
|
192
|
+
RECOMMENDED_ASSEMBLER: <number from 1 to N> (<provider:model>)
|
|
193
|
+
- State the machine winner index marker for file promotion:
|
|
194
|
+
WINNER_CANDIDATE_INDEX: <number from 1 to N>
|
|
195
|
+
- Provide the exact blueprint / instructions for combining the best pieces into a unified deliverable.
|
|
196
|
+
|
|
197
|
+
### 3. 🚀 Unified Solution & Execution Guide
|
|
198
|
+
- Present the final synthesized code or complete instructions combining the best candidate features.
|
|
199
|
+
- How to run, verify, and use the deliverable.`
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Builds the judge synthesis prompt for standard or curator mode.
|
|
204
|
+
*/
|
|
205
|
+
export function buildSynthesisPrompt(userPrompt, referenceOutputs = [], judgeCriteria = '', options = {}) {
|
|
206
|
+
if (options.curatorSynthesis) {
|
|
207
|
+
return buildCuratorSynthesisPrompt(userPrompt, referenceOutputs, judgeCriteria)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const joined = referenceOutputs
|
|
211
|
+
.map((r, i) => {
|
|
212
|
+
const fileSummary = (r.files && r.files.length > 0)
|
|
213
|
+
? ` [Files created: ${r.files.map((f) => f.relativePath).join(', ')}]`
|
|
214
|
+
: ''
|
|
215
|
+
let textContent = r.text
|
|
216
|
+
if (referenceOutputs.length >= 3 && textContent.length > 3000) {
|
|
217
|
+
textContent = stripOrSummarizeCode(textContent)
|
|
218
|
+
}
|
|
219
|
+
return `Reference ${i + 1} — ${r.label}:${fileSummary}\n${textContent}`
|
|
220
|
+
})
|
|
221
|
+
.join('\n\n')
|
|
222
|
+
|
|
223
|
+
const criteriaBlock = judgeCriteria && judgeCriteria.trim()
|
|
224
|
+
? `\n### 🎯 Additional evaluation criteria from the user:\n${judgeCriteria.trim()}\n`
|
|
225
|
+
: ''
|
|
226
|
+
|
|
227
|
+
return `You are the expert aggregator/judge in a Mixture of Agents (MoA) process. You evaluate solutions from multiple candidate models, judge which one is best (or how to combine their best parts), and deliver the final authoritative verdict and solution.
|
|
228
|
+
|
|
229
|
+
Original user prompt:
|
|
230
|
+
${userPrompt}
|
|
231
|
+
${criteriaBlock}
|
|
232
|
+
Reference responses from candidate models:
|
|
233
|
+
${joined}
|
|
234
|
+
|
|
235
|
+
${ANTIPATTERNS_RUBRIC}
|
|
236
|
+
|
|
237
|
+
Instructions:
|
|
238
|
+
Your response MUST be structured into three clear parts (respond in the same language as the user's prompt, e.g. Russian):
|
|
239
|
+
|
|
240
|
+
### 1. ⚖️ Judge verdict and comparative analysis
|
|
241
|
+
- **Winner**: clearly name the model and candidate number (e.g. "Winner: Candidate 1 (opencode-go:deepseek-v4-flash)" or "Reference 1 (label) is chosen").
|
|
242
|
+
- Always add the machine winner-selection marker:
|
|
243
|
+
WINNER_CANDIDATE_INDEX: <number from 1 to N>
|
|
244
|
+
- **Why this choice**: compare code, architecture, strengths, weaknesses and reliability of all candidates in detail.
|
|
245
|
+
|
|
246
|
+
### 2. 📁 Project files created
|
|
247
|
+
- List the winner files promoted to the project root and their purpose.
|
|
248
|
+
|
|
249
|
+
### 3. 🚀 How to run and use
|
|
250
|
+
- Describe how to open and run the created project.`
|
|
251
|
+
}
|