@patricksardinha/agentkit-cli 0.3.0 → 0.5.0
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/dist/cli.cjs +111 -111
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +111 -111
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +45 -4
- package/src/generators/claudeMdGenerator.ts +21 -15
- package/src/generators/workflowGenerator.ts +9 -36
- package/tests/commands/init.test.ts +145 -0
- package/tests/generators/blueprintSupport.test.ts +35 -30
- package/tests/generators/claudeMdGenerator.test.ts +48 -0
- package/tests/generators/workflowGenerator.test.ts +40 -0
package/src/commands/init.ts
CHANGED
|
@@ -24,6 +24,45 @@ const FRAMEWORK_LABELS: Record<StackInfo['framework'], string> = {
|
|
|
24
24
|
unknown: 'Unknown (generic)',
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const STACK_CHOICES = [
|
|
28
|
+
{ name: 'React + Vite', value: 'react' },
|
|
29
|
+
{ name: 'Next.js', value: 'nextjs' },
|
|
30
|
+
{ name: 'Tauri v2 (React + Rust)', value: 'tauri' },
|
|
31
|
+
{ name: 'FastAPI (Python)', value: 'fastapi' },
|
|
32
|
+
{ name: 'Express (Node.js)', value: 'express' },
|
|
33
|
+
{ name: 'Node.js (generic)', value: 'node' },
|
|
34
|
+
{ name: 'None of the above — generate a generic CLAUDE.md to fill manually', value: 'none' },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export async function resolveStack(
|
|
38
|
+
detected: StackInfo,
|
|
39
|
+
): Promise<{ stack: StackInfo; stackNotConfigured: boolean }> {
|
|
40
|
+
if (detected.framework !== 'unknown') {
|
|
41
|
+
return { stack: detected, stackNotConfigured: false }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logger.warn('Stack not detected automatically.')
|
|
45
|
+
const { selectedFramework } = await inquirer.prompt<{ selectedFramework: string }>([
|
|
46
|
+
{
|
|
47
|
+
type: 'list',
|
|
48
|
+
name: 'selectedFramework',
|
|
49
|
+
message: 'Stack not detected automatically. Please select your stack:',
|
|
50
|
+
choices: STACK_CHOICES as unknown as string[],
|
|
51
|
+
},
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
if (selectedFramework === 'none') {
|
|
55
|
+
return { stack: detected, stackNotConfigured: true }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const framework = selectedFramework as StackInfo['framework']
|
|
59
|
+
const language: StackInfo['language'] = framework === 'fastapi' ? 'python' : 'javascript'
|
|
60
|
+
return {
|
|
61
|
+
stack: { ...detected, framework, language, hasTypeScript: false },
|
|
62
|
+
stackNotConfigured: false,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
async function fileExists(path: string): Promise<boolean> {
|
|
28
67
|
try {
|
|
29
68
|
await readFile(path)
|
|
@@ -50,8 +89,10 @@ export function registerInit(program: Command): void {
|
|
|
50
89
|
logger.warn('Ce dossier n\'est pas un repo git — lancez git init si nécessaire')
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
const
|
|
54
|
-
|
|
92
|
+
const { stack: resolvedStack, stackNotConfigured } = await resolveStack(stack)
|
|
93
|
+
|
|
94
|
+
const label = FRAMEWORK_LABELS[resolvedStack.framework]
|
|
95
|
+
logger.info(`Stack : ${label} (${resolvedStack.language})`)
|
|
55
96
|
|
|
56
97
|
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
|
|
57
98
|
{
|
|
@@ -112,8 +153,8 @@ export function registerInit(program: Command): void {
|
|
|
112
153
|
}
|
|
113
154
|
|
|
114
155
|
const genSpinner = ora('Génération des fichiers…').start()
|
|
115
|
-
const claudeMdContent = generateClaudeMd(
|
|
116
|
-
const workflowContent = generateWorkflow(
|
|
156
|
+
const claudeMdContent = generateClaudeMd(resolvedStack, blueprintContent, stackNotConfigured)
|
|
157
|
+
const workflowContent = generateWorkflow(resolvedStack, blueprintContent, projectName)
|
|
117
158
|
const agents = extractAgentsFromWorkflow(workflowContent)
|
|
118
159
|
const playbookContent = generatePlaybook({ agents, projectName, hasBlueprint: !!blueprintContent })
|
|
119
160
|
await writeFile(claudeMdPath, claudeMdContent, 'utf-8')
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { StackInfo } from '../detectors/stackDetector.js'
|
|
2
|
-
import { parseBlueprint } from '../utils/blueprintParser.js'
|
|
3
2
|
import * as react from '../templates/react.js'
|
|
4
3
|
import * as nextjs from '../templates/nextjs.js'
|
|
5
4
|
import * as tauri from '../templates/tauri.js'
|
|
@@ -8,7 +7,19 @@ import * as express from '../templates/express.js'
|
|
|
8
7
|
import * as node from '../templates/node.js'
|
|
9
8
|
import * as unknown from '../templates/unknown.js'
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
const STACK_NOT_CONFIGURED_WARNING = `
|
|
11
|
+
## ⚠️ Stack not configured
|
|
12
|
+
AgentKit could not detect your stack and no stack was selected.
|
|
13
|
+
Before running Claude Code, fill in the following sections:
|
|
14
|
+
- Stack (framework, runtime, DB, tools)
|
|
15
|
+
- Commands (dev, build, test)
|
|
16
|
+
- Structure (folder layout)
|
|
17
|
+
|
|
18
|
+
Once filled, give Claude Code this instruction:
|
|
19
|
+
"Read PLAYBOOK.md and execute the procedure."
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
export function generateClaudeMd(stack: StackInfo, blueprintContent?: string, stackNotConfigured?: boolean): string {
|
|
12
23
|
let base: string
|
|
13
24
|
switch (stack.framework) {
|
|
14
25
|
case 'react': base = react.claudeMd(stack); break
|
|
@@ -20,23 +31,18 @@ export function generateClaudeMd(stack: StackInfo, blueprintContent?: string): s
|
|
|
20
31
|
default: base = unknown.claudeMd(stack)
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
if (stackNotConfigured) {
|
|
35
|
+
const firstNewline = base.indexOf('\n')
|
|
36
|
+
base = base.slice(0, firstNewline + 1) + STACK_NOT_CONFIGURED_WARNING + base.slice(firstNewline + 1)
|
|
37
|
+
}
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
.map((f, i) => {
|
|
30
|
-
const sub = f.items.length > 0 ? '\n' + f.items.map((it) => ` - ${it}`).join('\n') : ''
|
|
31
|
-
return `${i + 1}. **${f.name}**${sub}`
|
|
32
|
-
})
|
|
33
|
-
.join('\n')
|
|
39
|
+
if (!blueprintContent) return base
|
|
34
40
|
|
|
35
|
-
const
|
|
41
|
+
const blueprintNote = '\n> A PROJECT_BLUEPRINT.md is present — Claude Code will read it during Phase 0.\n'
|
|
36
42
|
|
|
37
43
|
const conventionsIdx = base.indexOf('\n## Conventions')
|
|
38
44
|
if (conventionsIdx !== -1) {
|
|
39
|
-
return base.slice(0, conventionsIdx) +
|
|
45
|
+
return base.slice(0, conventionsIdx) + blueprintNote + base.slice(conventionsIdx)
|
|
40
46
|
}
|
|
41
|
-
return base +
|
|
47
|
+
return base + blueprintNote
|
|
42
48
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { StackInfo } from '../detectors/stackDetector.js'
|
|
2
|
-
import { parseBlueprint } from '../utils/blueprintParser.js'
|
|
3
|
-
import { toSlug } from '../utils/agentParser.js'
|
|
4
2
|
import * as react from '../templates/react.js'
|
|
5
3
|
import * as nextjs from '../templates/nextjs.js'
|
|
6
4
|
import * as tauri from '../templates/tauri.js'
|
|
@@ -9,8 +7,8 @@ import * as express from '../templates/express.js'
|
|
|
9
7
|
import * as node from '../templates/node.js'
|
|
10
8
|
import * as unknown from '../templates/unknown.js'
|
|
11
9
|
|
|
12
|
-
export function generateWorkflow(stack: StackInfo, blueprintContent?: string): string {
|
|
13
|
-
if (blueprintContent) return
|
|
10
|
+
export function generateWorkflow(stack: StackInfo, blueprintContent?: string, projectName?: string): string {
|
|
11
|
+
if (blueprintContent) return blueprintPlaceholder(projectName ?? stack.framework)
|
|
14
12
|
|
|
15
13
|
switch (stack.framework) {
|
|
16
14
|
case 'react': return react.workflow(stack)
|
|
@@ -23,40 +21,15 @@ export function generateWorkflow(stack: StackInfo, blueprintContent?: string): s
|
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
function
|
|
27
|
-
|
|
24
|
+
function blueprintPlaceholder(projectName: string): string {
|
|
25
|
+
return `# AGENT_WORKFLOW.md — ${projectName}
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const outputLines =
|
|
33
|
-
feature.items.length > 0
|
|
34
|
-
? feature.items.map((item) => ` - ${item}`).join('\n')
|
|
35
|
-
: ` - src/${slug}/`
|
|
36
|
-
return `### Agent ${n} · ${feature.name}
|
|
37
|
-
Périmètre : Implémenter la fonctionnalité ${feature.name.toLowerCase()}
|
|
38
|
-
Produit :
|
|
39
|
-
${outputLines}
|
|
40
|
-
Critère : npm test (tests ${feature.name.toLowerCase()} passent)`
|
|
41
|
-
})
|
|
27
|
+
> This file will be filled in by Claude Code during Phase 0.
|
|
28
|
+
> Claude Code will read PROJECT_BLUEPRINT.md, propose a decomposition,
|
|
29
|
+
> and replace this content after human validation.
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
agentBlocks.push(
|
|
45
|
-
`### Agent ${ciN} · Tests & CI
|
|
46
|
-
Périmètre : Couverture de tests complète et configuration du pipeline CI
|
|
47
|
-
Produit :
|
|
48
|
-
- tests/
|
|
49
|
-
- .github/workflows/
|
|
50
|
-
Critère : npm test passe, pipeline CI vert`,
|
|
51
|
-
)
|
|
31
|
+
---
|
|
52
32
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## Stack détectée
|
|
56
|
-
Framework: ${stack.framework} | Language: ${stack.language}
|
|
57
|
-
|
|
58
|
-
## Agents
|
|
59
|
-
|
|
60
|
-
${agentBlocks.join('\n\n')}
|
|
33
|
+
*Waiting for Phase 0 decomposition...*
|
|
61
34
|
`
|
|
62
35
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import type { StackInfo } from '../../src/detectors/stackDetector.js'
|
|
3
|
+
|
|
4
|
+
vi.mock('inquirer', () => ({
|
|
5
|
+
default: {
|
|
6
|
+
prompt: vi.fn(),
|
|
7
|
+
},
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
vi.mock('../../src/utils/logger.js', () => ({
|
|
11
|
+
logger: {
|
|
12
|
+
info: vi.fn(),
|
|
13
|
+
success: vi.fn(),
|
|
14
|
+
warn: vi.fn(),
|
|
15
|
+
error: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
import inquirer from 'inquirer'
|
|
20
|
+
import { resolveStack } from '../../src/commands/init.js'
|
|
21
|
+
import { generateClaudeMd } from '../../src/generators/claudeMdGenerator.js'
|
|
22
|
+
|
|
23
|
+
function makeStack(
|
|
24
|
+
framework: StackInfo['framework'],
|
|
25
|
+
opts: Partial<Omit<StackInfo, 'framework'>> = {},
|
|
26
|
+
): StackInfo {
|
|
27
|
+
return {
|
|
28
|
+
framework,
|
|
29
|
+
language: framework === 'fastapi' ? 'python' : opts.hasTypeScript ? 'typescript' : 'javascript',
|
|
30
|
+
hasTypeScript: opts.hasTypeScript ?? false,
|
|
31
|
+
extras: opts.extras ?? [],
|
|
32
|
+
...opts,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('resolveStack', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns detected stack unchanged when framework is not unknown — no prompt shown', async () => {
|
|
42
|
+
const detected = makeStack('react', { hasTypeScript: true })
|
|
43
|
+
const result = await resolveStack(detected)
|
|
44
|
+
|
|
45
|
+
expect(result.stack).toEqual(detected)
|
|
46
|
+
expect(result.stackNotConfigured).toBe(false)
|
|
47
|
+
expect(vi.mocked(inquirer.prompt)).not.toHaveBeenCalled()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('does not prompt for any known framework', async () => {
|
|
51
|
+
const frameworks: StackInfo['framework'][] = ['nextjs', 'tauri', 'fastapi', 'express', 'node']
|
|
52
|
+
for (const framework of frameworks) {
|
|
53
|
+
vi.clearAllMocks()
|
|
54
|
+
const result = await resolveStack(makeStack(framework))
|
|
55
|
+
expect(result.stackNotConfigured).toBe(false)
|
|
56
|
+
expect(vi.mocked(inquirer.prompt)).not.toHaveBeenCalled()
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('prompts for stack selection when framework is unknown', async () => {
|
|
61
|
+
vi.mocked(inquirer.prompt).mockResolvedValueOnce({ selectedFramework: 'tauri' })
|
|
62
|
+
|
|
63
|
+
const result = await resolveStack(makeStack('unknown'))
|
|
64
|
+
|
|
65
|
+
expect(vi.mocked(inquirer.prompt)).toHaveBeenCalledOnce()
|
|
66
|
+
expect(result.stack.framework).toBe('tauri')
|
|
67
|
+
expect(result.stack.language).toBe('javascript')
|
|
68
|
+
expect(result.stackNotConfigured).toBe(false)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('unknown + user selects Tauri → stack.framework is tauri', async () => {
|
|
72
|
+
vi.mocked(inquirer.prompt).mockResolvedValueOnce({ selectedFramework: 'tauri' })
|
|
73
|
+
|
|
74
|
+
const result = await resolveStack(makeStack('unknown'))
|
|
75
|
+
|
|
76
|
+
expect(result.stack.framework).toBe('tauri')
|
|
77
|
+
expect(result.stackNotConfigured).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('unknown + user selects FastAPI → language is python', async () => {
|
|
81
|
+
vi.mocked(inquirer.prompt).mockResolvedValueOnce({ selectedFramework: 'fastapi' })
|
|
82
|
+
|
|
83
|
+
const result = await resolveStack(makeStack('unknown'))
|
|
84
|
+
|
|
85
|
+
expect(result.stack.framework).toBe('fastapi')
|
|
86
|
+
expect(result.stack.language).toBe('python')
|
|
87
|
+
expect(result.stackNotConfigured).toBe(false)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('unknown + user selects "None of the above" → stackNotConfigured is true, framework stays unknown', async () => {
|
|
91
|
+
vi.mocked(inquirer.prompt).mockResolvedValueOnce({ selectedFramework: 'none' })
|
|
92
|
+
|
|
93
|
+
const result = await resolveStack(makeStack('unknown'))
|
|
94
|
+
|
|
95
|
+
expect(result.stack.framework).toBe('unknown')
|
|
96
|
+
expect(result.stackNotConfigured).toBe(true)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('generateClaudeMd — stackNotConfigured warning', () => {
|
|
101
|
+
it('includes warning block when stackNotConfigured is true', () => {
|
|
102
|
+
const result = generateClaudeMd(makeStack('unknown'), undefined, true)
|
|
103
|
+
expect(result).toContain('⚠️ Stack not configured')
|
|
104
|
+
expect(result).toContain('AgentKit could not detect your stack')
|
|
105
|
+
expect(result).toContain('Stack (framework, runtime, DB, tools)')
|
|
106
|
+
expect(result).toContain('Read PLAYBOOK.md and execute the procedure.')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('does not include warning block when stackNotConfigured is false', () => {
|
|
110
|
+
const result = generateClaudeMd(makeStack('unknown'), undefined, false)
|
|
111
|
+
expect(result).not.toContain('⚠️ Stack not configured')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('does not include warning block when stackNotConfigured is omitted', () => {
|
|
115
|
+
const result = generateClaudeMd(makeStack('unknown'))
|
|
116
|
+
expect(result).not.toContain('⚠️ Stack not configured')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('unknown + None selected → uses unknown template content', () => {
|
|
120
|
+
const result = generateClaudeMd(makeStack('unknown'), undefined, true)
|
|
121
|
+
expect(result).toContain('## Stack')
|
|
122
|
+
expect(result).toContain('## Commands')
|
|
123
|
+
expect(result).toContain('## Conventions')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('unknown + Tauri selected → resolveStack returns tauri, generateClaudeMd uses tauri template', async () => {
|
|
127
|
+
vi.mocked(inquirer.prompt).mockResolvedValueOnce({ selectedFramework: 'tauri' })
|
|
128
|
+
|
|
129
|
+
const { stack, stackNotConfigured } = await resolveStack(makeStack('unknown'))
|
|
130
|
+
const result = generateClaudeMd(stack, undefined, stackNotConfigured)
|
|
131
|
+
|
|
132
|
+
expect(stack.framework).toBe('tauri')
|
|
133
|
+
expect(stackNotConfigured).toBe(false)
|
|
134
|
+
expect(result).toContain('Tauri')
|
|
135
|
+
expect(result).toContain('Rust')
|
|
136
|
+
expect(result).not.toContain('⚠️ Stack not configured')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('warning block still present alongside blueprint note when both are set', () => {
|
|
140
|
+
const blueprint = '# My App\n\n## Goal\nSomething\n'
|
|
141
|
+
const result = generateClaudeMd(makeStack('unknown'), blueprint, true)
|
|
142
|
+
expect(result).toContain('⚠️ Stack not configured')
|
|
143
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md is present')
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -63,27 +63,30 @@ describe('generateClaudeMd with blueprint', () => {
|
|
|
63
63
|
expect(generateClaudeMd(REACT_STACK)).toBe(generateClaudeMd(REACT_STACK, undefined))
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
it('
|
|
66
|
+
it('adds the blueprint note when blueprint is provided', () => {
|
|
67
67
|
const result = generateClaudeMd(REACT_STACK, BLUEPRINT)
|
|
68
|
-
expect(result).toContain('
|
|
69
|
-
expect(result).toContain('
|
|
70
|
-
expect(result).toContain('Dashboard')
|
|
71
|
-
expect(result).toContain('API')
|
|
68
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md is present')
|
|
69
|
+
expect(result).toContain('Phase 0')
|
|
72
70
|
})
|
|
73
71
|
|
|
74
|
-
it('
|
|
72
|
+
it('does NOT include a Features (Blueprint) section', () => {
|
|
75
73
|
const result = generateClaudeMd(REACT_STACK, BLUEPRINT)
|
|
76
|
-
expect(result).toContain('
|
|
77
|
-
expect(result).toContain('User statistics')
|
|
74
|
+
expect(result).not.toContain('## Features (Blueprint)')
|
|
78
75
|
})
|
|
79
76
|
|
|
80
|
-
it('
|
|
77
|
+
it('does NOT include blueprint sub-items as feature bullets', () => {
|
|
81
78
|
const result = generateClaudeMd(REACT_STACK, BLUEPRINT)
|
|
82
|
-
|
|
79
|
+
expect(result).not.toContain('JWT tokens')
|
|
80
|
+
expect(result).not.toContain('User statistics')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('places blueprint note before Conventions', () => {
|
|
84
|
+
const result = generateClaudeMd(REACT_STACK, BLUEPRINT)
|
|
85
|
+
const noteIdx = result.indexOf('PROJECT_BLUEPRINT.md is present')
|
|
83
86
|
const convIdx = result.indexOf('## Conventions')
|
|
84
|
-
expect(
|
|
87
|
+
expect(noteIdx).toBeGreaterThan(-1)
|
|
85
88
|
expect(convIdx).toBeGreaterThan(-1)
|
|
86
|
-
expect(
|
|
89
|
+
expect(noteIdx).toBeLessThan(convIdx)
|
|
87
90
|
})
|
|
88
91
|
|
|
89
92
|
it('still contains stack-specific content', () => {
|
|
@@ -98,33 +101,35 @@ describe('generateWorkflow with blueprint', () => {
|
|
|
98
101
|
expect(generateWorkflow(REACT_STACK)).toBe(generateWorkflow(REACT_STACK, undefined))
|
|
99
102
|
})
|
|
100
103
|
|
|
101
|
-
it('
|
|
104
|
+
it('returns a Phase 0 placeholder instead of agent blocks', () => {
|
|
102
105
|
const result = generateWorkflow(REACT_STACK, BLUEPRINT)
|
|
103
|
-
expect(result).toContain('
|
|
104
|
-
expect(result).toContain('
|
|
105
|
-
expect(result).toContain('
|
|
106
|
-
expect(result).toContain('
|
|
106
|
+
expect(result).toContain('AGENT_WORKFLOW.md')
|
|
107
|
+
expect(result).toContain('Phase 0')
|
|
108
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md')
|
|
109
|
+
expect(result).toContain('Waiting for Phase 0 decomposition')
|
|
107
110
|
})
|
|
108
111
|
|
|
109
|
-
it('
|
|
112
|
+
it('does NOT generate agent blocks from blueprint sections', () => {
|
|
110
113
|
const result = generateWorkflow(REACT_STACK, BLUEPRINT)
|
|
111
|
-
|
|
112
|
-
expect(
|
|
113
|
-
expect(
|
|
114
|
-
expect(
|
|
115
|
-
expect(agents[3].name).toBe('Tests & CI')
|
|
116
|
-
expect(agents[3].slug).toBe('tests-ci')
|
|
114
|
+
expect(result).not.toContain('Agent 1')
|
|
115
|
+
expect(result).not.toContain('Authentication')
|
|
116
|
+
expect(result).not.toContain('Dashboard')
|
|
117
|
+
expect(result).not.toContain('Tests & CI')
|
|
117
118
|
})
|
|
118
119
|
|
|
119
|
-
it('
|
|
120
|
+
it('does NOT include blueprint feature items as outputs', () => {
|
|
120
121
|
const result = generateWorkflow(REACT_STACK, BLUEPRINT)
|
|
121
|
-
expect(result).toContain('JWT tokens')
|
|
122
|
-
expect(result).toContain('User statistics')
|
|
122
|
+
expect(result).not.toContain('JWT tokens')
|
|
123
|
+
expect(result).not.toContain('User statistics')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('uses projectName in heading when provided', () => {
|
|
127
|
+
const result = generateWorkflow(REACT_STACK, BLUEPRINT, 'my-app')
|
|
128
|
+
expect(result).toContain('AGENT_WORKFLOW.md — my-app')
|
|
123
129
|
})
|
|
124
130
|
|
|
125
|
-
it('
|
|
131
|
+
it('falls back to framework name when projectName is omitted', () => {
|
|
126
132
|
const result = generateWorkflow(REACT_STACK, BLUEPRINT)
|
|
127
|
-
expect(result).toContain('react')
|
|
128
|
-
expect(result).toContain('typescript')
|
|
133
|
+
expect(result).toContain('AGENT_WORKFLOW.md — react')
|
|
129
134
|
})
|
|
130
135
|
})
|
|
@@ -83,4 +83,52 @@ describe('generateClaudeMd', () => {
|
|
|
83
83
|
expect(typeof result).toBe('string')
|
|
84
84
|
expect(result.length).toBeGreaterThan(0)
|
|
85
85
|
})
|
|
86
|
+
|
|
87
|
+
describe('blueprint note', () => {
|
|
88
|
+
const blueprint = `# My Project\n\n## Goal\nBuild something\n\n## Features\n- Auth\n- Dashboard\n`
|
|
89
|
+
|
|
90
|
+
it('adds the blueprint note when blueprintContent is provided', () => {
|
|
91
|
+
const result = generateClaudeMd(makeStack('react'), blueprint)
|
|
92
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md is present')
|
|
93
|
+
expect(result).toContain('Phase 0')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('does NOT list blueprint sections as features', () => {
|
|
97
|
+
const result = generateClaudeMd(makeStack('react'), blueprint)
|
|
98
|
+
expect(result).not.toContain('Features (Blueprint)')
|
|
99
|
+
expect(result).not.toContain('**Goal**')
|
|
100
|
+
expect(result).not.toContain('**Features**')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('still contains the stack-based template content', () => {
|
|
104
|
+
const result = generateClaudeMd(makeStack('react'), blueprint)
|
|
105
|
+
expect(result).toContain('React')
|
|
106
|
+
expect(result).toContain('## Stack')
|
|
107
|
+
expect(result).toContain('## Commands')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('adds blueprint note for unknown stack with blueprint', () => {
|
|
111
|
+
const result = generateClaudeMd(makeStack('unknown'), blueprint)
|
|
112
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md is present')
|
|
113
|
+
expect(result).toContain('Phase 0')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('returns base template unchanged when blueprintContent is absent', () => {
|
|
117
|
+
const withBlueprint = generateClaudeMd(makeStack('react'), blueprint)
|
|
118
|
+
const withoutBlueprint = generateClaudeMd(makeStack('react'))
|
|
119
|
+
expect(withBlueprint).not.toBe(withoutBlueprint)
|
|
120
|
+
expect(withoutBlueprint).not.toContain('PROJECT_BLUEPRINT.md is present')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('adds blueprint note for every framework', () => {
|
|
124
|
+
const frameworks: StackInfo['framework'][] = [
|
|
125
|
+
'react', 'nextjs', 'tauri', 'fastapi', 'express', 'node', 'unknown',
|
|
126
|
+
]
|
|
127
|
+
for (const framework of frameworks) {
|
|
128
|
+
const result = generateClaudeMd(makeStack(framework), blueprint)
|
|
129
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md is present')
|
|
130
|
+
expect(result).not.toContain('Features (Blueprint)')
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
86
134
|
})
|
|
@@ -81,4 +81,44 @@ describe('generateWorkflow', () => {
|
|
|
81
81
|
expect(typeof result).toBe('string')
|
|
82
82
|
expect(result.length).toBeGreaterThan(0)
|
|
83
83
|
})
|
|
84
|
+
|
|
85
|
+
describe('blueprint placeholder', () => {
|
|
86
|
+
const blueprint = `# My Project\n\n## Goal\nBuild something\n\n## Features\n- Auth\n- Dashboard\n`
|
|
87
|
+
|
|
88
|
+
it('returns a placeholder when blueprintContent is provided', () => {
|
|
89
|
+
const result = generateWorkflow(makeStack('react'), blueprint)
|
|
90
|
+
expect(result).toContain('AGENT_WORKFLOW.md')
|
|
91
|
+
expect(result).toContain('Phase 0')
|
|
92
|
+
expect(result).toContain('PROJECT_BLUEPRINT.md')
|
|
93
|
+
expect(result).toContain('Waiting for Phase 0 decomposition')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('uses projectName in heading when provided', () => {
|
|
97
|
+
const result = generateWorkflow(makeStack('react'), blueprint, 'my-app')
|
|
98
|
+
expect(result).toContain('AGENT_WORKFLOW.md — my-app')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('falls back to framework name when projectName is omitted', () => {
|
|
102
|
+
const result = generateWorkflow(makeStack('react'), blueprint)
|
|
103
|
+
expect(result).toContain('AGENT_WORKFLOW.md — react')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('does NOT parse blueprint sections as agents', () => {
|
|
107
|
+
const result = generateWorkflow(makeStack('react'), blueprint, 'my-app')
|
|
108
|
+
expect(result).not.toContain('Agent · Goal')
|
|
109
|
+
expect(result).not.toContain('Agent · Features')
|
|
110
|
+
expect(result).not.toContain('Agent 1')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('returns the placeholder for every framework when blueprint is provided', () => {
|
|
114
|
+
const frameworks: StackInfo['framework'][] = [
|
|
115
|
+
'react', 'nextjs', 'tauri', 'fastapi', 'express', 'node', 'unknown',
|
|
116
|
+
]
|
|
117
|
+
for (const framework of frameworks) {
|
|
118
|
+
const result = generateWorkflow(makeStack(framework), blueprint, 'proj')
|
|
119
|
+
expect(result).toContain('Phase 0')
|
|
120
|
+
expect(result).not.toContain('Agent 1')
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
})
|
|
84
124
|
})
|