brain-dev 1.0.1 → 1.1.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 +11 -0
- package/bin/lib/commands/execute.cjs +3 -1
- package/bin/lib/commands/plan.cjs +3 -1
- package/bin/lib/commands/quick.cjs +5 -2
- package/bin/lib/commands/verify.cjs +3 -1
- package/bin/lib/context.cjs +15 -2
- package/bin/lib/stack-expert.cjs +320 -0
- package/bin/lib/state.cjs +1 -1
- package/bin/templates/executor.md +9 -0
- package/bin/templates/planner.md +6 -0
- package/bin/templates/verifier.md +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,6 +136,17 @@ Brain auto-detects your project on init:
|
|
|
136
136
|
- **Workspace siblings**: Detects `myapp-mobile/`, `myapp-landing/` alongside `myapp/`
|
|
137
137
|
- **Stack-aware**: Asks "I detected a Laravel (PHP) + Vue.js frontend. What do you want to do?" instead of generic "What are you building?"
|
|
138
138
|
|
|
139
|
+
## Stack Expertise
|
|
140
|
+
|
|
141
|
+
Brain automatically provides technology-specific guidance to all agents based on your detected stack:
|
|
142
|
+
|
|
143
|
+
- **15+ frameworks built-in**: Laravel, Next.js, React Native, Express, Django, Rails, Vue.js, NestJS, Flutter, Spring Boot, FastAPI, Astro, SvelteKit, and more
|
|
144
|
+
- **3-layer expertise**: Static patterns + project-specific codebase analysis + Context7 live documentation
|
|
145
|
+
- **Role-aware**: Planner gets file structure guidance, executor gets CLI commands and anti-patterns, verifier gets convention checks
|
|
146
|
+
- **Any technology works**: Unknown frameworks automatically use Context7 for latest documentation
|
|
147
|
+
|
|
148
|
+
No configuration needed — Brain detects your stack and injects expertise into every pipeline stage.
|
|
149
|
+
|
|
139
150
|
## Lifecycle
|
|
140
151
|
|
|
141
152
|
```
|
|
@@ -9,6 +9,7 @@ const { logEvent } = require('../logger.cjs');
|
|
|
9
9
|
const { output, error } = require('../core.cjs');
|
|
10
10
|
const { recordInvocation, estimateTokens } = require('../cost.cjs');
|
|
11
11
|
const { acquireLock, releaseLock } = require('../lock.cjs');
|
|
12
|
+
const { generateExpertise } = require('../stack-expert.cjs');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Find a phase directory under .brain/phases/ matching a phase number.
|
|
@@ -354,7 +355,8 @@ async function run(args = [], opts = {}) {
|
|
|
354
355
|
plan_content: planContent,
|
|
355
356
|
phase: phaseMatch ? phaseMatch[1].trim() : String(phaseNumber),
|
|
356
357
|
plan_number: planNumMatch ? planNumMatch[1].trim() : String(targetPlan.num),
|
|
357
|
-
subsystem: phaseMatch ? phaseMatch[1].trim() : String(phaseNumber)
|
|
358
|
+
subsystem: phaseMatch ? phaseMatch[1].trim() : String(phaseNumber),
|
|
359
|
+
stack_expertise: generateExpertise(brainDir, 'executor')
|
|
358
360
|
});
|
|
359
361
|
|
|
360
362
|
// Append orchestration instructions to prompt
|
|
@@ -9,6 +9,7 @@ const { getAgent, resolveModel } = require('../agents.cjs');
|
|
|
9
9
|
const { logEvent } = require('../logger.cjs');
|
|
10
10
|
const { estimateFromPlan, getDefaultBudget, checkBudget } = require('../complexity.cjs');
|
|
11
11
|
const { output, error, success } = require('../core.cjs');
|
|
12
|
+
const { generateExpertise } = require('../stack-expert.cjs');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Find a phase directory under .brain/phases/ matching a phase number.
|
|
@@ -90,7 +91,8 @@ function generatePlannerPrompt(phase, brainDir) {
|
|
|
90
91
|
research_summary: researchSection,
|
|
91
92
|
output_dir: outputDir,
|
|
92
93
|
phase_number_padded: padded,
|
|
93
|
-
phase_slug: slug
|
|
94
|
+
phase_slug: slug,
|
|
95
|
+
stack_expertise: generateExpertise(brainDir, 'planner')
|
|
94
96
|
});
|
|
95
97
|
|
|
96
98
|
return { prompt, output_dir: outputDir };
|
|
@@ -7,6 +7,7 @@ const { loadTemplate, interpolate } = require('../templates.cjs');
|
|
|
7
7
|
const { resolveModel } = require('../agents.cjs');
|
|
8
8
|
const { logEvent } = require('../logger.cjs');
|
|
9
9
|
const { output, error, success } = require('../core.cjs');
|
|
10
|
+
const { generateExpertise } = require('../stack-expert.cjs');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Generate a URL-safe slug from a description.
|
|
@@ -163,7 +164,8 @@ function handleInit(args, brainDir, state) {
|
|
|
163
164
|
research_summary: '_Quick mode: no research phase._',
|
|
164
165
|
output_dir: taskDir,
|
|
165
166
|
phase_number_padded: String(nextNum).padStart(3, '0'),
|
|
166
|
-
phase_slug: slug
|
|
167
|
+
phase_slug: slug,
|
|
168
|
+
stack_expertise: generateExpertise(brainDir, 'planner')
|
|
167
169
|
});
|
|
168
170
|
|
|
169
171
|
const quickConstraints = [
|
|
@@ -249,7 +251,8 @@ function handleExecute(args, brainDir, state) {
|
|
|
249
251
|
plan_content: planContent,
|
|
250
252
|
phase: `Q${taskNum}`,
|
|
251
253
|
plan_number: '1',
|
|
252
|
-
subsystem: `quick-${taskNum}
|
|
254
|
+
subsystem: `quick-${taskNum}`,
|
|
255
|
+
stack_expertise: generateExpertise(brainDir, 'executor')
|
|
253
256
|
});
|
|
254
257
|
|
|
255
258
|
const model = resolveModel('executor', state);
|
|
@@ -10,6 +10,7 @@ const { output, error, success } = require('../core.cjs');
|
|
|
10
10
|
const antiPatterns = require('../anti-patterns.cjs');
|
|
11
11
|
const { buildDebuggerSpawnInstructions } = require('./execute.cjs');
|
|
12
12
|
const { isPathWithinRoot } = require('../security.cjs');
|
|
13
|
+
const { generateExpertise } = require('../stack-expert.cjs');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Find a phase directory under .brain/phases/ matching a phase number.
|
|
@@ -271,7 +272,8 @@ async function run(args = [], opts = {}) {
|
|
|
271
272
|
must_haves: mustHavesFormatted,
|
|
272
273
|
output_path: outputPath,
|
|
273
274
|
anti_pattern_results: formatAntiPatternResults(apResults),
|
|
274
|
-
nyquist_section: nyquistSection
|
|
275
|
+
nyquist_section: nyquistSection,
|
|
276
|
+
stack_expertise: generateExpertise(brainDir, 'verifier')
|
|
275
277
|
});
|
|
276
278
|
|
|
277
279
|
// Read depth config from state (defaults to 'deep' for backward compatibility)
|
package/bin/lib/context.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { estimateTokens } = require('./tokens.cjs');
|
|
6
|
+
const { generateExpertise } = require('./stack-expert.cjs');
|
|
6
7
|
|
|
7
8
|
// Token budgets per agent type
|
|
8
9
|
const CONTEXT_BUDGETS = {
|
|
@@ -17,6 +18,7 @@ const CONTEXT_BUDGETS = {
|
|
|
17
18
|
const PRIORITY_TIERS = {
|
|
18
19
|
P0_PLAN: 0,
|
|
19
20
|
P1_STATE: 1,
|
|
21
|
+
P1_5_STACK_EXPERTISE: 1.5,
|
|
20
22
|
P2_PHASE_CONTEXT: 2,
|
|
21
23
|
P3_PRIOR_SUMMARIES: 3,
|
|
22
24
|
P4_RESEARCH: 4,
|
|
@@ -157,6 +159,14 @@ function buildContextManifest(brainDir, phaseDir, planNum) {
|
|
|
157
159
|
required: true
|
|
158
160
|
});
|
|
159
161
|
|
|
162
|
+
// P1.5: Stack Expertise (required, synthetic - handled separately)
|
|
163
|
+
manifest.push({
|
|
164
|
+
tier: PRIORITY_TIERS.P1_5_STACK_EXPERTISE,
|
|
165
|
+
label: 'Stack Expertise',
|
|
166
|
+
path: '__SYNTHETIC_STACK_EXPERTISE__',
|
|
167
|
+
required: true
|
|
168
|
+
});
|
|
169
|
+
|
|
160
170
|
// P2: Phase CONTEXT.md decisions (required)
|
|
161
171
|
if (phaseDir) {
|
|
162
172
|
manifest.push({
|
|
@@ -255,7 +265,7 @@ function detectStaleContext(manifest, referenceTimestamp) {
|
|
|
255
265
|
const staleFiles = [];
|
|
256
266
|
|
|
257
267
|
for (const entry of manifest) {
|
|
258
|
-
if (entry.path === '__SYNTHETIC_PRIOR_SUMMARIES__') continue;
|
|
268
|
+
if (entry.path === '__SYNTHETIC_PRIOR_SUMMARIES__' || entry.path === '__SYNTHETIC_STACK_EXPERTISE__') continue;
|
|
259
269
|
try {
|
|
260
270
|
const stat = fs.statSync(entry.path);
|
|
261
271
|
if (stat.mtime.getTime() > refTime) {
|
|
@@ -300,7 +310,10 @@ function assembleTaskContext(brainDir, phaseDir, planNum, opts) {
|
|
|
300
310
|
let content;
|
|
301
311
|
let sectionHeader;
|
|
302
312
|
|
|
303
|
-
if (entry.path === '
|
|
313
|
+
if (entry.path === '__SYNTHETIC_STACK_EXPERTISE__') {
|
|
314
|
+
content = generateExpertise(brainDir, agentType);
|
|
315
|
+
sectionHeader = '## Stack Expertise';
|
|
316
|
+
} else if (entry.path === '__SYNTHETIC_PRIOR_SUMMARIES__') {
|
|
304
317
|
content = collectPriorSummaries(phaseDir, planNum);
|
|
305
318
|
sectionHeader = '## Prior Plan Results';
|
|
306
319
|
} else {
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { readDetection } = require('./story-helpers.cjs');
|
|
6
|
+
|
|
7
|
+
// Knowledge base: language → framework → expertise rules
|
|
8
|
+
// NOT hardcoded content — generates guidance from PATTERNS
|
|
9
|
+
const STACK_PATTERNS = {
|
|
10
|
+
// Language-level patterns
|
|
11
|
+
languages: {
|
|
12
|
+
php: { testFramework: 'PHPUnit', packageManager: 'composer', naming: 'PSR-4 namespacing, snake_case for DB columns' },
|
|
13
|
+
javascript: { testFramework: 'Jest/Vitest', packageManager: 'npm/yarn', naming: 'camelCase functions, PascalCase components' },
|
|
14
|
+
typescript: { testFramework: 'Jest/Vitest', packageManager: 'npm/yarn', naming: 'camelCase, PascalCase types, interfaces prefixed with I optional' },
|
|
15
|
+
python: { testFramework: 'pytest', packageManager: 'pip/poetry', naming: 'snake_case everywhere, PascalCase classes' },
|
|
16
|
+
go: { testFramework: 'go test', packageManager: 'go mod', naming: 'camelCase private, PascalCase exported' },
|
|
17
|
+
ruby: { testFramework: 'RSpec/Minitest', packageManager: 'bundler', naming: 'snake_case methods, PascalCase classes' },
|
|
18
|
+
rust: { testFramework: 'cargo test', packageManager: 'cargo', naming: 'snake_case, PascalCase types' },
|
|
19
|
+
java: { testFramework: 'JUnit', packageManager: 'maven/gradle', naming: 'camelCase methods, PascalCase classes' },
|
|
20
|
+
swift: { testFramework: 'XCTest', packageManager: 'SPM', naming: 'camelCase, PascalCase types' },
|
|
21
|
+
kotlin: { testFramework: 'JUnit/Kotest', packageManager: 'gradle', naming: 'camelCase, PascalCase classes' }
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Framework-level patterns (file structure, key concepts)
|
|
25
|
+
frameworks: {
|
|
26
|
+
'Laravel': {
|
|
27
|
+
structure: 'app/Models, app/Http/Controllers, routes/api.php, database/migrations',
|
|
28
|
+
keyCommands: 'php artisan make:model, make:controller, make:migration, migrate',
|
|
29
|
+
patterns: 'Eloquent ORM, Form Requests for validation, Resource controllers, Middleware',
|
|
30
|
+
antiPatterns: 'N+1 queries (use eager loading), fat controllers (use Services/Actions), raw SQL in controllers',
|
|
31
|
+
testing: 'Feature tests in tests/Feature, Unit tests in tests/Unit, use RefreshDatabase trait'
|
|
32
|
+
},
|
|
33
|
+
'Next.js': {
|
|
34
|
+
structure: 'app/ (App Router), components/, lib/, public/, app/api/ for routes',
|
|
35
|
+
keyCommands: 'npx next dev, next build, next start',
|
|
36
|
+
patterns: 'Server Components by default, use client for interactivity, Server Actions, Route Handlers',
|
|
37
|
+
antiPatterns: 'useEffect for data fetching (use Server Components), client-side state for server data, large client bundles',
|
|
38
|
+
testing: 'Jest + React Testing Library, or Vitest, test in __tests__/ or *.test.tsx'
|
|
39
|
+
},
|
|
40
|
+
'React Native': {
|
|
41
|
+
structure: 'app/ (Expo Router) or src/screens, components/, hooks/, services/',
|
|
42
|
+
keyCommands: 'npx expo start, expo prebuild, eas build',
|
|
43
|
+
patterns: 'Functional components + hooks, React Navigation/Expo Router, AsyncStorage, StyleSheet',
|
|
44
|
+
antiPatterns: 'Inline styles (use StyleSheet), heavy computation on JS thread, large FlatList without optimization',
|
|
45
|
+
testing: 'Jest + React Native Testing Library, detox for E2E'
|
|
46
|
+
},
|
|
47
|
+
'Express': {
|
|
48
|
+
structure: 'routes/, middleware/, controllers/, models/',
|
|
49
|
+
keyCommands: 'node server.js, npm start',
|
|
50
|
+
patterns: 'Router middleware chain, error handling middleware, async/await handlers',
|
|
51
|
+
antiPatterns: 'Callback hell, no error middleware, sync operations blocking event loop',
|
|
52
|
+
testing: 'Jest/Mocha + supertest for HTTP assertions'
|
|
53
|
+
},
|
|
54
|
+
'Django': {
|
|
55
|
+
structure: 'apps/<name>/models.py, views.py, urls.py, templates/',
|
|
56
|
+
keyCommands: 'python manage.py runserver, makemigrations, migrate, createsuperuser',
|
|
57
|
+
patterns: 'Class-based views, Django ORM, Forms/Serializers, middleware',
|
|
58
|
+
antiPatterns: 'N+1 queries (use select_related/prefetch_related), logic in views (use services)',
|
|
59
|
+
testing: 'pytest-django or TestCase, factory_boy for fixtures'
|
|
60
|
+
},
|
|
61
|
+
'Rails': {
|
|
62
|
+
structure: 'app/models, app/controllers, config/routes.rb, db/migrate',
|
|
63
|
+
keyCommands: 'rails generate, rails db:migrate, rails server',
|
|
64
|
+
patterns: 'ActiveRecord, RESTful routes, concerns, service objects',
|
|
65
|
+
antiPatterns: 'Fat models (extract services), callbacks abuse, N+1 (use includes)',
|
|
66
|
+
testing: 'RSpec + FactoryBot, system tests with Capybara'
|
|
67
|
+
},
|
|
68
|
+
'Vue.js': {
|
|
69
|
+
structure: 'src/components, src/views, src/stores (Pinia), src/router',
|
|
70
|
+
keyCommands: 'npm run dev, npm run build',
|
|
71
|
+
patterns: 'Composition API, Pinia stores, Vue Router, SFC (Single File Components)',
|
|
72
|
+
antiPatterns: 'Options API in new code, mutating props, watchers for everything',
|
|
73
|
+
testing: 'Vitest + Vue Test Utils'
|
|
74
|
+
},
|
|
75
|
+
'NestJS': {
|
|
76
|
+
structure: 'src/modules/<name>/, <name>.controller.ts, <name>.service.ts, <name>.module.ts',
|
|
77
|
+
keyCommands: 'nest generate, nest start --watch',
|
|
78
|
+
patterns: 'Dependency injection, decorators, guards, interceptors, pipes',
|
|
79
|
+
antiPatterns: 'Business logic in controllers, skipping DTOs, circular dependencies',
|
|
80
|
+
testing: 'Jest with @nestjs/testing, e2e with supertest'
|
|
81
|
+
},
|
|
82
|
+
'Flutter': {
|
|
83
|
+
structure: 'lib/screens, lib/widgets, lib/models, lib/services',
|
|
84
|
+
keyCommands: 'flutter run, flutter build, flutter test',
|
|
85
|
+
patterns: 'BLoC/Provider/Riverpod state management, Widget composition, Navigator 2.0',
|
|
86
|
+
antiPatterns: 'Deep widget trees (extract widgets), setState everywhere, blocking UI thread',
|
|
87
|
+
testing: 'flutter_test, widget tests, integration_test'
|
|
88
|
+
},
|
|
89
|
+
'Spring Boot': {
|
|
90
|
+
structure: 'src/main/java/com/example/, controllers/, services/, repositories/',
|
|
91
|
+
keyCommands: 'mvn spring-boot:run, ./gradlew bootRun',
|
|
92
|
+
patterns: '@RestController, @Service, @Repository, JPA entities, @Autowired',
|
|
93
|
+
antiPatterns: 'Field injection (use constructor), business logic in controllers, N+1 JPA',
|
|
94
|
+
testing: 'JUnit 5 + Mockito, @SpringBootTest, @WebMvcTest'
|
|
95
|
+
},
|
|
96
|
+
'FastAPI': {
|
|
97
|
+
structure: 'app/main.py, app/routers/, app/models/, app/schemas/',
|
|
98
|
+
keyCommands: 'uvicorn app.main:app --reload',
|
|
99
|
+
patterns: 'Pydantic models, dependency injection, async/await, path operations',
|
|
100
|
+
antiPatterns: 'Sync database calls in async endpoints, no Pydantic validation, global state',
|
|
101
|
+
testing: 'pytest + httpx AsyncClient'
|
|
102
|
+
},
|
|
103
|
+
'Astro': {
|
|
104
|
+
structure: 'src/pages, src/components, src/layouts, public/',
|
|
105
|
+
keyCommands: 'npm run dev, astro build',
|
|
106
|
+
patterns: 'Islands architecture, .astro components, content collections, SSG by default',
|
|
107
|
+
antiPatterns: 'Client-side JS when static works, large island components, ignoring content collections',
|
|
108
|
+
testing: 'Vitest for logic, Playwright for E2E'
|
|
109
|
+
},
|
|
110
|
+
'SvelteKit': {
|
|
111
|
+
structure: 'src/routes, src/lib, src/lib/components',
|
|
112
|
+
keyCommands: 'npm run dev, npm run build',
|
|
113
|
+
patterns: 'File-based routing, load functions, stores, form actions',
|
|
114
|
+
antiPatterns: 'Client-side fetching in load functions, reactive statements abuse',
|
|
115
|
+
testing: 'Vitest + @testing-library/svelte, Playwright'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generate role-specific technology expertise from 3 layers.
|
|
122
|
+
* @param {string} brainDir
|
|
123
|
+
* @param {string} [role='general'] - 'planner' | 'executor' | 'verifier' | 'general'
|
|
124
|
+
* @returns {string} Markdown expertise block for template injection
|
|
125
|
+
*/
|
|
126
|
+
function generateExpertise(brainDir, role = 'general') {
|
|
127
|
+
const detection = readDetection(brainDir);
|
|
128
|
+
if (!detection) return '';
|
|
129
|
+
|
|
130
|
+
const sections = [];
|
|
131
|
+
const stack = detection.stack || {};
|
|
132
|
+
const lang = stack.language || stack.primary?.language;
|
|
133
|
+
const framework = stack.framework || stack.primary?.framework;
|
|
134
|
+
const frontend = stack.frontend;
|
|
135
|
+
const features = detection.features || [];
|
|
136
|
+
|
|
137
|
+
sections.push('## Stack Expertise');
|
|
138
|
+
sections.push('');
|
|
139
|
+
|
|
140
|
+
// Language guidance
|
|
141
|
+
const langInfo = lang ? STACK_PATTERNS.languages[lang.toLowerCase()] : null;
|
|
142
|
+
if (langInfo) {
|
|
143
|
+
sections.push(`### Language: ${lang}`);
|
|
144
|
+
sections.push(`- Test framework: ${langInfo.testFramework}`);
|
|
145
|
+
sections.push(`- Package manager: ${langInfo.packageManager}`);
|
|
146
|
+
sections.push(`- Naming: ${langInfo.naming}`);
|
|
147
|
+
sections.push('');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Primary framework guidance
|
|
151
|
+
const fwInfo = framework ? findFrameworkPattern(framework) : null;
|
|
152
|
+
if (fwInfo) {
|
|
153
|
+
sections.push(`### Framework: ${framework}`);
|
|
154
|
+
sections.push(`- **File structure:** ${fwInfo.structure}`);
|
|
155
|
+
sections.push(`- **Key commands:** ${fwInfo.keyCommands}`);
|
|
156
|
+
sections.push(`- **Patterns to follow:** ${fwInfo.patterns}`);
|
|
157
|
+
sections.push(`- **Anti-patterns to AVOID:** ${fwInfo.antiPatterns}`);
|
|
158
|
+
sections.push(`- **Testing:** ${fwInfo.testing}`);
|
|
159
|
+
sections.push('');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Frontend framework (multi-stack)
|
|
163
|
+
if (frontend?.framework) {
|
|
164
|
+
const frontendInfo = findFrameworkPattern(frontend.framework);
|
|
165
|
+
if (frontendInfo) {
|
|
166
|
+
sections.push(`### Frontend: ${frontend.framework}`);
|
|
167
|
+
sections.push(`- **Structure:** ${frontendInfo.structure}`);
|
|
168
|
+
sections.push(`- **Patterns:** ${frontendInfo.patterns}`);
|
|
169
|
+
sections.push(`- **Anti-patterns:** ${frontendInfo.antiPatterns}`);
|
|
170
|
+
if (frontend.css) sections.push(`- **CSS:** ${frontend.css}`);
|
|
171
|
+
if (frontend.bundler) sections.push(`- **Bundler:** ${frontend.bundler}`);
|
|
172
|
+
sections.push('');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Feature-specific guidance
|
|
177
|
+
if (features.length > 0) {
|
|
178
|
+
sections.push(`### Detected Features`);
|
|
179
|
+
sections.push(`This project already has: ${features.join(', ')}`);
|
|
180
|
+
sections.push('When modifying these features, extend existing patterns rather than rebuilding.');
|
|
181
|
+
sections.push('');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Workspace siblings
|
|
185
|
+
if (detection.workspace?.siblings?.length > 0) {
|
|
186
|
+
sections.push(`### Related Projects`);
|
|
187
|
+
for (const s of detection.workspace.siblings) {
|
|
188
|
+
sections.push(`- **${s.name}:** ${s.stack?.framework || s.stack?.language || 'unknown'}`);
|
|
189
|
+
}
|
|
190
|
+
sections.push('Consider API contract compatibility when modifying shared interfaces.');
|
|
191
|
+
sections.push('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// LAYER 2: Codebase analysis (project-specific patterns)
|
|
195
|
+
const codebaseDir = path.join(brainDir, 'codebase');
|
|
196
|
+
if (fs.existsSync(codebaseDir)) {
|
|
197
|
+
const conventionsPath = path.join(codebaseDir, 'CONVENTIONS.md');
|
|
198
|
+
const stackPath = path.join(codebaseDir, 'STACK.md');
|
|
199
|
+
|
|
200
|
+
if (fs.existsSync(conventionsPath)) {
|
|
201
|
+
const conventions = fs.readFileSync(conventionsPath, 'utf8');
|
|
202
|
+
const excerpt = conventions.slice(0, 2000); // Keep concise
|
|
203
|
+
sections.push('### Project-Specific Conventions (from codebase analysis)');
|
|
204
|
+
sections.push('```conventions');
|
|
205
|
+
sections.push(excerpt);
|
|
206
|
+
sections.push('```');
|
|
207
|
+
sections.push('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if ((role === 'planner' || role === 'executor') && fs.existsSync(stackPath)) {
|
|
211
|
+
const stackAnalysis = fs.readFileSync(stackPath, 'utf8');
|
|
212
|
+
const excerpt = stackAnalysis.slice(0, 1500);
|
|
213
|
+
sections.push('### Existing Stack Details');
|
|
214
|
+
sections.push('```stack');
|
|
215
|
+
sections.push(excerpt);
|
|
216
|
+
sections.push('```');
|
|
217
|
+
sections.push('');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// LAYER 3: Context7 live documentation queries
|
|
222
|
+
if (framework || lang) {
|
|
223
|
+
const ctx7 = generateContext7Queries(detection);
|
|
224
|
+
if (ctx7) sections.push(ctx7);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Unknown framework: Context7 becomes PRIMARY (not fallback)
|
|
228
|
+
if (!fwInfo && (framework || lang)) {
|
|
229
|
+
sections.push(`### ${framework || lang} Project`);
|
|
230
|
+
sections.push(`IMPORTANT: No built-in patterns for ${framework || lang}.`);
|
|
231
|
+
sections.push('You MUST use Context7 to look up framework conventions BEFORE writing code:');
|
|
232
|
+
sections.push(`1. Use resolve-library-id to find "${framework || lang}"`);
|
|
233
|
+
sections.push('2. Query for: file structure, naming conventions, testing patterns');
|
|
234
|
+
sections.push('3. Follow the retrieved conventions strictly');
|
|
235
|
+
sections.push('');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Role-specific guidance
|
|
239
|
+
if (role === 'planner') {
|
|
240
|
+
sections.push('### Planner Guidance');
|
|
241
|
+
sections.push('- Ensure task file paths match framework directory structure');
|
|
242
|
+
sections.push('- Include test tasks using the detected test framework');
|
|
243
|
+
sections.push('- Set must_haves that reference framework conventions');
|
|
244
|
+
sections.push('');
|
|
245
|
+
} else if (role === 'executor') {
|
|
246
|
+
sections.push('### Executor Guidance');
|
|
247
|
+
sections.push('- Use framework CLI commands (listed above) to scaffold where possible');
|
|
248
|
+
sections.push('- Follow naming conventions strictly');
|
|
249
|
+
sections.push('- AVOID all listed anti-patterns');
|
|
250
|
+
sections.push('- Write tests in the detected test framework');
|
|
251
|
+
sections.push('');
|
|
252
|
+
} else if (role === 'verifier') {
|
|
253
|
+
sections.push('### Verifier Guidance');
|
|
254
|
+
sections.push('- Verify files are in correct framework directories');
|
|
255
|
+
sections.push('- Check naming follows detected conventions');
|
|
256
|
+
sections.push('- Scan for anti-patterns from the list above');
|
|
257
|
+
sections.push('- Verify tests use the correct test framework');
|
|
258
|
+
sections.push('');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return sections.join('\n');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Find a framework pattern by name (case-insensitive, partial match).
|
|
266
|
+
* @param {string} name - Framework name to look up
|
|
267
|
+
* @returns {object|null} Framework pattern object or null
|
|
268
|
+
*/
|
|
269
|
+
function findFrameworkPattern(name) {
|
|
270
|
+
if (!name) return null;
|
|
271
|
+
const lower = name.toLowerCase();
|
|
272
|
+
// Exact match first
|
|
273
|
+
for (const [key, value] of Object.entries(STACK_PATTERNS.frameworks)) {
|
|
274
|
+
if (key.toLowerCase() === lower) return value;
|
|
275
|
+
}
|
|
276
|
+
// startsWith match (e.g., "Next.js 15" matches "Next.js")
|
|
277
|
+
for (const [key, value] of Object.entries(STACK_PATTERNS.frameworks)) {
|
|
278
|
+
if (lower.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(lower)) {
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Generate Context7 lookup instructions for the detected stack.
|
|
287
|
+
* @param {object|string} detectionOrBrainDir - Parsed detection object or brainDir path
|
|
288
|
+
* @returns {string} Context7 query instructions
|
|
289
|
+
*/
|
|
290
|
+
function generateContext7Queries(detectionOrBrainDir) {
|
|
291
|
+
const detection = typeof detectionOrBrainDir === 'string'
|
|
292
|
+
? readDetection(detectionOrBrainDir)
|
|
293
|
+
: detectionOrBrainDir;
|
|
294
|
+
if (!detection) return '';
|
|
295
|
+
|
|
296
|
+
const framework = detection.stack?.framework || detection.stack?.primary?.framework;
|
|
297
|
+
const lang = detection.stack?.language || detection.stack?.primary?.language;
|
|
298
|
+
|
|
299
|
+
if (!framework && !lang) return '';
|
|
300
|
+
|
|
301
|
+
const lines = [];
|
|
302
|
+
lines.push('### Live Documentation (Context7)');
|
|
303
|
+
lines.push('If Context7 MCP is available, query for latest docs:');
|
|
304
|
+
if (framework) {
|
|
305
|
+
lines.push(`1. resolve-library-id: "${framework}"`);
|
|
306
|
+
lines.push(`2. query-docs with the resolved ID for task-relevant topics`);
|
|
307
|
+
}
|
|
308
|
+
if (lang && lang !== 'javascript' && lang !== 'typescript') {
|
|
309
|
+
lines.push(`- Also check: "${lang} standard library" for core patterns`);
|
|
310
|
+
}
|
|
311
|
+
lines.push('');
|
|
312
|
+
return lines.join('\n');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
generateExpertise,
|
|
317
|
+
generateContext7Queries,
|
|
318
|
+
findFrameworkPattern,
|
|
319
|
+
STACK_PATTERNS
|
|
320
|
+
};
|
package/bin/lib/state.cjs
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Executor Agent Instructions
|
|
2
2
|
|
|
3
|
+
## Technology Context
|
|
4
|
+
{{stack_expertise}}
|
|
5
|
+
|
|
6
|
+
When implementing, follow the framework patterns above:
|
|
7
|
+
- Place files in the correct framework directories
|
|
8
|
+
- Use framework-specific naming conventions
|
|
9
|
+
- Avoid the listed anti-patterns
|
|
10
|
+
- Write tests using the detected test framework
|
|
11
|
+
|
|
3
12
|
## Plan to Execute
|
|
4
13
|
|
|
5
14
|
**Plan file:** {{plan_path}}
|
package/bin/templates/planner.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
You are a planner agent creating execution plans for **Phase {{phase_number}}: {{phase_name}}**.
|
|
4
4
|
|
|
5
|
+
## Technology Context
|
|
6
|
+
{{stack_expertise}}
|
|
7
|
+
|
|
8
|
+
When creating plans, ensure tasks follow the framework patterns above.
|
|
9
|
+
File paths, naming, and testing approach must match the detected stack.
|
|
10
|
+
|
|
5
11
|
## Phase Context
|
|
6
12
|
|
|
7
13
|
**Goal:** {{phase_goal}}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Verifier Agent Instructions
|
|
2
2
|
|
|
3
|
+
## Technology Context
|
|
4
|
+
{{stack_expertise}}
|
|
5
|
+
|
|
6
|
+
During verification, check:
|
|
7
|
+
- Files are in correct framework directories
|
|
8
|
+
- Naming follows framework conventions
|
|
9
|
+
- No anti-patterns from the list above
|
|
10
|
+
- Tests use the correct test framework
|
|
11
|
+
|
|
3
12
|
## Phase Must-Haves
|
|
4
13
|
|
|
5
14
|
{{must_haves}}
|