brain-dev 0.3.0 → 1.0.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 +85 -53
- package/bin/brain-tools.cjs +4 -0
- package/bin/lib/commands/new-project.cjs +88 -779
- package/bin/lib/commands/progress.cjs +1 -1
- package/bin/lib/commands/story.cjs +972 -0
- package/bin/lib/commands.cjs +12 -3
- package/bin/lib/state.cjs +20 -1
- package/bin/lib/story-helpers.cjs +439 -0
- package/commands/brain/new-project.md +24 -18
- package/commands/brain/story.md +35 -0
- package/hooks/bootstrap.sh +1 -1
- package/package.json +1 -1
package/bin/lib/commands.cjs
CHANGED
|
@@ -20,12 +20,12 @@ const COMMANDS = [
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
name: 'new-project',
|
|
23
|
-
description: '
|
|
24
|
-
usage: 'brain-dev new-project [--answers <json>]
|
|
23
|
+
description: 'Detect and map project, suggest next step',
|
|
24
|
+
usage: 'brain-dev new-project [--answers <json>]',
|
|
25
25
|
group: 'Setup',
|
|
26
26
|
implemented: true,
|
|
27
27
|
needsState: true,
|
|
28
|
-
args: ' --answers <json> Provide user answers as JSON
|
|
28
|
+
args: ' --answers <json> Provide user answers as JSON (goal and name)'
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
name: 'help',
|
|
@@ -65,6 +65,15 @@ const COMMANDS = [
|
|
|
65
65
|
needsState: true,
|
|
66
66
|
args: ' --full Enable plan checking + verification\n --execute --task N Execute task N\n --verify --task N Verify task N (--full only)\n --complete --task N Complete and commit task N'
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: 'story',
|
|
70
|
+
description: 'Start a body of work with research, requirements, and roadmap',
|
|
71
|
+
usage: 'brain-dev story "title" [--continue] [--list] [--complete]',
|
|
72
|
+
group: 'Lifecycle',
|
|
73
|
+
implemented: true,
|
|
74
|
+
needsState: true,
|
|
75
|
+
args: ' "title" Story title (optionally with version: "v1.1 Feature")\n --continue Resume current story step\n --list List all stories\n --complete Complete current story\n --status Show story progress\n --research Force research phase\n --no-research Skip research'
|
|
76
|
+
},
|
|
68
77
|
{
|
|
69
78
|
name: 'new-task',
|
|
70
79
|
description: 'Create a significant task with full pipeline',
|
package/bin/lib/state.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
6
|
const CURRENT_SCHEMA = 'brain/v1';
|
|
7
|
-
const CURRENT_VERSION = '0.
|
|
7
|
+
const CURRENT_VERSION = '1.0.0';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Atomic write: write to temp file, then rename.
|
|
@@ -181,6 +181,11 @@ function migrateState(data) {
|
|
|
181
181
|
migrated.tasks = { current: null, count: 0, active: [], history: [] };
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// v1.0.0 fields (story system)
|
|
185
|
+
if (!migrated.stories) {
|
|
186
|
+
migrated.stories = { current: null, count: 0, active: [], history: [] };
|
|
187
|
+
}
|
|
188
|
+
|
|
184
189
|
return migrated;
|
|
185
190
|
}
|
|
186
191
|
|
|
@@ -341,6 +346,14 @@ function generateStateMd(state) {
|
|
|
341
346
|
lines.push(`- Completed: ${state.tasks.history ? state.tasks.history.length : 0}`);
|
|
342
347
|
}
|
|
343
348
|
|
|
349
|
+
if (state.stories && state.stories.active && state.stories.active.length > 0) {
|
|
350
|
+
lines.push('');
|
|
351
|
+
lines.push('## Active Stories');
|
|
352
|
+
for (const s of state.stories.active) {
|
|
353
|
+
lines.push(`- ${s.version}: ${s.title} (${s.status})`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
344
357
|
lines.push('## Blockers');
|
|
345
358
|
|
|
346
359
|
if (Array.isArray(blockers) && blockers.length > 0) {
|
|
@@ -468,6 +481,12 @@ function createDefaultState(platform) {
|
|
|
468
481
|
count: 0,
|
|
469
482
|
active: [],
|
|
470
483
|
history: []
|
|
484
|
+
},
|
|
485
|
+
stories: {
|
|
486
|
+
current: null,
|
|
487
|
+
count: 0,
|
|
488
|
+
active: [],
|
|
489
|
+
history: []
|
|
471
490
|
}
|
|
472
491
|
};
|
|
473
492
|
}
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 6 research focus areas for new-project research phase.
|
|
8
|
+
* Each area spawns a dedicated researcher agent.
|
|
9
|
+
* @type {Array<{name: string, file: string, area: string, description: string}>}
|
|
10
|
+
*/
|
|
11
|
+
const RESEARCH_AREAS = [
|
|
12
|
+
{ name: 'stack-researcher', file: 'stack.md', area: 'Technology Stack & Ecosystem', description: 'technology stack, frameworks, libraries, and ecosystem tools' },
|
|
13
|
+
{ name: 'architecture-researcher', file: 'architecture.md', area: 'Architecture Patterns', description: 'architecture patterns, project structure, and design decisions' },
|
|
14
|
+
{ name: 'pitfalls-researcher', file: 'pitfalls.md', area: 'Pitfalls & Anti-patterns', description: 'common pitfalls, anti-patterns, and mistakes to avoid' },
|
|
15
|
+
{ name: 'security-researcher', file: 'security.md', area: 'Security & Compliance', description: 'security requirements, compliance needs, and vulnerability prevention' },
|
|
16
|
+
{ name: 'competitive-researcher', file: 'competitive.md', area: 'Competitive Analysis', description: 'competitive landscape, similar products, and differentiation opportunities' },
|
|
17
|
+
{ name: 'testing-researcher', file: 'testing.md', area: 'Testing Strategy', description: 'testing approach, coverage targets, and quality assurance strategy' }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Core vision-first questions for greenfield (new) projects.
|
|
22
|
+
* Each question includes AskUserQuestion-compatible options as guidance templates.
|
|
23
|
+
* @type {Array<{id: string, text: string, header: string, multiSelect?: boolean, options: Array<{label: string, description: string}>}>}
|
|
24
|
+
*/
|
|
25
|
+
const CORE_QUESTIONS = [
|
|
26
|
+
{
|
|
27
|
+
id: 'vision',
|
|
28
|
+
text: 'What are you building? Describe the core idea in 1-2 sentences.',
|
|
29
|
+
header: 'Project Type',
|
|
30
|
+
options: [
|
|
31
|
+
{ label: 'Web Application', description: 'Full-stack web app with frontend and backend' },
|
|
32
|
+
{ label: 'API / Backend Service', description: 'REST/GraphQL API or microservice' },
|
|
33
|
+
{ label: 'CLI Tool', description: 'Command-line tool or developer utility' },
|
|
34
|
+
{ label: 'Mobile App', description: 'iOS/Android or cross-platform mobile application' }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'users',
|
|
39
|
+
text: 'Who is it for? Who are the primary users?',
|
|
40
|
+
header: 'Target Users',
|
|
41
|
+
options: [
|
|
42
|
+
{ label: 'Developers', description: 'Software engineers and technical users' },
|
|
43
|
+
{ label: 'End Users', description: 'Non-technical consumers or business users' },
|
|
44
|
+
{ label: 'Internal Team', description: 'Internal tools for your organization' },
|
|
45
|
+
{ label: 'B2B Customers', description: 'Business clients and enterprise users' }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'features',
|
|
50
|
+
text: 'What must it do? List the 3-5 most critical features.',
|
|
51
|
+
header: 'Core Features',
|
|
52
|
+
multiSelect: true,
|
|
53
|
+
options: [
|
|
54
|
+
{ label: 'Auth & Users', description: 'User registration, login, roles, permissions' },
|
|
55
|
+
{ label: 'CRUD & Data', description: 'Create, read, update, delete operations with database' },
|
|
56
|
+
{ label: 'Real-time', description: 'WebSocket, live updates, notifications' },
|
|
57
|
+
{ label: 'Integrations', description: 'Third-party APIs, payment, email, etc.' }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'constraints',
|
|
62
|
+
text: 'Any constraints? (tech stack preferences, timeline, budget, existing codebase)',
|
|
63
|
+
header: 'Constraints',
|
|
64
|
+
options: [
|
|
65
|
+
{ label: 'Existing Codebase', description: 'Adding to or extending an existing project' },
|
|
66
|
+
{ label: 'Specific Stack', description: 'Must use a particular language/framework' },
|
|
67
|
+
{ label: 'Tight Timeline', description: 'MVP or deadline-driven development' },
|
|
68
|
+
{ label: 'No Constraints', description: 'Flexible on technology and timeline' }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build brownfield (existing project) questions dynamically from detection results.
|
|
75
|
+
* @param {object} detection - Detection result from detectProject()
|
|
76
|
+
* @returns {Array} AskUserQuestion-compatible question specs
|
|
77
|
+
*/
|
|
78
|
+
function buildBrownfieldQuestions(detection) {
|
|
79
|
+
const { stack, features, summary, workspace } = detection;
|
|
80
|
+
|
|
81
|
+
// Question 1: Vision/Goal - context-aware with workspace
|
|
82
|
+
let visionText = `I detected a ${summary}. What do you want to do?`;
|
|
83
|
+
if (workspace && workspace.siblings.length > 0) {
|
|
84
|
+
const sibNames = workspace.siblings.map(s => `${s.name} (${s.stack ? (s.stack.framework || s.stack.language || 'unknown') : 'unknown'})`).join(', ');
|
|
85
|
+
visionText = `I detected a ${summary}. I also found ${workspace.siblings.length} related project(s): ${sibNames}. What do you want to do?`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const visionQuestion = {
|
|
89
|
+
id: 'vision',
|
|
90
|
+
text: visionText,
|
|
91
|
+
header: 'Goal',
|
|
92
|
+
options: [
|
|
93
|
+
{ label: 'Add New Feature', description: 'Build something new on top of the existing codebase' },
|
|
94
|
+
{ label: 'Refactor / Improve', description: 'Improve architecture, performance, or code quality' },
|
|
95
|
+
{ label: 'Fix & Stabilize', description: 'Fix bugs, add tests, improve reliability' },
|
|
96
|
+
{ label: 'Scale & Optimize', description: 'Performance optimization, scaling, infrastructure' }
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Question 2: Users - same as greenfield but context-aware text
|
|
101
|
+
const usersQuestion = {
|
|
102
|
+
id: 'users',
|
|
103
|
+
text: 'Who are the current or target users of this project?',
|
|
104
|
+
header: 'Target Users',
|
|
105
|
+
options: [
|
|
106
|
+
{ label: 'Developers', description: 'Software engineers and technical users' },
|
|
107
|
+
{ label: 'End Users', description: 'Non-technical consumers or business users' },
|
|
108
|
+
{ label: 'Internal Team', description: 'Internal tools for your organization' },
|
|
109
|
+
{ label: 'B2B Customers', description: 'Business clients and enterprise users' }
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Question 3: Features/Priorities - dynamically built from detection
|
|
114
|
+
const featureOptions = [];
|
|
115
|
+
// Add detected features as "extend" options (max 2)
|
|
116
|
+
for (const feat of features.slice(0, 2)) {
|
|
117
|
+
featureOptions.push({ label: `Extend ${feat}`, description: `Build on existing ${feat} capability` });
|
|
118
|
+
}
|
|
119
|
+
// Fill remaining slots with generic options (total max 4)
|
|
120
|
+
const genericFeatureOpts = [
|
|
121
|
+
{ label: 'New Feature', description: 'Something not yet in the codebase' },
|
|
122
|
+
{ label: 'Testing & Quality', description: 'Improve test coverage and code quality' },
|
|
123
|
+
{ label: 'Performance', description: 'Optimize speed, memory, or resource usage' },
|
|
124
|
+
{ label: 'Documentation', description: 'Improve docs, README, API documentation' }
|
|
125
|
+
];
|
|
126
|
+
for (const opt of genericFeatureOpts) {
|
|
127
|
+
if (featureOptions.length >= 4) break;
|
|
128
|
+
featureOptions.push(opt);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const featuresText = features.length > 0
|
|
132
|
+
? `I found these capabilities: ${features.join(', ')}. What's the next priority?`
|
|
133
|
+
: 'What are the most important features or improvements?';
|
|
134
|
+
|
|
135
|
+
const featuresQuestion = {
|
|
136
|
+
id: 'features',
|
|
137
|
+
text: featuresText,
|
|
138
|
+
header: 'Priorities',
|
|
139
|
+
multiSelect: true,
|
|
140
|
+
options: featureOptions
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Question 4: Constraints - stack-aware (use primary stack)
|
|
144
|
+
const primary = stack.primary || stack;
|
|
145
|
+
const stackLabel = primary.framework || (primary.language ? primary.language.charAt(0).toUpperCase() + primary.language.slice(1) : 'current stack');
|
|
146
|
+
const constraintsText = primary.framework
|
|
147
|
+
? `The project uses ${primary.framework}${primary.language ? ` (${primary.language})` : ''}. Any additional constraints?`
|
|
148
|
+
: 'Any constraints? (timeline, budget, compatibility)';
|
|
149
|
+
|
|
150
|
+
const constraintsQuestion = {
|
|
151
|
+
id: 'constraints',
|
|
152
|
+
text: constraintsText,
|
|
153
|
+
header: 'Constraints',
|
|
154
|
+
options: [
|
|
155
|
+
{ label: `Keep ${stackLabel}`, description: 'Stay within the existing technology choices' },
|
|
156
|
+
{ label: 'Allow Migration', description: 'Open to changing frameworks or libraries' },
|
|
157
|
+
{ label: 'Tight Timeline', description: 'MVP or deadline-driven development' },
|
|
158
|
+
{ label: 'No Constraints', description: 'Flexible on approach' }
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return [visionQuestion, usersQuestion, featuresQuestion, constraintsQuestion];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Read detection.json from .brain/ directory.
|
|
167
|
+
* @param {string} brainDir - Path to the .brain directory
|
|
168
|
+
* @returns {object|null} Parsed detection data, or null if not found/invalid
|
|
169
|
+
*/
|
|
170
|
+
function readDetection(brainDir) {
|
|
171
|
+
try {
|
|
172
|
+
const detPath = path.join(brainDir, 'detection.json');
|
|
173
|
+
if (!fs.statSync(detPath).isFile()) return null;
|
|
174
|
+
const content = fs.readFileSync(detPath, 'utf8');
|
|
175
|
+
const data = JSON.parse(content);
|
|
176
|
+
if (!data || typeof data !== 'object' || !data.type) return null;
|
|
177
|
+
// Ensure stack.primary exists for safe access
|
|
178
|
+
if (data.stack && !data.stack.primary) {
|
|
179
|
+
data.stack.primary = { language: data.stack.language || null, framework: data.stack.framework || null, runtime: data.stack.runtime || null, dependencies: data.stack.dependencies || [] };
|
|
180
|
+
}
|
|
181
|
+
return data;
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build codebase context string for brownfield researcher prompts.
|
|
189
|
+
* Formats detected stack, features, and workspace info into a markdown section.
|
|
190
|
+
* @param {object} detection - Detection result from detectProject()
|
|
191
|
+
* @returns {string} Markdown-formatted codebase context
|
|
192
|
+
*/
|
|
193
|
+
function buildCodebaseContext(detection) {
|
|
194
|
+
const { stack, features, signals, workspace } = detection;
|
|
195
|
+
const primary = stack.primary || stack;
|
|
196
|
+
const lines = [
|
|
197
|
+
'',
|
|
198
|
+
'## Existing Codebase Context',
|
|
199
|
+
'',
|
|
200
|
+
'This is NOT a greenfield project. An existing codebase was detected:',
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
if (primary.framework) {
|
|
204
|
+
lines.push(`- **Primary Stack:** ${primary.framework}${primary.language ? ` (${primary.language})` : ''}`);
|
|
205
|
+
} else if (primary.language) {
|
|
206
|
+
lines.push(`- **Language:** ${primary.language}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Frontend stack (when separate from primary, e.g., Laravel + Vue.js)
|
|
210
|
+
if (stack.frontend) {
|
|
211
|
+
const feParts = [stack.frontend.framework, stack.frontend.bundler, stack.frontend.css].filter(Boolean).join(' + ');
|
|
212
|
+
if (feParts) {
|
|
213
|
+
lines.push(`- **Frontend:** ${feParts}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (features.length > 0) {
|
|
218
|
+
lines.push(`- **Existing features:** ${features.join(', ')}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (signals.codeFiles > 0) {
|
|
222
|
+
lines.push(`- **Size:** ${signals.codeFiles} source files`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (signals.commitCount > 0) {
|
|
226
|
+
lines.push(`- **History:** ${signals.commitCount} commits`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const deps = primary.dependencies || [];
|
|
230
|
+
if (deps.length > 0) {
|
|
231
|
+
lines.push(`- **Key dependencies:** ${deps.slice(0, 5).join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Workspace siblings
|
|
235
|
+
if (workspace && workspace.siblings.length > 0) {
|
|
236
|
+
lines.push('');
|
|
237
|
+
lines.push('### Related Projects in Workspace');
|
|
238
|
+
for (const sib of workspace.siblings) {
|
|
239
|
+
lines.push(`- **${sib.name}:** ${sib.stack ? (sib.stack.framework || sib.stack.language || 'unknown') : 'unknown'}`);
|
|
240
|
+
}
|
|
241
|
+
lines.push('');
|
|
242
|
+
lines.push('Consider how this project integrates with sibling projects.');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push('**IMPORTANT:** Research should focus on EXTENDING/IMPROVING this existing codebase, not starting from scratch.');
|
|
247
|
+
lines.push('Consider compatibility with the existing stack and patterns.');
|
|
248
|
+
lines.push('');
|
|
249
|
+
|
|
250
|
+
return lines.join('\n');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generate PROJECT.md content from user answers, marking unknowns as TBD.
|
|
255
|
+
* Optionally includes an existing codebase section for brownfield projects.
|
|
256
|
+
* @param {object} answers - User answers (vision, users, features, constraints)
|
|
257
|
+
* @param {object} [detection] - Detection result for brownfield projects
|
|
258
|
+
* @returns {string} PROJECT.md markdown content
|
|
259
|
+
*/
|
|
260
|
+
function generateProjectMd(answers, detection) {
|
|
261
|
+
const tbd = (val) => {
|
|
262
|
+
if (!val) return 'TBD';
|
|
263
|
+
if (Array.isArray(val)) return val.join(', ') || 'TBD';
|
|
264
|
+
if (typeof val !== 'string') return String(val);
|
|
265
|
+
const lower = val.toLowerCase().trim();
|
|
266
|
+
if (lower === "i don't know yet" || lower === 'idk' || lower === 'not sure' || lower === 'tbd') {
|
|
267
|
+
return 'TBD';
|
|
268
|
+
}
|
|
269
|
+
return val;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const lines = [
|
|
273
|
+
'# Project',
|
|
274
|
+
'',
|
|
275
|
+
'## Vision',
|
|
276
|
+
tbd(answers.vision),
|
|
277
|
+
'',
|
|
278
|
+
'## Users',
|
|
279
|
+
tbd(answers.users),
|
|
280
|
+
'',
|
|
281
|
+
'## Features',
|
|
282
|
+
tbd(answers.features),
|
|
283
|
+
'',
|
|
284
|
+
'## Constraints',
|
|
285
|
+
tbd(answers.constraints),
|
|
286
|
+
''
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
// Add existing codebase section for brownfield projects
|
|
290
|
+
if (detection && detection.type === 'brownfield') {
|
|
291
|
+
const primary = detection.stack.primary || detection.stack;
|
|
292
|
+
lines.push('## Existing Codebase');
|
|
293
|
+
if (primary.framework) {
|
|
294
|
+
lines.push(`- **Stack:** ${primary.framework}${primary.language ? ` (${primary.language})` : ''}`);
|
|
295
|
+
} else if (primary.language) {
|
|
296
|
+
lines.push(`- **Language:** ${primary.language}`);
|
|
297
|
+
}
|
|
298
|
+
if (detection.stack.frontend) {
|
|
299
|
+
const feParts = [detection.stack.frontend.framework, detection.stack.frontend.bundler, detection.stack.frontend.css].filter(Boolean).join(' + ');
|
|
300
|
+
if (feParts) lines.push(`- **Frontend:** ${feParts}`);
|
|
301
|
+
}
|
|
302
|
+
if (detection.features.length > 0) {
|
|
303
|
+
lines.push(`- **Features:** ${detection.features.join(', ')}`);
|
|
304
|
+
}
|
|
305
|
+
if (detection.signals.codeFiles > 0) {
|
|
306
|
+
lines.push(`- **Size:** ${detection.signals.codeFiles} source files`);
|
|
307
|
+
}
|
|
308
|
+
if (detection.signals.commitCount > 0) {
|
|
309
|
+
lines.push(`- **Commits:** ${detection.signals.commitCount}`);
|
|
310
|
+
}
|
|
311
|
+
lines.push(`- **Status:** Active development`);
|
|
312
|
+
lines.push('');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return lines.join('\n');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Generate REQUIREMENTS.md content with hierarchical IDs grouped by category.
|
|
320
|
+
* Creates categories from user features and research summary content.
|
|
321
|
+
* @param {string[]} features - List of feature strings extracted from PROJECT.md
|
|
322
|
+
* @param {string} summaryContent - Content of the research SUMMARY.md
|
|
323
|
+
* @returns {{requirementsMd: string, categories: Array}} Generated markdown and category data
|
|
324
|
+
*/
|
|
325
|
+
function generateRequirementsMd(features, summaryContent) {
|
|
326
|
+
// Create categories from features and summary
|
|
327
|
+
const categories = [];
|
|
328
|
+
|
|
329
|
+
// Category 1: Core features (from user's feature list)
|
|
330
|
+
if (features.length > 0) {
|
|
331
|
+
const reqIds = features.map((_, i) => `1.${i + 1}`);
|
|
332
|
+
categories.push({
|
|
333
|
+
name: 'Core Features',
|
|
334
|
+
goal: 'Implement the primary functionality',
|
|
335
|
+
reqIds,
|
|
336
|
+
items: features.map((f, i) => ({ id: `1.${i + 1}`, text: f }))
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Category 2: Infrastructure (from research summary)
|
|
341
|
+
const infraItems = [];
|
|
342
|
+
if (summaryContent.includes('Stack') || summaryContent.includes('stack')) {
|
|
343
|
+
infraItems.push({ id: '2.1', text: 'Setup recommended technology stack' });
|
|
344
|
+
}
|
|
345
|
+
if (summaryContent.includes('Architecture') || summaryContent.includes('architecture')) {
|
|
346
|
+
infraItems.push({ id: '2.2', text: 'Implement recommended architecture patterns' });
|
|
347
|
+
}
|
|
348
|
+
if (infraItems.length > 0) {
|
|
349
|
+
categories.push({
|
|
350
|
+
name: 'Infrastructure',
|
|
351
|
+
goal: 'Setup project infrastructure and architecture',
|
|
352
|
+
reqIds: infraItems.map(i => i.id),
|
|
353
|
+
items: infraItems
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Category 3: Quality (testing + security from summary)
|
|
358
|
+
const qualityItems = [];
|
|
359
|
+
if (summaryContent.includes('Testing') || summaryContent.includes('testing')) {
|
|
360
|
+
qualityItems.push({ id: '3.1', text: 'Implement testing strategy' });
|
|
361
|
+
}
|
|
362
|
+
if (summaryContent.includes('Security') || summaryContent.includes('security')) {
|
|
363
|
+
qualityItems.push({ id: '3.2', text: 'Implement security requirements' });
|
|
364
|
+
}
|
|
365
|
+
if (qualityItems.length > 0) {
|
|
366
|
+
categories.push({
|
|
367
|
+
name: 'Quality & Security',
|
|
368
|
+
goal: 'Ensure quality and security standards',
|
|
369
|
+
reqIds: qualityItems.map(i => i.id),
|
|
370
|
+
items: qualityItems
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Fallback: if no categories created, make a default one
|
|
375
|
+
if (categories.length === 0) {
|
|
376
|
+
categories.push({
|
|
377
|
+
name: 'Setup',
|
|
378
|
+
goal: 'Initialize the project',
|
|
379
|
+
reqIds: ['1.1'],
|
|
380
|
+
items: [{ id: '1.1', text: 'Project initialization' }]
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Generate markdown
|
|
385
|
+
const lines = ['# Requirements', ''];
|
|
386
|
+
|
|
387
|
+
for (const cat of categories) {
|
|
388
|
+
lines.push(`## ${cat.name}`);
|
|
389
|
+
lines.push('');
|
|
390
|
+
for (const item of cat.items) {
|
|
391
|
+
lines.push(`- **${item.id}** ${item.text}`);
|
|
392
|
+
}
|
|
393
|
+
lines.push('');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { requirementsMd: lines.join('\n'), categories };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Extract features list from PROJECT.md content.
|
|
401
|
+
* Parses lines under the "## Features" heading that start with list markers.
|
|
402
|
+
* @param {string} content - Full PROJECT.md content
|
|
403
|
+
* @returns {string[]} Array of feature strings
|
|
404
|
+
*/
|
|
405
|
+
function extractFeatures(content) {
|
|
406
|
+
const featSection = extractSection(content, 'Features');
|
|
407
|
+
if (!featSection) return [];
|
|
408
|
+
|
|
409
|
+
// Split by lines starting with - or numbered items
|
|
410
|
+
return featSection
|
|
411
|
+
.split('\n')
|
|
412
|
+
.map(l => l.replace(/^[-*\d.)\s]+/, '').trim())
|
|
413
|
+
.filter(l => l.length > 0);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Extract a markdown section's content by heading name.
|
|
418
|
+
* Looks for "## heading" and returns text until the next "## " or end of content.
|
|
419
|
+
* @param {string} content - Full markdown content
|
|
420
|
+
* @param {string} heading - Section heading to find (without the ## prefix)
|
|
421
|
+
* @returns {string} Trimmed section content, or empty string if not found
|
|
422
|
+
*/
|
|
423
|
+
function extractSection(content, heading) {
|
|
424
|
+
const regex = new RegExp(`## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
425
|
+
const match = content.match(regex);
|
|
426
|
+
return match ? match[1].trim() : '';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
module.exports = {
|
|
430
|
+
RESEARCH_AREAS,
|
|
431
|
+
CORE_QUESTIONS,
|
|
432
|
+
buildBrownfieldQuestions,
|
|
433
|
+
readDetection,
|
|
434
|
+
buildCodebaseContext,
|
|
435
|
+
generateProjectMd,
|
|
436
|
+
generateRequirementsMd,
|
|
437
|
+
extractFeatures,
|
|
438
|
+
extractSection
|
|
439
|
+
};
|
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brain:new-project
|
|
3
|
-
description:
|
|
3
|
+
description: Detect project, map codebase, and choose next step
|
|
4
4
|
allowed-tools:
|
|
5
5
|
- Read
|
|
6
6
|
- Write
|
|
7
|
-
- Edit
|
|
8
|
-
- Glob
|
|
9
|
-
- Grep
|
|
10
7
|
- Bash
|
|
11
|
-
- Task
|
|
12
8
|
- AskUserQuestion
|
|
13
9
|
---
|
|
14
10
|
<objective>
|
|
15
|
-
|
|
16
|
-
Creates PROJECT.md, REQUIREMENTS.md, and ROADMAP.md with the gathered context.
|
|
11
|
+
Set up Brain for an existing or new project. Detects your stack, maps the codebase, then asks what you want to do next.
|
|
17
12
|
</objective>
|
|
18
13
|
|
|
19
|
-
<context>
|
|
20
|
-
No arguments required. The CLI handles interactive setup.
|
|
21
|
-
</context>
|
|
22
|
-
|
|
23
14
|
<process>
|
|
24
|
-
1. Run the
|
|
15
|
+
1. Run the detection:
|
|
25
16
|
```bash
|
|
26
17
|
npx brain-dev new-project
|
|
27
18
|
```
|
|
28
19
|
|
|
29
|
-
2.
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
2. Read the CLI output. It will contain project detection results (stack, type, sibling projects).
|
|
21
|
+
|
|
22
|
+
3. IMPORTANT: Use the **AskUserQuestion** tool to present the goal question to the user. Format:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Use AskUserQuestion with these parameters:
|
|
26
|
+
- question: The detection summary + "What do you want to do?"
|
|
27
|
+
- options: ["Story — Start a story (multi-phase feature or milestone)", "Task — Work on a specific task", "Quick — Quick fix or small change"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The user will select one of the options via the interactive UI at the bottom of the screen.
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
4. After the user selects, run the finalize step with their choice:
|
|
33
|
+
```bash
|
|
34
|
+
npx brain-dev new-project --answers '{"goal":"story","name":"ProjectName"}'
|
|
35
|
+
```
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
Replace `"story"` with `"task"` or `"quick"` based on what the user selected.
|
|
38
|
+
Replace `"ProjectName"` with the project name (use the detected framework name or ask).
|
|
36
39
|
|
|
37
|
-
5.
|
|
40
|
+
5. The CLI will set up the project and suggest the next command:
|
|
41
|
+
- Story → `/brain:story "v1.0 Feature title"`
|
|
42
|
+
- Task → `/brain:new-task "description"`
|
|
43
|
+
- Quick → `/brain:quick "description"`
|
|
38
44
|
</process>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:story
|
|
3
|
+
description: Start a body of work with research, requirements, roadmap, and phased execution
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Edit
|
|
8
|
+
- Bash
|
|
9
|
+
- Grep
|
|
10
|
+
- Glob
|
|
11
|
+
- Agent
|
|
12
|
+
- AskUserQuestion
|
|
13
|
+
---
|
|
14
|
+
<objective>
|
|
15
|
+
Start a new story (milestone) — a cohesive body of work with multiple phases.
|
|
16
|
+
Brain researches the domain, generates requirements, creates a roadmap, then guides execution.
|
|
17
|
+
</objective>
|
|
18
|
+
|
|
19
|
+
<context>
|
|
20
|
+
Stories are Brain's primary way to organize significant work. A story creates:
|
|
21
|
+
- Research (6 parallel domain researchers)
|
|
22
|
+
- Requirements (hierarchical REQ-IDs)
|
|
23
|
+
- Roadmap (phased execution plan)
|
|
24
|
+
- Phases (discuss → plan → execute → verify → complete)
|
|
25
|
+
</context>
|
|
26
|
+
|
|
27
|
+
<process>
|
|
28
|
+
1. Start: `npx brain-dev story "v1.1 Feature title"`
|
|
29
|
+
2. Answer the vision/scope questions
|
|
30
|
+
3. Save answers: `npx brain-dev story --answers '{"vision":"...","users":"...","features":"...","constraints":"..."}'`
|
|
31
|
+
4. Continue through steps: `npx brain-dev story --continue` (repeat until story is active)
|
|
32
|
+
5. Work on phases: `/brain:discuss`, `/brain:plan`, `/brain:execute`, `/brain:verify`, `/brain:complete`
|
|
33
|
+
6. Complete story: `npx brain-dev story --complete`
|
|
34
|
+
7. List stories: `npx brain-dev story --list`
|
|
35
|
+
</process>
|
package/hooks/bootstrap.sh
CHANGED
|
@@ -48,7 +48,7 @@ try {
|
|
|
48
48
|
'Phase: ' + (data.phase && data.phase.current || 0) + ' (' + (data.phase && data.phase.status || 'initialized') + ')',
|
|
49
49
|
'Next: ' + (data.nextAction || '/brain:new-project'),
|
|
50
50
|
'',
|
|
51
|
-
'Commands: /brain:new-project, /brain:discuss, /brain:plan, /brain:execute, /brain:verify, /brain:complete, /brain:quick, /brain:new-task, /brain:progress, /brain:pause, /brain:resume, /brain:help, /brain:health, /brain:update, /brain:storm, /brain:adr, /brain:phase, /brain:config, /brain:map, /brain:recover, /brain:dashboard, /brain:auto (or execute --auto)',
|
|
51
|
+
'Commands: /brain:new-project, /brain:story, /brain:discuss, /brain:plan, /brain:execute, /brain:verify, /brain:complete, /brain:quick, /brain:new-task, /brain:progress, /brain:pause, /brain:resume, /brain:help, /brain:health, /brain:update, /brain:storm, /brain:adr, /brain:phase, /brain:config, /brain:map, /brain:recover, /brain:dashboard, /brain:auto (or execute --auto)',
|
|
52
52
|
'',
|
|
53
53
|
'Instructions for Claude:',
|
|
54
54
|
'- When user types /brain:<command>, run: npx brain-dev <command> [args]',
|