@goodandready/dsh-moa 0.2.9 → 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.
@@ -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
+ }