@iservu-inc/adf-cli 0.17.1 → 0.17.5
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/CLAUDE.md +1 -1
- package/lib/commands/init.js +2 -1
- package/lib/templates/scripts/analyze-docs.js +12 -1
- package/lib/utils/context-extractor.js +48 -0
- package/lib/utils/framework-detector.js +10 -1
- package/package.json +1 -1
- package/tests/context-extractor.test.js +45 -0
- package/tests/framework-detector.test.js +28 -0
package/CLAUDE.md
CHANGED
|
@@ -72,7 +72,7 @@ npm publish --dry-run
|
|
|
72
72
|
- Routes to command handlers in `lib/commands/`
|
|
73
73
|
|
|
74
74
|
2. **Commands** (`lib/commands/`)
|
|
75
|
-
- `init.js` - Initialize framework, detect project, start interview session
|
|
75
|
+
- `init.js` - Initialize framework, detect project (brownfield detection: agent-native, openspec, specification-driven, gemini-conductor), start interview session
|
|
76
76
|
- `config.js` - Configure AI provider, analysis settings, learning system
|
|
77
77
|
- `deploy.js` - Deploy to development tools (Windsurf, Cursor, VS Code, etc.)
|
|
78
78
|
- `update.js` - Check for and install CLI updates
|
package/lib/commands/init.js
CHANGED
|
@@ -38,7 +38,8 @@ async function init(options) {
|
|
|
38
38
|
const frameworkNames = {
|
|
39
39
|
'agent-native': 'Agent-Native',
|
|
40
40
|
'openspec': 'OpenSpec',
|
|
41
|
-
'specification-driven': 'Specification-Driven'
|
|
41
|
+
'specification-driven': 'Specification-Driven',
|
|
42
|
+
'gemini-conductor': 'Gemini CLI Conductor'
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
const displayNames = detectedFrameworks
|
|
@@ -106,6 +106,7 @@ class DocumentAnalyzer {
|
|
|
106
106
|
path.join(this.projectRoot, 'stories'),
|
|
107
107
|
path.join(this.projectRoot, 'specs'),
|
|
108
108
|
path.join(this.projectRoot, 'shared'),
|
|
109
|
+
path.join(this.projectRoot, 'conductor'),
|
|
109
110
|
this.projectRoot // Root level files
|
|
110
111
|
];
|
|
111
112
|
|
|
@@ -206,6 +207,11 @@ class DocumentAnalyzer {
|
|
|
206
207
|
if (content.match(/qa\s+agent/i)) keywords.add('qa-agent');
|
|
207
208
|
if (content.match(/sm\s+agent/i) || content.match(/scrum\s+master/i)) keywords.add('sm-agent');
|
|
208
209
|
|
|
210
|
+
// Conductor indicators
|
|
211
|
+
if (content.includes('conductor')) keywords.add('gemini-conductor');
|
|
212
|
+
if (content.includes('Gemini CLI')) keywords.add('gemini-conductor');
|
|
213
|
+
if (content.includes('gemini conductor')) keywords.add('gemini-conductor');
|
|
214
|
+
|
|
209
215
|
// Workflow indicators
|
|
210
216
|
if (content.includes('TDD') || content.includes('Test-Driven')) keywords.add('tdd');
|
|
211
217
|
if (content.includes('RED-GREEN-REFACTOR') || content.includes('red → green → refactor')) keywords.add('tdd-workflow');
|
|
@@ -231,7 +237,8 @@ class DocumentAnalyzer {
|
|
|
231
237
|
bmad: 0,
|
|
232
238
|
openSpec: 0,
|
|
233
239
|
contextEngineering: 0,
|
|
234
|
-
adf: 0
|
|
240
|
+
adf: 0,
|
|
241
|
+
conductor: 0
|
|
235
242
|
};
|
|
236
243
|
|
|
237
244
|
for (const doc of this.analysis.documents) {
|
|
@@ -262,6 +269,10 @@ class DocumentAnalyzer {
|
|
|
262
269
|
// ADF indicators
|
|
263
270
|
if (doc.keywords.includes('adf-framework')) frameworkIndicators.adf += 5;
|
|
264
271
|
if (fs.existsSync(path.join(this.projectRoot, '.adf-context.json'))) frameworkIndicators.adf += 10;
|
|
272
|
+
|
|
273
|
+
// Conductor indicators
|
|
274
|
+
if (doc.keywords.includes('gemini-conductor')) frameworkIndicators.conductor += 5;
|
|
275
|
+
if (doc.path && doc.path.includes('conductor')) frameworkIndicators.conductor += 3;
|
|
265
276
|
}
|
|
266
277
|
|
|
267
278
|
// Determine detected framework
|
|
@@ -63,6 +63,10 @@ class ContextExtractor {
|
|
|
63
63
|
context = await this.extractFromOpenSpec2(projectDir, context);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
if (frameworks.includes('gemini-conductor')) {
|
|
67
|
+
context = await this.extractFromConductor(projectDir, context);
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
return context;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -120,6 +124,50 @@ class ContextExtractor {
|
|
|
120
124
|
return context;
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
static async extractFromConductor(projectDir, context) {
|
|
128
|
+
const conductorDir = path.join(projectDir, 'conductor');
|
|
129
|
+
|
|
130
|
+
// Extract from product.md (project overview, features, vision)
|
|
131
|
+
const productFile = path.join(conductorDir, 'product.md');
|
|
132
|
+
if (await fs.pathExists(productFile)) {
|
|
133
|
+
const content = await fs.readFile(productFile, 'utf-8');
|
|
134
|
+
|
|
135
|
+
// Try to extract project name from first heading
|
|
136
|
+
const nameMatch = content.match(/^#\s+(.+?)$/m);
|
|
137
|
+
if (nameMatch && !context.name) {
|
|
138
|
+
context.name = nameMatch[1].trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Extract overview from product description or first section
|
|
142
|
+
const overview = this.extractSection(content, 'Overview')
|
|
143
|
+
|| this.extractSection(content, 'Vision')
|
|
144
|
+
|| this.extractSection(content, 'Description');
|
|
145
|
+
if (overview) {
|
|
146
|
+
context.overview += (context.overview ? '\n\n' : '') + overview;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Extract from tech-stack.md (languages, frameworks, infrastructure)
|
|
151
|
+
const techStackFile = path.join(conductorDir, 'tech-stack.md');
|
|
152
|
+
if (await fs.pathExists(techStackFile)) {
|
|
153
|
+
const content = await fs.readFile(techStackFile, 'utf-8');
|
|
154
|
+
context.techStack += (context.techStack ? '\n\n' : '') + content.trim();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Extract architecture from workflow.md (patterns, conventions)
|
|
158
|
+
const workflowFile = path.join(conductorDir, 'workflow.md');
|
|
159
|
+
if (await fs.pathExists(workflowFile)) {
|
|
160
|
+
const content = await fs.readFile(workflowFile, 'utf-8');
|
|
161
|
+
const architecture = this.extractSection(content, 'Architecture')
|
|
162
|
+
|| this.extractSection(content, 'Quality Gates');
|
|
163
|
+
if (architecture) {
|
|
164
|
+
context.architecture += (context.architecture ? '\n\n' : '') + architecture;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return context;
|
|
169
|
+
}
|
|
170
|
+
|
|
123
171
|
/**
|
|
124
172
|
* Extracts content under a specific heading.
|
|
125
173
|
* Supports both # and ## and emojis.
|
|
@@ -9,7 +9,7 @@ class FrameworkDetector {
|
|
|
9
9
|
/**
|
|
10
10
|
* Detects which frameworks are present in the given project directory.
|
|
11
11
|
* @param {string} projectDir - The project directory to scan.
|
|
12
|
-
* @returns {Promise<string[]>} Array of detected framework IDs ('adf', 'agent-native', 'openspec', 'specification-driven').
|
|
12
|
+
* @returns {Promise<string[]>} Array of detected framework IDs ('adf', 'agent-native', 'openspec', 'specification-driven', 'gemini-conductor').
|
|
13
13
|
*/
|
|
14
14
|
static async detect(projectDir) {
|
|
15
15
|
const detected = [];
|
|
@@ -47,6 +47,15 @@ class FrameworkDetector {
|
|
|
47
47
|
detected.push('specification-driven');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// 5. Detect Gemini CLI Conductor
|
|
51
|
+
// Indicators: conductor/product.md, conductor/workflow.md
|
|
52
|
+
if (
|
|
53
|
+
await fs.pathExists(path.join(projectDir, 'conductor', 'product.md')) ||
|
|
54
|
+
await fs.pathExists(path.join(projectDir, 'conductor', 'workflow.md'))
|
|
55
|
+
) {
|
|
56
|
+
detected.push('gemini-conductor');
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
return detected;
|
|
51
60
|
}
|
|
52
61
|
}
|
package/package.json
CHANGED
|
@@ -50,6 +50,51 @@ Add many cool features.
|
|
|
50
50
|
expect(context.proposedChanges).toContain('Add many cool features');
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
test('should extract context from Gemini CLI Conductor', async () => {
|
|
54
|
+
await fs.ensureDir(path.join(tempDir, 'conductor'));
|
|
55
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'product.md'), `# My Product
|
|
56
|
+
|
|
57
|
+
## Overview
|
|
58
|
+
An AI-powered task management system.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
- Smart task prioritization
|
|
62
|
+
- Team collaboration
|
|
63
|
+
`);
|
|
64
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'tech-stack.md'), `# Tech Stack
|
|
65
|
+
|
|
66
|
+
- **Language**: TypeScript
|
|
67
|
+
- **Runtime**: Node.js
|
|
68
|
+
- **Framework**: Express
|
|
69
|
+
`);
|
|
70
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'workflow.md'), `# Development Workflow
|
|
71
|
+
|
|
72
|
+
## Quality Gates
|
|
73
|
+
All PRs must pass linting, tests, and review.
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
const context = await ContextExtractor.extract(tempDir, ['gemini-conductor']);
|
|
77
|
+
expect(context.name).toBe('My Product');
|
|
78
|
+
expect(context.overview).toContain('AI-powered task management');
|
|
79
|
+
expect(context.techStack).toContain('TypeScript');
|
|
80
|
+
expect(context.architecture).toContain('linting, tests, and review');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('should extract Conductor context with partial files', async () => {
|
|
84
|
+
await fs.ensureDir(path.join(tempDir, 'conductor'));
|
|
85
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'product.md'), `# TaskFlow
|
|
86
|
+
|
|
87
|
+
## Vision
|
|
88
|
+
A next-gen project management tool.
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
const context = await ContextExtractor.extract(tempDir, ['gemini-conductor']);
|
|
92
|
+
expect(context.name).toBe('TaskFlow');
|
|
93
|
+
expect(context.overview).toContain('next-gen project management');
|
|
94
|
+
expect(context.techStack).toBe('');
|
|
95
|
+
expect(context.architecture).toBe('');
|
|
96
|
+
});
|
|
97
|
+
|
|
53
98
|
test('should extract tech stack and architecture from specification-driven', async () => {
|
|
54
99
|
const specContent = `# Specification: App Core
|
|
55
100
|
|
|
@@ -48,6 +48,34 @@ describe('Framework Detector', () => {
|
|
|
48
48
|
expect(detected).toContain('openspec');
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
test('should detect Gemini CLI Conductor project', async () => {
|
|
52
|
+
await fs.ensureDir(path.join(tempDir, 'conductor'));
|
|
53
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'product.md'), '# My Product');
|
|
54
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'workflow.md'), '# Workflow');
|
|
55
|
+
const detected = await FrameworkDetector.detect(tempDir);
|
|
56
|
+
expect(detected).toContain('gemini-conductor');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should detect Conductor with only product.md', async () => {
|
|
60
|
+
await fs.ensureDir(path.join(tempDir, 'conductor'));
|
|
61
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'product.md'), '# My Product');
|
|
62
|
+
const detected = await FrameworkDetector.detect(tempDir);
|
|
63
|
+
expect(detected).toContain('gemini-conductor');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('should detect Conductor with only workflow.md', async () => {
|
|
67
|
+
await fs.ensureDir(path.join(tempDir, 'conductor'));
|
|
68
|
+
await fs.writeFile(path.join(tempDir, 'conductor', 'workflow.md'), '# Workflow');
|
|
69
|
+
const detected = await FrameworkDetector.detect(tempDir);
|
|
70
|
+
expect(detected).toContain('gemini-conductor');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('should not detect Conductor for empty conductor directory', async () => {
|
|
74
|
+
await fs.ensureDir(path.join(tempDir, 'conductor'));
|
|
75
|
+
const detected = await FrameworkDetector.detect(tempDir);
|
|
76
|
+
expect(detected).not.toContain('gemini-conductor');
|
|
77
|
+
});
|
|
78
|
+
|
|
51
79
|
test('should return empty array for no frameworks', async () => {
|
|
52
80
|
const detected = await FrameworkDetector.detect(tempDir);
|
|
53
81
|
expect(detected).toEqual([]);
|