opencode-conductor-cdd-plugin 1.0.0-beta.19 → 1.0.0-beta.21
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 +19 -3
- package/dist/prompts/strategies/delegate.md +124 -10
- package/dist/prompts/strategies/manual.md +138 -6
- package/dist/test/integration/omo3-delegation.test.d.ts +1 -0
- package/dist/test/integration/omo3-delegation.test.js +581 -0
- package/dist/tools/delegate.d.ts +12 -0
- package/dist/tools/delegate.js +82 -33
- package/dist/utils/configDetection.d.ts +15 -0
- package/dist/utils/configDetection.js +38 -9
- package/dist/utils/configDetection.test.js +320 -8
- package/dist/utils/documentGeneration.d.ts +3 -0
- package/dist/utils/documentGeneration.js +29 -9
- package/dist/utils/interactiveMenu.test.js +5 -0
- package/dist/utils/languageSupport.d.ts +5 -0
- package/dist/utils/languageSupport.js +163 -0
- package/dist/utils/languageSupport.test.d.ts +1 -0
- package/dist/utils/languageSupport.test.js +158 -0
- package/dist/utils/synergyState.test.js +17 -3
- package/package.json +3 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { validateSelection, parseSelection } from './interactiveMenu.js';
|
|
4
|
+
import { translateQuestion, detectLanguage } from './languageSupport.js';
|
|
4
5
|
// Constants
|
|
5
6
|
const MAX_QUESTIONS = 5;
|
|
6
7
|
const MAX_REVISION_ATTEMPTS = 3;
|
|
@@ -18,6 +19,7 @@ const CHECKPOINT_MAP = {
|
|
|
18
19
|
*/
|
|
19
20
|
export async function presentQuestionsSequentially(questions, responder, options = {}) {
|
|
20
21
|
const { maxQuestions = MAX_QUESTIONS, customInputPrompt } = options;
|
|
22
|
+
let { language = 'en' } = options;
|
|
21
23
|
const session = {
|
|
22
24
|
section: questions[0]?.section || 'product',
|
|
23
25
|
questionsAsked: [],
|
|
@@ -28,30 +30,47 @@ export async function presentQuestionsSequentially(questions, responder, options
|
|
|
28
30
|
};
|
|
29
31
|
for (let i = 0; i < Math.min(questions.length, maxQuestions); i++) {
|
|
30
32
|
const question = questions[i];
|
|
31
|
-
|
|
33
|
+
// Translate question if language is Spanish
|
|
34
|
+
const translatedQuestion = language === 'es' ? translateQuestion(question, 'es') : question;
|
|
35
|
+
session.questionsAsked.push(translatedQuestion);
|
|
32
36
|
// Get user response
|
|
33
|
-
let selections;
|
|
37
|
+
let selections = [];
|
|
34
38
|
let isValid = false;
|
|
35
39
|
// Keep asking until valid response
|
|
36
40
|
while (!isValid) {
|
|
37
|
-
selections = await responder(
|
|
38
|
-
isValid = validateSelection(selections.join(','),
|
|
41
|
+
selections = await responder(translatedQuestion, i + 1);
|
|
42
|
+
isValid = validateSelection(selections.join(','), translatedQuestion.type);
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// Auto-detect language from first user response if custom input provided
|
|
45
|
+
if (i === 0 && selections.length > 0 && selections[0].length > 5) {
|
|
46
|
+
const detectedLang = detectLanguage(selections.join(' '));
|
|
47
|
+
if (detectedLang !== language) {
|
|
48
|
+
language = detectedLang;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const parsedSelections = parseSelection(selections.join(','), translatedQuestion.type);
|
|
52
|
+
// Map selections to actual option text (from translated question)
|
|
42
53
|
const selectedOptions = parsedSelections.map(letter => {
|
|
43
54
|
const index = letter.charCodeAt(0) - 'A'.charCodeAt(0);
|
|
44
|
-
return
|
|
55
|
+
return translatedQuestion.options[index];
|
|
45
56
|
});
|
|
46
57
|
const answer = {
|
|
47
|
-
questionId:
|
|
58
|
+
questionId: translatedQuestion.id,
|
|
48
59
|
selections: parsedSelections,
|
|
49
60
|
selectedOptions,
|
|
50
61
|
timestamp: new Date().toISOString(),
|
|
51
62
|
};
|
|
52
63
|
// Handle option D (custom input)
|
|
53
64
|
if (parsedSelections.includes('D') && customInputPrompt) {
|
|
54
|
-
|
|
65
|
+
const customInput = await customInputPrompt(translatedQuestion);
|
|
66
|
+
answer.customText = customInput;
|
|
67
|
+
// Detect language from custom text input
|
|
68
|
+
if (customInput && customInput.length > 5) {
|
|
69
|
+
const detectedLang = detectLanguage(customInput);
|
|
70
|
+
if (detectedLang !== language) {
|
|
71
|
+
language = detectedLang;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
55
74
|
}
|
|
56
75
|
session.answers.push(answer);
|
|
57
76
|
// Handle option E (auto-generate) - stop asking questions
|
|
@@ -242,6 +261,7 @@ export async function generateDocument(options) {
|
|
|
242
261
|
// Step 1: Present questions sequentially
|
|
243
262
|
const session = await presentQuestionsSequentially(questions, responder, {
|
|
244
263
|
customInputPrompt,
|
|
264
|
+
language: options.language || 'en',
|
|
245
265
|
});
|
|
246
266
|
// Step 2: Draft document from answers
|
|
247
267
|
let draft = draftDocumentFromAnswers(session, analysis);
|
|
@@ -121,6 +121,11 @@ describe('interactiveMenu', () => {
|
|
|
121
121
|
expect(validateSelection('', 'additive')).toBe(false);
|
|
122
122
|
expect(validateSelection(' ', 'exclusive')).toBe(false);
|
|
123
123
|
});
|
|
124
|
+
it('should reject input with only commas or whitespace', () => {
|
|
125
|
+
expect(validateSelection(',,,', 'exclusive')).toBe(false);
|
|
126
|
+
expect(validateSelection(', , ,', 'additive')).toBe(false);
|
|
127
|
+
expect(validateSelection(' , ', 'exclusive')).toBe(false);
|
|
128
|
+
});
|
|
124
129
|
it('should reject option E in combination with others for additive', () => {
|
|
125
130
|
expect(validateSelection('A,E', 'additive')).toBe(false);
|
|
126
131
|
expect(validateSelection('E,A', 'additive')).toBe(false);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Question } from './questionGenerator.js';
|
|
2
|
+
export type Language = 'en' | 'es';
|
|
3
|
+
export declare function detectLanguage(text: string): Language;
|
|
4
|
+
export declare function translateQuestion(question: Question, language: Language): Question;
|
|
5
|
+
export declare function translateInstruction(key: string, language: Language): string;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const SPANISH_INDICATORS = [
|
|
2
|
+
'¿', '¡', 'á', 'é', 'í', 'ó', 'ú', 'ñ', 'ü',
|
|
3
|
+
'cuál', 'qué', 'cómo', 'dónde', 'cuándo', 'quién',
|
|
4
|
+
'los', 'las', 'una', 'del', 'con', 'para', 'por',
|
|
5
|
+
'necesito', 'quiero', 'aplicación', 'proyecto', 'crear',
|
|
6
|
+
'objetivo', 'principal', 'usuarios', 'principales',
|
|
7
|
+
'añadir', 'configuración', 'nuevo', 'algo',
|
|
8
|
+
];
|
|
9
|
+
const ENGLISH_INDICATORS = [
|
|
10
|
+
'what', 'how', 'where', 'when', 'who', 'which',
|
|
11
|
+
'the', 'a', 'an', 'of', 'with', 'for', 'by',
|
|
12
|
+
'need', 'want', 'application', 'project', 'create',
|
|
13
|
+
'main', 'primary', 'users', 'add', 'something', 'new',
|
|
14
|
+
];
|
|
15
|
+
const TRANSLATIONS = {
|
|
16
|
+
'menu.select_option': {
|
|
17
|
+
en: 'Select your option (e.g., "A", "B", "C")',
|
|
18
|
+
es: 'Selecciona tu opción (ej: "A", "B", "C")',
|
|
19
|
+
},
|
|
20
|
+
'menu.custom_input': {
|
|
21
|
+
en: 'Enter custom text',
|
|
22
|
+
es: 'Ingresa texto personalizado',
|
|
23
|
+
},
|
|
24
|
+
'menu.auto_generate': {
|
|
25
|
+
en: 'Auto-generate from context',
|
|
26
|
+
es: 'Auto-generar desde contexto',
|
|
27
|
+
},
|
|
28
|
+
'approval.approve': {
|
|
29
|
+
en: 'Approve this document',
|
|
30
|
+
es: 'Aprobar este documento',
|
|
31
|
+
},
|
|
32
|
+
'approval.revise': {
|
|
33
|
+
en: 'Request revisions',
|
|
34
|
+
es: 'Solicitar revisiones',
|
|
35
|
+
},
|
|
36
|
+
'question.primary_purpose': {
|
|
37
|
+
en: 'What is the primary purpose of this project?',
|
|
38
|
+
es: '¿Cuál es el propósito principal de este proyecto?',
|
|
39
|
+
},
|
|
40
|
+
'option.web_application': {
|
|
41
|
+
en: 'Build a web application',
|
|
42
|
+
es: 'Construir una aplicación web',
|
|
43
|
+
},
|
|
44
|
+
'option.cli_tool': {
|
|
45
|
+
en: 'Build a CLI tool',
|
|
46
|
+
es: 'Construir una herramienta CLI',
|
|
47
|
+
},
|
|
48
|
+
'option.library': {
|
|
49
|
+
en: 'Create a library',
|
|
50
|
+
es: 'Crear una biblioteca',
|
|
51
|
+
},
|
|
52
|
+
'option.custom': {
|
|
53
|
+
en: 'Enter custom text',
|
|
54
|
+
es: 'Ingresar texto personalizado',
|
|
55
|
+
},
|
|
56
|
+
'option.auto_generate': {
|
|
57
|
+
en: 'Auto-generate from context',
|
|
58
|
+
es: 'Auto-generar desde contexto',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export function detectLanguage(text) {
|
|
62
|
+
if (!text || text.trim().length === 0) {
|
|
63
|
+
return 'en';
|
|
64
|
+
}
|
|
65
|
+
const lowerText = text.toLowerCase();
|
|
66
|
+
const words = lowerText.split(/\s+/);
|
|
67
|
+
let spanishScore = 0;
|
|
68
|
+
let englishScore = 0;
|
|
69
|
+
for (let i = 0; i < words.length; i++) {
|
|
70
|
+
const word = words[i];
|
|
71
|
+
// First word gets significantly more weight (10x) to dominate language detection
|
|
72
|
+
const weight = i === 0 ? 10 : 1;
|
|
73
|
+
if (SPANISH_INDICATORS.includes(word)) {
|
|
74
|
+
spanishScore += weight;
|
|
75
|
+
}
|
|
76
|
+
if (ENGLISH_INDICATORS.includes(word)) {
|
|
77
|
+
englishScore += weight;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Special characters strongly indicate Spanish
|
|
81
|
+
for (const char of ['¿', '¡', 'á', 'é', 'í', 'ó', 'ú', 'ñ', 'ü']) {
|
|
82
|
+
if (lowerText.includes(char)) {
|
|
83
|
+
spanishScore += 5;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return spanishScore > englishScore ? 'es' : 'en';
|
|
87
|
+
}
|
|
88
|
+
export function translateQuestion(question, language) {
|
|
89
|
+
if (language === 'en') {
|
|
90
|
+
return question;
|
|
91
|
+
}
|
|
92
|
+
const translated = {
|
|
93
|
+
...question,
|
|
94
|
+
text: translateQuestionText(question.text, question.id),
|
|
95
|
+
options: question.options.map((opt, idx) => translateOption(opt, idx)),
|
|
96
|
+
};
|
|
97
|
+
return translated;
|
|
98
|
+
}
|
|
99
|
+
function translateQuestionText(text, questionId) {
|
|
100
|
+
const translationKey = `question.${questionId}`;
|
|
101
|
+
if (TRANSLATIONS[translationKey]) {
|
|
102
|
+
return TRANSLATIONS[translationKey].es;
|
|
103
|
+
}
|
|
104
|
+
if (text.includes('primary purpose')) {
|
|
105
|
+
return '¿Cuál es el propósito principal de este proyecto?';
|
|
106
|
+
}
|
|
107
|
+
if (text.includes('target users')) {
|
|
108
|
+
return '¿Quiénes son los usuarios objetivo?';
|
|
109
|
+
}
|
|
110
|
+
if (text.includes('key features')) {
|
|
111
|
+
return '¿Cuáles son las características principales?';
|
|
112
|
+
}
|
|
113
|
+
if (text.includes('programming language')) {
|
|
114
|
+
return '¿Qué lenguajes de programación usarás?';
|
|
115
|
+
}
|
|
116
|
+
if (text.includes('framework')) {
|
|
117
|
+
return '¿Qué frameworks usarás?';
|
|
118
|
+
}
|
|
119
|
+
return text;
|
|
120
|
+
}
|
|
121
|
+
function translateOption(option, index) {
|
|
122
|
+
const optionLower = option.toLowerCase();
|
|
123
|
+
if (optionLower.includes('web application')) {
|
|
124
|
+
return 'Construir una aplicación web';
|
|
125
|
+
}
|
|
126
|
+
if (optionLower.includes('cli tool')) {
|
|
127
|
+
return 'Construir una herramienta CLI';
|
|
128
|
+
}
|
|
129
|
+
if (optionLower.includes('library')) {
|
|
130
|
+
return 'Crear una biblioteca';
|
|
131
|
+
}
|
|
132
|
+
if (optionLower.includes('enter custom') || optionLower.includes('custom text')) {
|
|
133
|
+
return 'Ingresar texto personalizado';
|
|
134
|
+
}
|
|
135
|
+
if (optionLower.includes('auto-generate')) {
|
|
136
|
+
return 'Auto-generar desde contexto';
|
|
137
|
+
}
|
|
138
|
+
if (optionLower.includes('developers')) {
|
|
139
|
+
return 'Desarrolladores';
|
|
140
|
+
}
|
|
141
|
+
if (optionLower.includes('end users')) {
|
|
142
|
+
return 'Usuarios finales';
|
|
143
|
+
}
|
|
144
|
+
if (optionLower.includes('businesses')) {
|
|
145
|
+
return 'Empresas';
|
|
146
|
+
}
|
|
147
|
+
if (optionLower.includes('typescript')) {
|
|
148
|
+
return 'TypeScript';
|
|
149
|
+
}
|
|
150
|
+
if (optionLower.includes('python')) {
|
|
151
|
+
return 'Python';
|
|
152
|
+
}
|
|
153
|
+
if (optionLower.includes('javascript')) {
|
|
154
|
+
return 'JavaScript';
|
|
155
|
+
}
|
|
156
|
+
return option;
|
|
157
|
+
}
|
|
158
|
+
export function translateInstruction(key, language) {
|
|
159
|
+
if (TRANSLATIONS[key] && TRANSLATIONS[key][language]) {
|
|
160
|
+
return TRANSLATIONS[key][language];
|
|
161
|
+
}
|
|
162
|
+
return key;
|
|
163
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { detectLanguage, translateQuestion, translateInstruction, } from './languageSupport.js';
|
|
3
|
+
describe('languageSupport', () => {
|
|
4
|
+
describe('detectLanguage', () => {
|
|
5
|
+
it('should detect Spanish from common Spanish words', () => {
|
|
6
|
+
expect(detectLanguage('¿Cuál es el objetivo principal?')).toBe('es');
|
|
7
|
+
expect(detectLanguage('Los usuarios principales son')).toBe('es');
|
|
8
|
+
expect(detectLanguage('Necesito crear una aplicación')).toBe('es');
|
|
9
|
+
});
|
|
10
|
+
it('should detect Spanish from Spanish-specific characters', () => {
|
|
11
|
+
expect(detectLanguage('Añadir funcionalidad')).toBe('es');
|
|
12
|
+
expect(detectLanguage('Configuración básica')).toBe('es');
|
|
13
|
+
});
|
|
14
|
+
it('should detect English from common English words', () => {
|
|
15
|
+
expect(detectLanguage('What is the primary goal?')).toBe('en');
|
|
16
|
+
expect(detectLanguage('The main users are')).toBe('en');
|
|
17
|
+
expect(detectLanguage('I need to create an application')).toBe('en');
|
|
18
|
+
});
|
|
19
|
+
it('should default to English for ambiguous input', () => {
|
|
20
|
+
expect(detectLanguage('123')).toBe('en');
|
|
21
|
+
expect(detectLanguage('React TypeScript')).toBe('en');
|
|
22
|
+
expect(detectLanguage('')).toBe('en');
|
|
23
|
+
});
|
|
24
|
+
it('should handle mixed language input', () => {
|
|
25
|
+
// Should detect based on predominant language
|
|
26
|
+
expect(detectLanguage('Create a proyecto nuevo')).toBe('en');
|
|
27
|
+
expect(detectLanguage('Necesito create something')).toBe('es');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('translateQuestion', () => {
|
|
31
|
+
const englishQuestion = {
|
|
32
|
+
id: 'product-1',
|
|
33
|
+
text: 'What is the primary purpose of this project?',
|
|
34
|
+
type: 'exclusive',
|
|
35
|
+
section: 'product',
|
|
36
|
+
options: [
|
|
37
|
+
'Build a web application',
|
|
38
|
+
'Build a CLI tool',
|
|
39
|
+
'Create a library',
|
|
40
|
+
'Enter custom text',
|
|
41
|
+
'Auto-generate from context',
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
it('should translate question text to Spanish', () => {
|
|
45
|
+
const translated = translateQuestion(englishQuestion, 'es');
|
|
46
|
+
expect(translated.text).toContain('propósito');
|
|
47
|
+
expect(translated.text).toContain('proyecto');
|
|
48
|
+
});
|
|
49
|
+
it('should translate options to Spanish', () => {
|
|
50
|
+
const translated = translateQuestion(englishQuestion, 'es');
|
|
51
|
+
expect(translated.options[0]).toContain('aplicación web');
|
|
52
|
+
expect(translated.options[3]).toContain('texto personalizado');
|
|
53
|
+
expect(translated.options[4]).toContain('Auto-generar');
|
|
54
|
+
});
|
|
55
|
+
it('should keep English text when language is English', () => {
|
|
56
|
+
const translated = translateQuestion(englishQuestion, 'en');
|
|
57
|
+
expect(translated.text).toBe(englishQuestion.text);
|
|
58
|
+
expect(translated.options).toEqual(englishQuestion.options);
|
|
59
|
+
});
|
|
60
|
+
it('should preserve question metadata (id, type, section)', () => {
|
|
61
|
+
const translated = translateQuestion(englishQuestion, 'es');
|
|
62
|
+
expect(translated.id).toBe(englishQuestion.id);
|
|
63
|
+
expect(translated.type).toBe(englishQuestion.type);
|
|
64
|
+
expect(translated.section).toBe(englishQuestion.section);
|
|
65
|
+
});
|
|
66
|
+
it('should use fallback translation for target users question', () => {
|
|
67
|
+
const question = {
|
|
68
|
+
id: 'users-1',
|
|
69
|
+
text: 'Who are the target users?',
|
|
70
|
+
type: 'exclusive',
|
|
71
|
+
section: 'product',
|
|
72
|
+
options: ['Developers', 'End users', 'Businesses'],
|
|
73
|
+
};
|
|
74
|
+
const translated = translateQuestion(question, 'es');
|
|
75
|
+
expect(translated.text).toContain('usuarios objetivo');
|
|
76
|
+
expect(translated.options[0]).toBe('Desarrolladores');
|
|
77
|
+
expect(translated.options[1]).toContain('Usuarios finales');
|
|
78
|
+
expect(translated.options[2]).toBe('Empresas');
|
|
79
|
+
});
|
|
80
|
+
it('should use fallback translation for key features question', () => {
|
|
81
|
+
const question = {
|
|
82
|
+
id: 'features-1',
|
|
83
|
+
text: 'What are the key features?',
|
|
84
|
+
type: 'exclusive',
|
|
85
|
+
section: 'product',
|
|
86
|
+
options: ['Feature 1', 'Feature 2'],
|
|
87
|
+
};
|
|
88
|
+
const translated = translateQuestion(question, 'es');
|
|
89
|
+
expect(translated.text).toContain('características principales');
|
|
90
|
+
});
|
|
91
|
+
it('should use fallback translation for programming language question', () => {
|
|
92
|
+
const question = {
|
|
93
|
+
id: 'tech-1',
|
|
94
|
+
text: 'What programming language will you use?',
|
|
95
|
+
type: 'exclusive',
|
|
96
|
+
section: 'tech-stack',
|
|
97
|
+
options: ['TypeScript', 'Python', 'JavaScript'],
|
|
98
|
+
};
|
|
99
|
+
const translated = translateQuestion(question, 'es');
|
|
100
|
+
expect(translated.text).toContain('lenguajes de programación');
|
|
101
|
+
expect(translated.options[0]).toBe('TypeScript');
|
|
102
|
+
expect(translated.options[1]).toBe('Python');
|
|
103
|
+
expect(translated.options[2]).toBe('JavaScript');
|
|
104
|
+
});
|
|
105
|
+
it('should use fallback translation for framework question', () => {
|
|
106
|
+
const question = {
|
|
107
|
+
id: 'tech-2',
|
|
108
|
+
text: 'What framework will you use?',
|
|
109
|
+
type: 'exclusive',
|
|
110
|
+
section: 'tech-stack',
|
|
111
|
+
options: ['React', 'Vue', 'Angular'],
|
|
112
|
+
};
|
|
113
|
+
const translated = translateQuestion(question, 'es');
|
|
114
|
+
expect(translated.text).toContain('frameworks');
|
|
115
|
+
});
|
|
116
|
+
it('should return original text if no translation matches', () => {
|
|
117
|
+
const question = {
|
|
118
|
+
id: 'custom-question',
|
|
119
|
+
text: 'Some custom question text without matches',
|
|
120
|
+
type: 'exclusive',
|
|
121
|
+
section: 'product',
|
|
122
|
+
options: ['Option A', 'Option B'],
|
|
123
|
+
};
|
|
124
|
+
const translated = translateQuestion(question, 'es');
|
|
125
|
+
expect(translated.text).toBe(question.text);
|
|
126
|
+
expect(translated.options).toEqual(question.options);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('translateInstruction', () => {
|
|
130
|
+
it('should translate menu instructions to Spanish', () => {
|
|
131
|
+
const key = 'menu.select_option';
|
|
132
|
+
const translated = translateInstruction(key, 'es');
|
|
133
|
+
expect(translated).toContain('Selecciona');
|
|
134
|
+
expect(translated).toContain('opción');
|
|
135
|
+
});
|
|
136
|
+
it('should translate menu instructions to English', () => {
|
|
137
|
+
const key = 'menu.select_option';
|
|
138
|
+
const translated = translateInstruction(key, 'en');
|
|
139
|
+
expect(translated).toContain('Select');
|
|
140
|
+
expect(translated).toContain('option');
|
|
141
|
+
});
|
|
142
|
+
it('should handle approval instructions in Spanish', () => {
|
|
143
|
+
const key = 'approval.approve';
|
|
144
|
+
const translated = translateInstruction(key, 'es');
|
|
145
|
+
expect(translated.toLowerCase()).toContain('aprobar');
|
|
146
|
+
});
|
|
147
|
+
it('should handle approval instructions in English', () => {
|
|
148
|
+
const key = 'approval.approve';
|
|
149
|
+
const translated = translateInstruction(key, 'en');
|
|
150
|
+
expect(translated.toLowerCase()).toContain('approve');
|
|
151
|
+
});
|
|
152
|
+
it('should return key if translation not found', () => {
|
|
153
|
+
const key = 'unknown.key';
|
|
154
|
+
const translated = translateInstruction(key, 'es');
|
|
155
|
+
expect(translated).toBe(key);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -15,6 +15,7 @@ describe("synergyState", () => {
|
|
|
15
15
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
16
16
|
hasCDDInOpenCode: false,
|
|
17
17
|
hasCDDInOMO: false,
|
|
18
|
+
hasCDDInSlim: true,
|
|
18
19
|
synergyActive: true,
|
|
19
20
|
synergyFramework: 'oh-my-opencode-slim',
|
|
20
21
|
slimAgents: ['explorer', 'librarian', 'oracle', 'designer'],
|
|
@@ -28,6 +29,7 @@ describe("synergyState", () => {
|
|
|
28
29
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
29
30
|
hasCDDInOpenCode: false,
|
|
30
31
|
hasCDDInOMO: false,
|
|
32
|
+
hasCDDInSlim: true,
|
|
31
33
|
synergyActive: true,
|
|
32
34
|
synergyFramework: 'oh-my-opencode-slim',
|
|
33
35
|
slimAgents: ['explorer', 'librarian'],
|
|
@@ -42,6 +44,7 @@ describe("synergyState", () => {
|
|
|
42
44
|
.mockReturnValueOnce({
|
|
43
45
|
hasCDDInOpenCode: false,
|
|
44
46
|
hasCDDInOMO: false,
|
|
47
|
+
hasCDDInSlim: true,
|
|
45
48
|
synergyActive: true,
|
|
46
49
|
synergyFramework: 'oh-my-opencode-slim',
|
|
47
50
|
slimAgents: ['explorer'],
|
|
@@ -49,6 +52,7 @@ describe("synergyState", () => {
|
|
|
49
52
|
.mockReturnValueOnce({
|
|
50
53
|
hasCDDInOpenCode: false,
|
|
51
54
|
hasCDDInOMO: true,
|
|
55
|
+
hasCDDInSlim: false,
|
|
52
56
|
synergyActive: true,
|
|
53
57
|
synergyFramework: 'oh-my-opencode',
|
|
54
58
|
cddModel: 'model-1',
|
|
@@ -64,13 +68,15 @@ describe("synergyState", () => {
|
|
|
64
68
|
.mockReturnValueOnce({
|
|
65
69
|
hasCDDInOpenCode: false,
|
|
66
70
|
hasCDDInOMO: false,
|
|
71
|
+
hasCDDInSlim: true,
|
|
67
72
|
synergyActive: true,
|
|
68
73
|
synergyFramework: 'oh-my-opencode-slim',
|
|
69
74
|
slimAgents: ['explorer'],
|
|
70
75
|
})
|
|
71
76
|
.mockReturnValueOnce({
|
|
72
77
|
hasCDDInOpenCode: false,
|
|
73
|
-
hasCDDInOMO:
|
|
78
|
+
hasCDDInOMO: true,
|
|
79
|
+
hasCDDInSlim: false,
|
|
74
80
|
synergyActive: true,
|
|
75
81
|
synergyFramework: 'oh-my-opencode',
|
|
76
82
|
cddModel: 'model-changed',
|
|
@@ -85,6 +91,7 @@ describe("synergyState", () => {
|
|
|
85
91
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
86
92
|
hasCDDInOpenCode: true,
|
|
87
93
|
hasCDDInOMO: false,
|
|
94
|
+
hasCDDInSlim: false,
|
|
88
95
|
synergyActive: false,
|
|
89
96
|
synergyFramework: 'none',
|
|
90
97
|
cddModel: 'model-1',
|
|
@@ -99,13 +106,15 @@ describe("synergyState", () => {
|
|
|
99
106
|
.mockReturnValueOnce({
|
|
100
107
|
hasCDDInOpenCode: false,
|
|
101
108
|
hasCDDInOMO: false,
|
|
109
|
+
hasCDDInSlim: true,
|
|
102
110
|
synergyActive: true,
|
|
103
111
|
synergyFramework: 'oh-my-opencode-slim',
|
|
104
112
|
slimAgents: ['explorer'],
|
|
105
113
|
})
|
|
106
114
|
.mockReturnValueOnce({
|
|
107
115
|
hasCDDInOpenCode: false,
|
|
108
|
-
hasCDDInOMO:
|
|
116
|
+
hasCDDInOMO: true,
|
|
117
|
+
hasCDDInSlim: false,
|
|
109
118
|
synergyActive: true,
|
|
110
119
|
synergyFramework: 'oh-my-opencode',
|
|
111
120
|
cddModel: 'model-1',
|
|
@@ -124,6 +133,7 @@ describe("synergyState", () => {
|
|
|
124
133
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
125
134
|
hasCDDInOpenCode: false,
|
|
126
135
|
hasCDDInOMO: false,
|
|
136
|
+
hasCDDInSlim: false,
|
|
127
137
|
synergyActive: false,
|
|
128
138
|
synergyFramework: 'none',
|
|
129
139
|
});
|
|
@@ -136,6 +146,7 @@ describe("synergyState", () => {
|
|
|
136
146
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
137
147
|
hasCDDInOpenCode: false,
|
|
138
148
|
hasCDDInOMO: false,
|
|
149
|
+
hasCDDInSlim: true,
|
|
139
150
|
synergyActive: true,
|
|
140
151
|
synergyFramework: 'oh-my-opencode-slim',
|
|
141
152
|
slimAgents: ['explorer', 'oracle'],
|
|
@@ -147,6 +158,7 @@ describe("synergyState", () => {
|
|
|
147
158
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
148
159
|
hasCDDInOpenCode: false,
|
|
149
160
|
hasCDDInOMO: true,
|
|
161
|
+
hasCDDInSlim: false,
|
|
150
162
|
synergyActive: true,
|
|
151
163
|
synergyFramework: 'oh-my-opencode',
|
|
152
164
|
cddModel: 'model-1',
|
|
@@ -163,13 +175,15 @@ describe("synergyState", () => {
|
|
|
163
175
|
.mockReturnValueOnce({
|
|
164
176
|
hasCDDInOpenCode: false,
|
|
165
177
|
hasCDDInOMO: false,
|
|
178
|
+
hasCDDInSlim: true,
|
|
166
179
|
synergyActive: true,
|
|
167
180
|
synergyFramework: 'oh-my-opencode-slim',
|
|
168
181
|
slimAgents: ['explorer'],
|
|
169
182
|
})
|
|
170
183
|
.mockReturnValueOnce({
|
|
171
184
|
hasCDDInOpenCode: false,
|
|
172
|
-
hasCDDInOMO:
|
|
185
|
+
hasCDDInOMO: true,
|
|
186
|
+
hasCDDInSlim: false,
|
|
173
187
|
synergyActive: true,
|
|
174
188
|
synergyFramework: 'oh-my-opencode',
|
|
175
189
|
cddModel: 'changed',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-conductor-cdd-plugin",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.21",
|
|
4
4
|
"description": "Context-Driven Development (CDD) plugin for OpenCode - Transform your AI coding workflow with structured specifications, plans, and implementation tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"prepublishOnly": "npm run build"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@opencode-ai/plugin": "1.
|
|
45
|
+
"@opencode-ai/plugin": "^1.1.27",
|
|
46
46
|
"smol-toml": "^1.6.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@semantic-release/npm": "^12.0.1",
|
|
54
54
|
"@semantic-release/release-notes-generator": "^14.0.0",
|
|
55
55
|
"@types/node": "^20.0.0",
|
|
56
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
56
57
|
"semantic-release": "^24.2.1",
|
|
57
58
|
"typescript": "^5.0.0",
|
|
58
59
|
"vitest": "^4.0.16"
|