sage-team 3.6.0 → 3.7.1
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 +40 -9
- package/dist/cli/commands/setup-claude.d.ts +3 -0
- package/dist/cli/commands/setup-claude.d.ts.map +1 -0
- package/dist/cli/commands/setup-claude.js +75 -0
- package/dist/cli/commands/setup-claude.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/engine/__tests__/ceo-brain.test.js +2 -2
- package/dist/engine/__tests__/ceo-brain.test.js.map +1 -1
- package/dist/engine/__tests__/dispatcher.test.js +2 -1
- package/dist/engine/__tests__/dispatcher.test.js.map +1 -1
- package/dist/engine/ceo-brain.d.ts +15 -8
- package/dist/engine/ceo-brain.d.ts.map +1 -1
- package/dist/engine/ceo-brain.js +339 -213
- package/dist/engine/ceo-brain.js.map +1 -1
- package/dist/engine/dispatcher.d.ts +2 -0
- package/dist/engine/dispatcher.d.ts.map +1 -1
- package/dist/engine/dispatcher.js +26 -6
- package/dist/engine/dispatcher.js.map +1 -1
- package/dist/engine/orchestrator.d.ts +1 -0
- package/dist/engine/orchestrator.d.ts.map +1 -1
- package/dist/engine/orchestrator.js +35 -4
- package/dist/engine/orchestrator.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/api.d.ts.map +1 -1
- package/dist/server/routes/api.js +10 -3
- package/dist/server/routes/api.js.map +1 -1
- package/{src/web/dist/assets/BufferResource-C0a2-23_.js → dist/web/dist/assets/BufferResource-BGsixSHO.js} +1 -1
- package/dist/web/dist/assets/BufferResource-BbpTIQ85.js +185 -0
- package/dist/web/dist/assets/BufferResource-BjJe3r2a.js +185 -0
- package/dist/web/dist/assets/BufferResource-C7ypptTD.js +185 -0
- package/dist/web/dist/assets/BufferResource-CuR0zQl9.js +185 -0
- package/dist/web/dist/assets/BufferResource-_EbXUkK9.js +185 -0
- package/dist/web/dist/assets/BufferResource-dCF0GA9M.js +185 -0
- package/{src/web/dist/assets/CanvasRenderer-DAx637pP.js → dist/web/dist/assets/CanvasRenderer-BGC-Rjpm.js} +1 -1
- package/dist/web/dist/assets/CanvasRenderer-BQ7ScDtX.js +1 -0
- package/dist/web/dist/assets/CanvasRenderer-CBkg7qb3.js +1 -0
- package/dist/web/dist/assets/CanvasRenderer-CcFmL3Lw.js +1 -0
- package/dist/web/dist/assets/CanvasRenderer-CwKoxkGD.js +1 -0
- package/dist/web/dist/assets/CanvasRenderer-YM8daXJu.js +1 -0
- package/dist/web/dist/assets/CanvasRenderer-yT9JOc5o.js +1 -0
- package/{src/web/dist/assets/Filter-BpH04nQq.js → dist/web/dist/assets/Filter-BzM7i9ln.js} +1 -1
- package/dist/web/dist/assets/Filter-CJvSyeWH.js +1 -0
- package/dist/web/dist/assets/Filter-CUPFlJF_.js +1 -0
- package/dist/web/dist/assets/Filter-D2kh6Lyn.js +1 -0
- package/dist/web/dist/assets/Filter-DMjNMMPy.js +1 -0
- package/dist/web/dist/assets/Filter-GQK6iqrb.js +1 -0
- package/dist/web/dist/assets/Filter-nUtqhIEl.js +1 -0
- package/{src/web/dist/assets/RenderTargetSystem-B6ma-XuE.js → dist/web/dist/assets/RenderTargetSystem-B-ekYymr.js} +1 -1
- package/dist/web/dist/assets/RenderTargetSystem-BylEtp0F.js +172 -0
- package/dist/web/dist/assets/RenderTargetSystem-D2L3KLdt.js +172 -0
- package/dist/web/dist/assets/RenderTargetSystem-DNsILH5P.js +172 -0
- package/dist/web/dist/assets/RenderTargetSystem-lhfSOVIz.js +172 -0
- package/dist/web/dist/assets/RenderTargetSystem-nRg6zEP8.js +172 -0
- package/dist/web/dist/assets/RenderTargetSystem-uCCnK3Aw.js +172 -0
- package/{src/web/dist/assets/WebGLRenderer-DxbdlULS.js → dist/web/dist/assets/WebGLRenderer-BEupTZT-.js} +1 -1
- package/dist/web/dist/assets/WebGLRenderer-BJuclWjv.js +156 -0
- package/dist/web/dist/assets/WebGLRenderer-BONohh1V.js +156 -0
- package/dist/web/dist/assets/WebGLRenderer-Bsr1n0Uu.js +156 -0
- package/dist/web/dist/assets/WebGLRenderer-Da_BkFlZ.js +156 -0
- package/dist/web/dist/assets/WebGLRenderer-DiL4ivWY.js +156 -0
- package/dist/web/dist/assets/WebGLRenderer-DqRhyEby.js +156 -0
- package/{src/web/dist/assets/WebGPURenderer-DALpH00k.js → dist/web/dist/assets/WebGPURenderer-C0BJNyre.js} +1 -1
- package/dist/web/dist/assets/WebGPURenderer-Clrsu_cP.js +41 -0
- package/dist/web/dist/assets/WebGPURenderer-DbGOPnSE.js +41 -0
- package/dist/web/dist/assets/WebGPURenderer-Ds8VNo2-.js +41 -0
- package/dist/web/dist/assets/WebGPURenderer-Du7e_Agz.js +41 -0
- package/dist/web/dist/assets/WebGPURenderer-pLgFan3y.js +41 -0
- package/dist/web/dist/assets/WebGPURenderer-pd03jK7j.js +41 -0
- package/{src/web/dist/assets/browserAll-BToSt8GL.js → dist/web/dist/assets/browserAll-1QqybVac.js} +1 -1
- package/dist/web/dist/assets/browserAll-Bd3WFbs9.js +14 -0
- package/dist/web/dist/assets/browserAll-Bv-Fa9BT.js +14 -0
- package/dist/web/dist/assets/browserAll-C9HRyLf9.js +14 -0
- package/dist/web/dist/assets/browserAll-D7SUz7ce.js +14 -0
- package/dist/web/dist/assets/browserAll-TAEt2LTG.js +14 -0
- package/dist/web/dist/assets/browserAll-d1yltqWP.js +14 -0
- package/dist/web/dist/assets/index-BQx3wJPr.css +1 -0
- package/dist/web/dist/assets/index-C1XGJE1t.js +298 -0
- package/dist/web/dist/assets/index-C6hoHqN5.js +298 -0
- package/dist/web/dist/assets/index-CFVD9jMF.js +298 -0
- package/dist/web/dist/assets/index-CO3-IuiC.js +298 -0
- package/dist/web/dist/assets/index-CRT2SA6d.js +298 -0
- package/dist/web/dist/assets/index-CVcQAOB6.css +1 -0
- package/dist/web/dist/assets/index-CwYVpEl9.js +298 -0
- package/{src/web/dist/assets/index-CnLwjlPD.js → dist/web/dist/assets/index-DBSQsI6C.js} +47 -47
- package/{src/web/dist/assets/webworkerAll-DmVDEFdQ.js → dist/web/dist/assets/webworkerAll-B3fxgx9g.js} +1 -1
- package/dist/web/dist/assets/webworkerAll-BILg7nF2.js +83 -0
- package/dist/web/dist/assets/webworkerAll-C7T1zoFZ.js +83 -0
- package/dist/web/dist/assets/webworkerAll-CMt6Ulvh.js +83 -0
- package/dist/web/dist/assets/webworkerAll-CREQA0I8.js +83 -0
- package/dist/web/dist/assets/webworkerAll-CykNhAo8.js +83 -0
- package/dist/web/dist/assets/webworkerAll-WnWYpa5G.js +83 -0
- package/{src → dist}/web/dist/index.html +2 -2
- package/package.json +2 -3
- package/src/web/dist/assets/index-8Qtg1JIx.css +0 -1
package/dist/engine/ceo-brain.js
CHANGED
|
@@ -6,106 +6,135 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.CEOBrain = void 0;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
keywords: [],
|
|
64
|
-
greeting: 'Interesting project! Let me make sure I understand exactly what you need before I rally the team.',
|
|
65
|
-
questions: [
|
|
66
|
-
'What technology/framework do you want us to use?',
|
|
67
|
-
'What are the 3 most important features or requirements?',
|
|
68
|
-
'Do you have any design preferences or references?',
|
|
69
|
-
'What is the target audience for this project?',
|
|
70
|
-
],
|
|
71
|
-
};
|
|
72
|
-
function detectProjectType(goal) {
|
|
73
|
-
const lower = goal.toLowerCase();
|
|
74
|
-
for (const pt of PROJECT_TYPES) {
|
|
75
|
-
if (pt.keywords.some(kw => lower.includes(kw))) {
|
|
76
|
-
return pt;
|
|
11
|
+
// ── Project context scanner ──────────────────────────────────────
|
|
12
|
+
function scanProjectContext() {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const lines = [];
|
|
15
|
+
// Read package.json if exists
|
|
16
|
+
const pkgPath = path_1.default.join(cwd, 'package.json');
|
|
17
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
20
|
+
lines.push(`## package.json`);
|
|
21
|
+
lines.push(`- name: ${pkg.name || 'unknown'}`);
|
|
22
|
+
lines.push(`- description: ${pkg.description || 'none'}`);
|
|
23
|
+
if (pkg.scripts)
|
|
24
|
+
lines.push(`- scripts: ${Object.keys(pkg.scripts).join(', ')}`);
|
|
25
|
+
if (pkg.dependencies)
|
|
26
|
+
lines.push(`- dependencies: ${Object.keys(pkg.dependencies).join(', ')}`);
|
|
27
|
+
lines.push('');
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
}
|
|
31
|
+
// Read requirements.txt / pyproject.toml for Python projects
|
|
32
|
+
const reqPath = path_1.default.join(cwd, 'requirements.txt');
|
|
33
|
+
if (fs_1.default.existsSync(reqPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const content = fs_1.default.readFileSync(reqPath, 'utf-8').trim();
|
|
36
|
+
lines.push(`## requirements.txt`);
|
|
37
|
+
lines.push(content.split('\n').slice(0, 20).join('\n'));
|
|
38
|
+
lines.push('');
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
}
|
|
42
|
+
const pyprojectPath = path_1.default.join(cwd, 'pyproject.toml');
|
|
43
|
+
if (fs_1.default.existsSync(pyprojectPath)) {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs_1.default.readFileSync(pyprojectPath, 'utf-8');
|
|
46
|
+
lines.push(`## pyproject.toml (first 30 lines)`);
|
|
47
|
+
lines.push(content.split('\n').slice(0, 30).join('\n'));
|
|
48
|
+
lines.push('');
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
// Read README if exists (first 50 lines)
|
|
53
|
+
for (const readme of ['README.md', 'readme.md', 'README.txt', 'README']) {
|
|
54
|
+
const readmePath = path_1.default.join(cwd, readme);
|
|
55
|
+
if (fs_1.default.existsSync(readmePath)) {
|
|
56
|
+
try {
|
|
57
|
+
const content = fs_1.default.readFileSync(readmePath, 'utf-8');
|
|
58
|
+
lines.push(`## ${readme} (first 50 lines)`);
|
|
59
|
+
lines.push(content.split('\n').slice(0, 50).join('\n'));
|
|
60
|
+
lines.push('');
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
break;
|
|
77
64
|
}
|
|
78
65
|
}
|
|
79
|
-
|
|
66
|
+
// Directory tree (top level + 1 depth)
|
|
67
|
+
try {
|
|
68
|
+
const entries = fs_1.default.readdirSync(cwd, { withFileTypes: true });
|
|
69
|
+
const relevant = entries.filter(e => !e.name.startsWith('.') &&
|
|
70
|
+
!['node_modules', '__pycache__', 'dist', 'build', '.git', '.sage-team', 'venv', '.venv'].includes(e.name));
|
|
71
|
+
lines.push('## Project structure');
|
|
72
|
+
for (const entry of relevant.slice(0, 30)) {
|
|
73
|
+
const prefix = entry.isDirectory() ? '📁' : '📄';
|
|
74
|
+
lines.push(`${prefix} ${entry.name}`);
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
try {
|
|
77
|
+
const sub = fs_1.default.readdirSync(path_1.default.join(cwd, entry.name), { withFileTypes: true });
|
|
78
|
+
for (const s of sub.slice(0, 10)) {
|
|
79
|
+
const sp = s.isDirectory() ? '📁' : '📄';
|
|
80
|
+
lines.push(` ${sp} ${s.name}`);
|
|
81
|
+
}
|
|
82
|
+
if (sub.length > 10)
|
|
83
|
+
lines.push(` ... and ${sub.length - 10} more`);
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
// Git info
|
|
92
|
+
try {
|
|
93
|
+
const branch = (0, child_process_1.execSync)('git branch --show-current', { cwd, encoding: 'utf-8', timeout: 3000 }).trim();
|
|
94
|
+
const lastCommits = (0, child_process_1.execSync)('git log --oneline -5', { cwd, encoding: 'utf-8', timeout: 3000 }).trim();
|
|
95
|
+
lines.push('## Git');
|
|
96
|
+
lines.push(`Branch: ${branch}`);
|
|
97
|
+
lines.push(`Recent commits:\n${lastCommits}`);
|
|
98
|
+
lines.push('');
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
return lines.join('\n');
|
|
80
102
|
}
|
|
81
103
|
// ── Claude Code spawn (only used for decomposition) ─────────────
|
|
82
104
|
function findClaudeExe() {
|
|
83
105
|
if (process.platform === 'win32') {
|
|
84
|
-
|
|
106
|
+
// 1. Native .exe
|
|
85
107
|
const exePaths = [
|
|
86
108
|
path_1.default.join(process.env.USERPROFILE || '', '.local', 'bin', 'claude.exe'),
|
|
87
109
|
path_1.default.join(process.env.LOCALAPPDATA || '', 'Programs', 'claude', 'claude.exe'),
|
|
88
110
|
];
|
|
89
111
|
for (const p of exePaths) {
|
|
90
|
-
if (
|
|
91
|
-
return p;
|
|
112
|
+
if (fs_1.default.existsSync(p))
|
|
113
|
+
return { command: p, prefix: [] };
|
|
114
|
+
}
|
|
115
|
+
// 2. npm global cli.js (run with node)
|
|
116
|
+
const npmCliJs = path_1.default.join(process.env.APPDATA || '', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
117
|
+
if (fs_1.default.existsSync(npmCliJs)) {
|
|
118
|
+
return { command: process.execPath, prefix: [npmCliJs] };
|
|
92
119
|
}
|
|
120
|
+
// 3. where command
|
|
93
121
|
try {
|
|
94
122
|
const lines = (0, child_process_1.execSync)('where claude.exe', { encoding: 'utf-8' }).trim().split('\n');
|
|
95
123
|
const exeLine = lines.find((l) => l.trim().endsWith('.exe'));
|
|
96
124
|
if (exeLine)
|
|
97
|
-
return exeLine.trim();
|
|
125
|
+
return { command: exeLine.trim(), prefix: [] };
|
|
98
126
|
}
|
|
99
127
|
catch { /* continue */ }
|
|
100
128
|
}
|
|
101
|
-
return 'claude';
|
|
129
|
+
return { command: 'claude', prefix: [] };
|
|
102
130
|
}
|
|
103
131
|
function runClaude(systemPrompt, userPrompt) {
|
|
104
132
|
return new Promise((resolve, reject) => {
|
|
105
|
-
const
|
|
133
|
+
const { command, prefix } = findClaudeExe();
|
|
106
134
|
const env = { ...process.env };
|
|
107
135
|
delete env.CLAUDECODE;
|
|
108
|
-
const proc = (0, child_process_1.spawn)(
|
|
136
|
+
const proc = (0, child_process_1.spawn)(command, [
|
|
137
|
+
...prefix,
|
|
109
138
|
'--print',
|
|
110
139
|
'--dangerously-skip-permissions',
|
|
111
140
|
'--output-format', 'text',
|
|
@@ -139,17 +168,40 @@ function runClaude(systemPrompt, userPrompt) {
|
|
|
139
168
|
});
|
|
140
169
|
});
|
|
141
170
|
}
|
|
171
|
+
const CEO_SYSTEM_PROMPT = `You are Sage, CEO of Sage Team — an AI-powered autonomous software company with 11 agents.
|
|
172
|
+
|
|
173
|
+
Your personality: Confident, warm, decisive. You speak naturally and directly. You refer to your team members by name.
|
|
174
|
+
|
|
175
|
+
Your team:
|
|
176
|
+
- Nova (CTO) — technical strategy
|
|
177
|
+
- Aria (Architect) — system design
|
|
178
|
+
- Dex (Senior Dev) — core implementation
|
|
179
|
+
- Flux (Fullstack Dev) — full-stack features
|
|
180
|
+
- Quinn (QA Lead) — testing & quality
|
|
181
|
+
- Gage (DevOps) — infrastructure & deployment
|
|
182
|
+
- Morgan (Product Manager) — product strategy
|
|
183
|
+
- Uma (UX Designer) — design & user experience
|
|
184
|
+
- River (Scrum Master) — process & coordination
|
|
185
|
+
- Atlas (Data Engineer) — data & analytics
|
|
186
|
+
|
|
187
|
+
CRITICAL RULES:
|
|
188
|
+
1. You ALWAYS read the project context provided to understand what already exists in the current directory
|
|
189
|
+
2. If there is existing code, you DO NOT propose building from scratch — you work WITH the existing project
|
|
190
|
+
3. You ask relevant questions based on what you actually see in the project
|
|
191
|
+
4. You respond in the SAME LANGUAGE the user writes in (Portuguese → Portuguese, English → English, etc.)
|
|
192
|
+
5. Keep responses concise and natural — you're a CEO, not a chatbot
|
|
193
|
+
6. When the user asks to analyze/review an existing project, you plan analysis tasks, not creation tasks`;
|
|
142
194
|
// ── CEO Brain ───────────────────────────────────────────────────
|
|
143
195
|
class CEOBrain {
|
|
144
196
|
config;
|
|
145
197
|
client = null;
|
|
146
198
|
conversationHistory = [];
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
projectContext = '';
|
|
200
|
+
projectScanned = false;
|
|
149
201
|
constructor(config) {
|
|
150
202
|
this.config = config;
|
|
151
203
|
if (config.apiKey === 'test') {
|
|
152
|
-
// Test mode
|
|
204
|
+
// Test mode — no client
|
|
153
205
|
}
|
|
154
206
|
else if (process.env.ANTHROPIC_API_KEY) {
|
|
155
207
|
try {
|
|
@@ -160,49 +212,110 @@ class CEOBrain {
|
|
|
160
212
|
}
|
|
161
213
|
}
|
|
162
214
|
}
|
|
215
|
+
ensureProjectContext() {
|
|
216
|
+
if (!this.projectScanned) {
|
|
217
|
+
this.projectContext = scanProjectContext();
|
|
218
|
+
this.projectScanned = true;
|
|
219
|
+
}
|
|
220
|
+
return this.projectContext;
|
|
221
|
+
}
|
|
163
222
|
/**
|
|
164
|
-
* Chat with the user —
|
|
165
|
-
* Uses smart project detection to ask relevant questions.
|
|
166
|
-
* Only decomposition (later) needs AI.
|
|
223
|
+
* Chat with the user — uses Claude API if available, template fallback otherwise.
|
|
167
224
|
*/
|
|
168
225
|
async chat(userMessage) {
|
|
169
226
|
this.conversationHistory.push({ role: 'user', content: userMessage });
|
|
227
|
+
const context = this.ensureProjectContext();
|
|
170
228
|
const messageCount = this.conversationHistory.filter(m => m.role === 'user').length;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
229
|
+
// Try AI-powered chat first
|
|
230
|
+
if (this.client) {
|
|
231
|
+
try {
|
|
232
|
+
return await this.chatWithAI(userMessage, context, messageCount);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Fall through to template
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Template fallback
|
|
239
|
+
return this.chatWithTemplate(userMessage, messageCount);
|
|
240
|
+
}
|
|
241
|
+
async chatWithAI(userMessage, projectContext, messageCount) {
|
|
242
|
+
const systemPrompt = `${CEO_SYSTEM_PROMPT}
|
|
243
|
+
|
|
244
|
+
## Current Project Context
|
|
245
|
+
${projectContext || 'Empty directory — no existing project detected.'}
|
|
246
|
+
|
|
247
|
+
## Response Rules
|
|
248
|
+
- On FIRST message: Greet the user, acknowledge what you see in the project, ask 2-3 targeted questions. Respond as JSON: {"type":"question","message":"your greeting","questions":["q1","q2"]}
|
|
249
|
+
- On SECOND message or when user says to proceed: Signal ready. Respond as JSON: {"type":"ready","message":"your confirmation","summary":"concise goal summary"}
|
|
250
|
+
- ALWAYS respond with valid JSON only, no markdown wrapping`;
|
|
251
|
+
const messages = this.conversationHistory.map(m => ({
|
|
252
|
+
role: m.role,
|
|
253
|
+
content: m.content,
|
|
254
|
+
}));
|
|
255
|
+
const response = await this.client.messages.create({
|
|
256
|
+
model: this.config.model,
|
|
257
|
+
max_tokens: 1024,
|
|
258
|
+
system: systemPrompt,
|
|
259
|
+
messages,
|
|
260
|
+
});
|
|
261
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
262
|
+
this.conversationHistory.push({ role: 'assistant', content: text });
|
|
263
|
+
// Parse the JSON response
|
|
264
|
+
try {
|
|
265
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
266
|
+
if (jsonMatch) {
|
|
267
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
268
|
+
return {
|
|
269
|
+
type: parsed.type || (messageCount >= 2 ? 'ready' : 'question'),
|
|
270
|
+
message: parsed.message || text,
|
|
271
|
+
questions: parsed.questions,
|
|
272
|
+
summary: parsed.summary,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch { }
|
|
277
|
+
// If JSON parsing fails, treat as ready after 2+ messages
|
|
278
|
+
if (messageCount >= 2) {
|
|
279
|
+
const summary = this.conversationHistory.filter(m => m.role === 'user').map(m => m.content).join(' | ');
|
|
280
|
+
return { type: 'ready', message: text, summary };
|
|
181
281
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
282
|
+
return { type: 'question', message: text };
|
|
283
|
+
}
|
|
284
|
+
chatWithTemplate(userMessage, messageCount) {
|
|
285
|
+
if (messageCount === 1) {
|
|
286
|
+
const context = this.ensureProjectContext();
|
|
287
|
+
const hasExistingProject = context.includes('package.json') || context.includes('requirements.txt') || context.includes('pyproject.toml');
|
|
288
|
+
let greeting;
|
|
289
|
+
let questions;
|
|
290
|
+
if (hasExistingProject) {
|
|
291
|
+
greeting = 'I can see there\'s already an existing project here. Let me understand what you need.';
|
|
292
|
+
questions = [
|
|
293
|
+
'What specific changes or improvements do you want the team to work on?',
|
|
294
|
+
'Are there any areas of the codebase that need special attention?',
|
|
295
|
+
'What\'s the priority — new features, bug fixes, refactoring, or something else?',
|
|
296
|
+
];
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
greeting = 'Interesting project! Let me make sure I understand exactly what you need before I rally the team.';
|
|
300
|
+
questions = [
|
|
301
|
+
'What technology/framework do you want us to use?',
|
|
302
|
+
'What are the 3 most important features or requirements?',
|
|
303
|
+
'Do you have any design preferences or references?',
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
const response = { type: 'question', message: greeting, questions };
|
|
195
307
|
this.conversationHistory.push({ role: 'assistant', content: JSON.stringify(response) });
|
|
196
308
|
return response;
|
|
197
309
|
}
|
|
198
|
-
// 3+ messages — still ready
|
|
199
310
|
const goalSummary = this.conversationHistory
|
|
200
311
|
.filter(m => m.role === 'user')
|
|
201
312
|
.map(m => m.content)
|
|
202
313
|
.join(' | ');
|
|
203
314
|
const response = {
|
|
204
315
|
type: 'ready',
|
|
205
|
-
message:
|
|
316
|
+
message: messageCount === 2
|
|
317
|
+
? `Got it! I have a clear picture now. Let me call the team together and plan the sprint.`
|
|
318
|
+
: `Adding that to the plan. Let me rally the team now.`,
|
|
206
319
|
summary: goalSummary,
|
|
207
320
|
};
|
|
208
321
|
this.conversationHistory.push({ role: 'assistant', content: JSON.stringify(response) });
|
|
@@ -210,8 +323,8 @@ class CEOBrain {
|
|
|
210
323
|
}
|
|
211
324
|
resetConversation() {
|
|
212
325
|
this.conversationHistory = [];
|
|
213
|
-
this.
|
|
214
|
-
this.
|
|
326
|
+
this.projectScanned = false;
|
|
327
|
+
this.projectContext = '';
|
|
215
328
|
}
|
|
216
329
|
getConversationContext() {
|
|
217
330
|
return this.conversationHistory
|
|
@@ -223,38 +336,44 @@ class CEOBrain {
|
|
|
223
336
|
const contextBlock = context
|
|
224
337
|
? `## Conversation Context\n${context}\n\n`
|
|
225
338
|
: '';
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
${
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
339
|
+
const projectBlock = this.ensureProjectContext();
|
|
340
|
+
return `You are Sage, CEO of an AI software company. Decompose this goal into a sprint with concrete tasks.
|
|
341
|
+
|
|
342
|
+
${contextBlock}## Goal
|
|
343
|
+
${goal}
|
|
344
|
+
|
|
345
|
+
## Current Project (files in working directory)
|
|
346
|
+
${projectBlock || 'Empty directory — new project.'}
|
|
347
|
+
|
|
348
|
+
## Available Team
|
|
349
|
+
${agentList}
|
|
350
|
+
|
|
351
|
+
## Rules
|
|
352
|
+
- CRITICAL: If there is existing code, tasks must work WITH the existing codebase, NOT create from scratch
|
|
353
|
+
- Each task must be assignable to exactly ONE agent
|
|
354
|
+
- Tasks should be 2-5 minutes of focused work each
|
|
355
|
+
- Use depends_on to express task dependencies (reference by index: "task-0", "task-1", etc.)
|
|
356
|
+
- Assign required_skills using the agent's skill IDs (sp- or ag- prefix)
|
|
357
|
+
- Priority: 1=critical, 2=high, 3=medium, 4=low, 5=nice-to-have
|
|
358
|
+
- Order tasks by dependency chain: independent tasks first
|
|
359
|
+
- Include description with enough context for the agent to work autonomously
|
|
360
|
+
- Task descriptions must reference specific files/modules from the project when working on existing code
|
|
361
|
+
|
|
362
|
+
## Response Format
|
|
363
|
+
Respond with ONLY valid JSON (no markdown, no explanation):
|
|
364
|
+
|
|
365
|
+
{
|
|
366
|
+
"sprint": { "name": "Sprint Name", "goal": "Sprint goal" },
|
|
367
|
+
"tasks": [
|
|
368
|
+
{
|
|
369
|
+
"title": "Task title",
|
|
370
|
+
"description": "Detailed description referencing specific files",
|
|
371
|
+
"assignee": "agent-id",
|
|
372
|
+
"priority": 1,
|
|
373
|
+
"required_skills": ["sp-tdd-cycle"],
|
|
374
|
+
"depends_on": []
|
|
375
|
+
}
|
|
376
|
+
]
|
|
258
377
|
}`;
|
|
259
378
|
}
|
|
260
379
|
parseDecomposition(response) {
|
|
@@ -291,30 +410,53 @@ Respond with ONLY valid JSON (no markdown, no explanation):
|
|
|
291
410
|
})),
|
|
292
411
|
};
|
|
293
412
|
}
|
|
294
|
-
async decompose(goal,
|
|
295
|
-
//
|
|
413
|
+
async decompose(goal, roster) {
|
|
414
|
+
// Try AI-powered decomposition first
|
|
415
|
+
if (this.client) {
|
|
416
|
+
try {
|
|
417
|
+
return await this.decomposeWithAI(goal, roster);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// Fall through to template
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Template fallback
|
|
296
424
|
return this.decomposeFromTemplate(goal);
|
|
297
425
|
}
|
|
298
|
-
|
|
426
|
+
async decomposeWithAI(goal, roster) {
|
|
427
|
+
const context = this.getConversationContext();
|
|
428
|
+
const prompt = this.buildDecompositionPrompt(goal, roster, context || undefined);
|
|
429
|
+
const response = await this.client.messages.create({
|
|
430
|
+
model: this.config.model,
|
|
431
|
+
max_tokens: 4096,
|
|
432
|
+
messages: [{ role: 'user', content: prompt }],
|
|
433
|
+
});
|
|
434
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
435
|
+
return this.parseDecomposition(text);
|
|
436
|
+
}
|
|
437
|
+
/** Template-based decomposition — instant, no AI. Fallback only. */
|
|
299
438
|
decomposeFromTemplate(goal) {
|
|
300
439
|
const allContext = this.conversationHistory.map(m => m.content).join(' ').toLowerCase();
|
|
301
440
|
const combined = `${goal.toLowerCase()} ${allContext}`;
|
|
441
|
+
const projectContext = this.ensureProjectContext();
|
|
442
|
+
const hasExistingProject = projectContext.includes('package.json') || projectContext.includes('requirements.txt') || projectContext.includes('pyproject.toml');
|
|
443
|
+
// If existing project detected, use analysis/improvement template
|
|
444
|
+
if (hasExistingProject && this.isAnalysisRequest(combined)) {
|
|
445
|
+
return this.analysisSprintTemplate(goal, projectContext);
|
|
446
|
+
}
|
|
302
447
|
if (this.matchesKeywords(combined, ['site', 'website', 'landing', 'página', 'pagina', 'homepage', 'web page', 'webpage'])) {
|
|
303
448
|
return this.websiteSprintTemplate(goal, combined);
|
|
304
449
|
}
|
|
305
450
|
if (this.matchesKeywords(combined, ['rest api', 'graphql', 'backend api', 'api endpoint', 'microservice', 'server api'])) {
|
|
306
451
|
return this.apiSprintTemplate(goal);
|
|
307
452
|
}
|
|
308
|
-
if (this.matchesKeywords(combined, ['app', 'mobile', 'ios', 'android', 'react native', 'flutter', 'aplicativo'])) {
|
|
309
|
-
return this.mobileSprintTemplate(goal);
|
|
310
|
-
}
|
|
311
453
|
if (this.matchesKeywords(combined, ['dashboard', 'admin', 'painel', 'analytics', 'crm', 'portal'])) {
|
|
312
454
|
return this.dashboardSprintTemplate(goal);
|
|
313
455
|
}
|
|
314
456
|
if (this.matchesKeywords(combined, ['ecommerce', 'e-commerce', 'loja', 'store', 'shop', 'produto', 'product', 'cart', 'carrinho'])) {
|
|
315
457
|
return this.ecommerceSprintTemplate(goal);
|
|
316
458
|
}
|
|
317
|
-
if (this.matchesKeywords(combined, ['cli', 'command line', 'terminal', 'tool', 'script', '
|
|
459
|
+
if (this.matchesKeywords(combined, ['cli', 'command line', 'terminal', 'tool', 'script', 'ferramenta'])) {
|
|
318
460
|
return this.cliToolSprintTemplate(goal);
|
|
319
461
|
}
|
|
320
462
|
if (this.matchesKeywords(combined, ['bot', 'chatbot', 'discord', 'telegram', 'slack', 'whatsapp'])) {
|
|
@@ -323,12 +465,52 @@ Respond with ONLY valid JSON (no markdown, no explanation):
|
|
|
323
465
|
if (this.matchesKeywords(combined, ['game', 'jogo', 'gaming', 'gameplay'])) {
|
|
324
466
|
return this.gameSprintTemplate(goal);
|
|
325
467
|
}
|
|
326
|
-
//
|
|
468
|
+
// Don't match 'app'/'mobile' too eagerly — only if clearly about mobile
|
|
469
|
+
if (this.matchesKeywords(combined, ['mobile app', 'ios app', 'android app', 'react native', 'flutter app', 'aplicativo mobile'])) {
|
|
470
|
+
return this.mobileSprintTemplate(goal);
|
|
471
|
+
}
|
|
472
|
+
// If existing project, default to improvement sprint
|
|
473
|
+
if (hasExistingProject) {
|
|
474
|
+
return this.improvementSprintTemplate(goal, projectContext);
|
|
475
|
+
}
|
|
476
|
+
// Generic fallback for new projects
|
|
327
477
|
return this.genericSprintTemplate(goal);
|
|
328
478
|
}
|
|
479
|
+
isAnalysisRequest(text) {
|
|
480
|
+
return this.matchesKeywords(text, [
|
|
481
|
+
'analis', 'review', 'revis', 'estado', 'status', 'audit', 'verificar', 'check',
|
|
482
|
+
'como est', 'what is the', 'how is', 'diagnos', 'inspect', 'avaliar', 'evaluate',
|
|
483
|
+
]);
|
|
484
|
+
}
|
|
329
485
|
matchesKeywords(text, keywords) {
|
|
330
486
|
return keywords.some(kw => text.includes(kw));
|
|
331
487
|
}
|
|
488
|
+
/** Template for analyzing an existing project */
|
|
489
|
+
analysisSprintTemplate(goal, projectContext) {
|
|
490
|
+
return {
|
|
491
|
+
sprint: { name: 'Project Analysis', goal: `Analyze and review: ${goal.slice(0, 100)}` },
|
|
492
|
+
tasks: [
|
|
493
|
+
{ title: 'Analyze project architecture and code quality', description: `Read all source files in the project directory. Analyze the architecture, code organization, patterns used, and overall quality. Identify strengths and weaknesses. Project context:\n${projectContext.slice(0, 500)}`, assignee: 'aria', priority: 1, required_skills: [], depends_on: [] },
|
|
494
|
+
{ title: 'Review tests and code coverage', description: `Run existing tests and analyze coverage. Identify untested code paths, fragile tests, and testing gaps. Check test quality and best practices.`, assignee: 'quinn', priority: 1, required_skills: [], depends_on: [] },
|
|
495
|
+
{ title: 'Audit dependencies and security', description: `Review all dependencies for outdated versions, known vulnerabilities, and unnecessary packages. Check for security issues in the codebase.`, assignee: 'gage', priority: 2, required_skills: [], depends_on: [] },
|
|
496
|
+
{ title: 'Review documentation and developer experience', description: `Check README, inline docs, API documentation. Evaluate setup process, developer onboarding experience, and documentation completeness.`, assignee: 'atlas', priority: 2, required_skills: [], depends_on: ['task-0'] },
|
|
497
|
+
{ title: 'Compile analysis report with recommendations', description: `Gather findings from all team members and compile a comprehensive report: what's working well, what needs improvement, and prioritized next steps.`, assignee: 'morgan', priority: 1, required_skills: [], depends_on: ['task-0', 'task-1', 'task-2', 'task-3'] },
|
|
498
|
+
],
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
/** Template for improving an existing project */
|
|
502
|
+
improvementSprintTemplate(goal, projectContext) {
|
|
503
|
+
return {
|
|
504
|
+
sprint: { name: 'Project Improvement', goal: `Improve: ${goal.slice(0, 100)}` },
|
|
505
|
+
tasks: [
|
|
506
|
+
{ title: 'Review existing codebase and plan changes', description: `Read the existing source code to understand the current architecture before making any changes. Plan the implementation approach. Project context:\n${projectContext.slice(0, 500)}\n\nGoal: ${goal.slice(0, 300)}`, assignee: 'aria', priority: 1, required_skills: [], depends_on: [] },
|
|
507
|
+
{ title: 'Implement primary changes', description: `Make the core changes requested by the user. Work with the existing code — modify, extend, or refactor as needed. Do not rewrite from scratch. Goal: ${goal.slice(0, 300)}`, assignee: 'dex', priority: 1, required_skills: [], depends_on: ['task-0'] },
|
|
508
|
+
{ title: 'Implement supporting changes', description: `Handle any secondary modifications, configuration updates, or infrastructure changes needed to support the primary work.`, assignee: 'gage', priority: 2, required_skills: [], depends_on: ['task-0'] },
|
|
509
|
+
{ title: 'Update tests for changes', description: `Add or update tests to cover the new/modified functionality. Ensure existing tests still pass.`, assignee: 'quinn', priority: 2, required_skills: [], depends_on: ['task-1'] },
|
|
510
|
+
{ title: 'Update documentation', description: `Update README, comments, and any documentation to reflect the changes made.`, assignee: 'atlas', priority: 3, required_skills: [], depends_on: ['task-1'] },
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
}
|
|
332
514
|
websiteSprintTemplate(goal, context) {
|
|
333
515
|
const sprintName = 'Website MVP';
|
|
334
516
|
const hasNextjs = context.includes('next') || context.includes('react');
|
|
@@ -338,70 +520,14 @@ Respond with ONLY valid JSON (no markdown, no explanation):
|
|
|
338
520
|
return {
|
|
339
521
|
sprint: { name: sprintName, goal: `Build a professional website: ${goal.slice(0, 100)}` },
|
|
340
522
|
tasks: [
|
|
341
|
-
{
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
},
|
|
349
|
-
{
|
|
350
|
-
title: 'Design UI/UX wireframes and component library',
|
|
351
|
-
description: `Design the visual style for the website: color palette, typography, spacing, and component designs. Create reusable UI components (Header, Footer, Hero, Card, Button, CTA sections). Ensure responsive design for mobile, tablet, and desktop. Context: ${goal.slice(0, 200)}`,
|
|
352
|
-
assignee: 'uma',
|
|
353
|
-
priority: 1,
|
|
354
|
-
required_skills: [],
|
|
355
|
-
depends_on: [],
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
title: 'Build Home page with Hero, Features, and CTA',
|
|
359
|
-
description: `Implement the Home/Landing page with: hero section with headline and call-to-action, services overview section, testimonials preview, and contact CTA. Use ${framework} with ${styling}. Ensure responsive design.`,
|
|
360
|
-
assignee: 'dex',
|
|
361
|
-
priority: 1,
|
|
362
|
-
required_skills: [],
|
|
363
|
-
depends_on: ['task-0'],
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
title: 'Build Services and About pages',
|
|
367
|
-
description: `Create the Services page (list all services with descriptions, icons, and CTAs) and the About page (company story, team, mission/values). Use ${framework} with ${styling}. Include placeholder content.`,
|
|
368
|
-
assignee: 'gage',
|
|
369
|
-
priority: 2,
|
|
370
|
-
required_skills: [],
|
|
371
|
-
depends_on: ['task-0'],
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
title: 'Build Gallery and Testimonials pages',
|
|
375
|
-
description: `Create the Gallery page (grid of project images with lightbox/modal) and Testimonials page (customer reviews with ratings and quotes). Use placeholder images and content.`,
|
|
376
|
-
assignee: 'gage',
|
|
377
|
-
priority: 2,
|
|
378
|
-
required_skills: [],
|
|
379
|
-
depends_on: ['task-0'],
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
title: 'Build Contact page with form',
|
|
383
|
-
description: `Create the Contact page with: contact form (name, email, phone, message, service type dropdown), company address/map placeholder, phone number, email, business hours. Form should validate inputs client-side.`,
|
|
384
|
-
assignee: 'dex',
|
|
385
|
-
priority: 2,
|
|
386
|
-
required_skills: [],
|
|
387
|
-
depends_on: ['task-0'],
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
title: 'SEO optimization and metadata',
|
|
391
|
-
description: `Add proper SEO: meta tags (title, description, OpenGraph), semantic HTML structure, alt tags for images, sitemap.xml, robots.txt. Optimize for local SEO if applicable. Add structured data (JSON-LD) for local business.`,
|
|
392
|
-
assignee: 'atlas',
|
|
393
|
-
priority: 3,
|
|
394
|
-
required_skills: [],
|
|
395
|
-
depends_on: ['task-2', 'task-3'],
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
title: 'Testing and quality review',
|
|
399
|
-
description: `Test all pages: responsive design on mobile/tablet/desktop, navigation works correctly, form validation works, images load properly, no broken links, accessibility basics (contrast, alt tags, keyboard nav). Cross-browser check.`,
|
|
400
|
-
assignee: 'quinn',
|
|
401
|
-
priority: 3,
|
|
402
|
-
required_skills: [],
|
|
403
|
-
depends_on: ['task-2', 'task-3', 'task-4', 'task-5'],
|
|
404
|
-
},
|
|
523
|
+
{ title: 'Define project architecture and tech stack', description: `Architect the website using ${framework} with ${styling}. Define folder structure, routing strategy, and component hierarchy. Create the project scaffold with all necessary configuration files.`, assignee: 'aria', priority: 1, required_skills: [], depends_on: [] },
|
|
524
|
+
{ title: 'Design UI/UX wireframes and component library', description: `Design the visual style for the website: color palette, typography, spacing, and component designs. Create reusable UI components (Header, Footer, Hero, Card, Button, CTA sections). Ensure responsive design for mobile, tablet, and desktop. Context: ${goal.slice(0, 200)}`, assignee: 'uma', priority: 1, required_skills: [], depends_on: [] },
|
|
525
|
+
{ title: 'Build Home page with Hero, Features, and CTA', description: `Implement the Home/Landing page with: hero section with headline and call-to-action, services overview section, testimonials preview, and contact CTA. Use ${framework} with ${styling}. Ensure responsive design.`, assignee: 'dex', priority: 1, required_skills: [], depends_on: ['task-0'] },
|
|
526
|
+
{ title: 'Build Services and About pages', description: `Create the Services page (list all services with descriptions, icons, and CTAs) and the About page (company story, team, mission/values). Use ${framework} with ${styling}. Include placeholder content.`, assignee: 'gage', priority: 2, required_skills: [], depends_on: ['task-0'] },
|
|
527
|
+
{ title: 'Build Gallery and Testimonials pages', description: `Create the Gallery page (grid of project images with lightbox/modal) and Testimonials page (customer reviews with ratings and quotes). Use placeholder images and content.`, assignee: 'gage', priority: 2, required_skills: [], depends_on: ['task-0'] },
|
|
528
|
+
{ title: 'Build Contact page with form', description: `Create the Contact page with: contact form (name, email, phone, message, service type dropdown), company address/map placeholder, phone number, email, business hours. Form should validate inputs client-side.`, assignee: 'dex', priority: 2, required_skills: [], depends_on: ['task-0'] },
|
|
529
|
+
{ title: 'SEO optimization and metadata', description: `Add proper SEO: meta tags (title, description, OpenGraph), semantic HTML structure, alt tags for images, sitemap.xml, robots.txt. Optimize for local SEO if applicable. Add structured data (JSON-LD) for local business.`, assignee: 'atlas', priority: 3, required_skills: [], depends_on: ['task-2', 'task-3'] },
|
|
530
|
+
{ title: 'Testing and quality review', description: `Test all pages: responsive design on mobile/tablet/desktop, navigation works correctly, form validation works, images load properly, no broken links, accessibility basics (contrast, alt tags, keyboard nav). Cross-browser check.`, assignee: 'quinn', priority: 3, required_skills: [], depends_on: ['task-2', 'task-3', 'task-4', 'task-5'] },
|
|
405
531
|
],
|
|
406
532
|
};
|
|
407
533
|
}
|
|
@@ -499,7 +625,7 @@ Respond with ONLY valid JSON (no markdown, no explanation):
|
|
|
499
625
|
],
|
|
500
626
|
};
|
|
501
627
|
}
|
|
502
|
-
/** Generic fallback template — works for ANY project type */
|
|
628
|
+
/** Generic fallback template — works for ANY new project type */
|
|
503
629
|
genericSprintTemplate(goal) {
|
|
504
630
|
return {
|
|
505
631
|
sprint: { name: 'Project Sprint', goal: `Build: ${goal.slice(0, 100)}` },
|