@patricksardinha/agentkit-cli 0.6.0 → 0.7.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.
@@ -9,6 +9,7 @@ import { generateClaudeMd } from '../generators/claudeMdGenerator.js'
9
9
  import { generateWorkflow } from '../generators/workflowGenerator.js'
10
10
  import { generatePlaybook } from '../generators/playbookGenerator.js'
11
11
  import { generateSkills } from '../generators/skillsGenerator.js'
12
+ import { generateReadme } from '../generators/readmeGenerator.js'
12
13
  import { extractAgentsFromWorkflow } from '../utils/agentParser.js'
13
14
  import { logger } from '../utils/logger.js'
14
15
  import type { StackInfo } from '../detectors/stackDetector.js'
@@ -112,6 +113,7 @@ export function registerInit(program: Command): void {
112
113
  const claudeMdPath = join(cwd, 'CLAUDE.md')
113
114
  const workflowPath = join(cwd, 'AGENT_WORKFLOW.md')
114
115
  const playbookPath = join(cwd, 'PLAYBOOK.md')
116
+ const readmePath = join(cwd, 'README.md')
115
117
 
116
118
  // Resolve project name from package.json or directory name
117
119
  let projectName = basename(cwd)
@@ -162,11 +164,23 @@ export function registerInit(program: Command): void {
162
164
  await writeFile(workflowPath, workflowContent, 'utf-8')
163
165
  await writeFile(playbookPath, playbookContent, 'utf-8')
164
166
  await generateSkills(agents, cwd)
167
+
168
+ const readmeExists = await fileExists(readmePath)
169
+ if (!readmeExists) {
170
+ const readmeContent = generateReadme({ projectName, blueprintContent, stack: resolvedStack })
171
+ await writeFile(readmePath, readmeContent, 'utf-8')
172
+ }
173
+
165
174
  genSpinner.succeed('Fichiers générés')
166
175
 
167
176
  logger.success('CLAUDE.md → créé')
168
177
  logger.success('AGENT_WORKFLOW.md → créé')
169
178
  logger.success('PLAYBOOK.md → créé')
170
179
  logger.success(`agents/ → ${agents.length} dossier(s) créé(s)`)
180
+ if (readmeExists) {
181
+ logger.warn('README.md already exists — skipped (not overwritten)')
182
+ } else {
183
+ logger.success('README.md → créé')
184
+ }
171
185
  })
172
186
  }
@@ -0,0 +1,199 @@
1
+ import type { StackInfo } from '../detectors/stackDetector.js'
2
+
3
+ export interface ReadmeInput {
4
+ projectName: string
5
+ blueprintContent?: string
6
+ stack: StackInfo
7
+ }
8
+
9
+ function extractSection(content: string, heading: string): string {
10
+ const regex = new RegExp(`##\\s+${heading}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`)
11
+ const match = regex.exec(content)
12
+ return match ? match[1].trim() : ''
13
+ }
14
+
15
+ function stackLabel(stack: StackInfo): string {
16
+ switch (stack.framework) {
17
+ case 'react': return 'React + Vite'
18
+ case 'nextjs': return 'Next.js'
19
+ case 'tauri': return 'Tauri v2'
20
+ case 'fastapi': return 'FastAPI'
21
+ case 'express': return 'Express'
22
+ case 'node': return 'Node.js'
23
+ default: return 'Unknown'
24
+ }
25
+ }
26
+
27
+ function gettingStarted(stack: StackInfo): string {
28
+ switch (stack.framework) {
29
+ case 'tauri':
30
+ return `\`\`\`bash
31
+ npm install
32
+ npm run tauri:dev # development (hot reload)
33
+ npm run tauri:build # production bundle
34
+ \`\`\``
35
+ case 'react':
36
+ return `\`\`\`bash
37
+ npm install
38
+ npm run dev # development server
39
+ npm run build # production build
40
+ \`\`\``
41
+ case 'nextjs':
42
+ return `\`\`\`bash
43
+ npm install
44
+ npm run dev # development server (http://localhost:3000)
45
+ npm run build # production build
46
+ \`\`\``
47
+ case 'fastapi':
48
+ return `\`\`\`bash
49
+ pip install -r requirements.txt
50
+ uvicorn main:app --reload
51
+ \`\`\``
52
+ case 'express':
53
+ case 'node':
54
+ return `\`\`\`bash
55
+ npm install
56
+ npm start
57
+ \`\`\``
58
+ default:
59
+ return `\`\`\`bash
60
+ npm install
61
+ npm run dev
62
+ \`\`\``
63
+ }
64
+ }
65
+
66
+ function projectStructure(stack: StackInfo): string {
67
+ switch (stack.framework) {
68
+ case 'react':
69
+ return `\`\`\`
70
+ src/
71
+ components/ ← UI components (PascalCase)
72
+ hooks/ ← custom hooks (prefix: use*)
73
+ pages/ ← page-level components
74
+ utils/ ← shared helpers
75
+ \`\`\``
76
+ case 'nextjs':
77
+ return `\`\`\`
78
+ src/
79
+ app/ ← App Router pages and layouts
80
+ components/ ← shared UI components
81
+ lib/ ← server utilities, db clients
82
+ utils/ ← shared helpers
83
+ \`\`\``
84
+ case 'tauri':
85
+ return `\`\`\`
86
+ src/ ← frontend (${stack.hasTypeScript ? 'TypeScript' : 'JavaScript'})
87
+ components/
88
+ utils/
89
+ src-tauri/ ← Rust backend
90
+ src/
91
+ main.rs ← Tauri entry point
92
+ commands.rs ← Tauri commands (IPC)
93
+ tauri.conf.json
94
+ \`\`\``
95
+ case 'fastapi':
96
+ return `\`\`\`
97
+ app/
98
+ main.py ← FastAPI app entry point
99
+ routers/ ← API route groups
100
+ models/ ← Pydantic models
101
+ services/ ← business logic
102
+ dependencies/ ← FastAPI dependencies (DI)
103
+ tests/ ← pytest tests
104
+ \`\`\``
105
+ case 'express':
106
+ return `\`\`\`
107
+ src/
108
+ routes/ ← Express routers (one per domain)
109
+ controllers/ ← request handlers
110
+ services/ ← business logic
111
+ middleware/ ← Express middleware
112
+ utils/ ← shared helpers
113
+ \`\`\``
114
+ case 'node':
115
+ return `\`\`\`
116
+ src/
117
+ index.ts ← entry point
118
+ lib/ ← core library code
119
+ utils/ ← shared helpers
120
+ \`\`\``
121
+ default:
122
+ return `\`\`\`
123
+ src/ ← source files
124
+ \`\`\``
125
+ }
126
+ }
127
+
128
+ function stackTable(stack: StackInfo, blueprintConstraints: string): string {
129
+ const lang = stack.language === 'python' ? 'Python' : stack.hasTypeScript ? 'TypeScript' : 'JavaScript'
130
+ const rows: [string, string][] = [
131
+ ['Framework', stackLabel(stack)],
132
+ ['Language', lang],
133
+ ]
134
+ if (stack.extras.includes('prisma')) rows.push(['Database', 'Prisma ORM'])
135
+ if (stack.extras.includes('tailwind')) rows.push(['Styling', 'Tailwind CSS'])
136
+ if (stack.extras.includes('testing')) rows.push(['Testing', 'Vitest / Jest'])
137
+
138
+ const header = '| Technology | Details |\n|---|---|'
139
+ const rowLines = rows.map(([k, v]) => `| **${k}** | ${v} |`).join('\n')
140
+
141
+ let table = `${header}\n${rowLines}`
142
+
143
+ if (blueprintConstraints) {
144
+ table += `\n\n**Tech constraints (from blueprint)**:\n${blueprintConstraints}`
145
+ }
146
+
147
+ return table
148
+ }
149
+
150
+ export function generateReadme({ projectName, blueprintContent, stack }: ReadmeInput): string {
151
+ const goal = blueprintContent ? extractSection(blueprintContent, 'Goal') : ''
152
+ const features = blueprintContent ? extractSection(blueprintContent, 'Features') : ''
153
+ const techConstraints = blueprintContent
154
+ ? extractSection(blueprintContent, 'Tech constraints')
155
+ : ''
156
+
157
+ const tagline = goal
158
+ ? goal.split('\n')[0].replace(/^[-*>]\s*/, '')
159
+ : `A ${stackLabel(stack)} project.`
160
+
161
+ const overview = goal
162
+ ? goal
163
+ : `This project was scaffolded with [AgentKit](https://www.npmjs.com/package/@patricksardinha/agentkit-cli).`
164
+
165
+ const featuresSection = features
166
+ ? features
167
+ : '_Features will be described here._'
168
+
169
+ return `# ${projectName}
170
+
171
+ > ${tagline}
172
+
173
+ ## Overview
174
+
175
+ ${overview}
176
+
177
+ ## Tech Stack
178
+
179
+ ${stackTable(stack, techConstraints)}
180
+
181
+ ## Getting Started
182
+
183
+ ${gettingStarted(stack)}
184
+
185
+ ## Features
186
+
187
+ ${featuresSection}
188
+
189
+ ## Project Structure
190
+
191
+ ${projectStructure(stack)}
192
+
193
+ ## Built with AgentKit
194
+
195
+ This project was scaffolded with [AgentKit](https://www.npmjs.com/package/@patricksardinha/agentkit-cli) — a CLI that generates AI-native agent workflows for Claude Code.
196
+
197
+ Read \`PLAYBOOK.md\` to understand the agent workflow that built this project.
198
+ `
199
+ }
@@ -0,0 +1,200 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { generateReadme } from '../../src/generators/readmeGenerator.js'
3
+ import type { StackInfo } from '../../src/detectors/stackDetector.js'
4
+
5
+ function makeStack(
6
+ framework: StackInfo['framework'],
7
+ opts: Partial<Omit<StackInfo, 'framework'>> = {},
8
+ ): StackInfo {
9
+ return {
10
+ framework,
11
+ language: framework === 'fastapi' ? 'python' : opts.hasTypeScript ? 'typescript' : 'javascript',
12
+ hasTypeScript: opts.hasTypeScript ?? false,
13
+ extras: opts.extras ?? [],
14
+ ...opts,
15
+ }
16
+ }
17
+
18
+ const sampleBlueprint = `# My Awesome App
19
+
20
+ ## Goal
21
+ A desktop app for logging development sessions with local AI search.
22
+
23
+ ## Features
24
+ - Session logging with timestamps
25
+ - Weekly summary reports
26
+ - Natural language search via Ollama
27
+
28
+ ## Tech constraints
29
+ - Tauri v2, all local, no cloud
30
+ - Ollama for AI, no external API keys
31
+ `
32
+
33
+ // ─── Project name ─────────────────────────────────────────────────────────────
34
+
35
+ describe('generateReadme — project name', () => {
36
+ it('contains the project name in the title', () => {
37
+ const result = generateReadme({ projectName: 'my-project', stack: makeStack('react') })
38
+ expect(result).toContain('# my-project')
39
+ })
40
+
41
+ it('contains the project name when blueprint is present', () => {
42
+ const result = generateReadme({
43
+ projectName: 'devlog-desktop',
44
+ blueprintContent: sampleBlueprint,
45
+ stack: makeStack('tauri', { hasTypeScript: true }),
46
+ })
47
+ expect(result).toContain('# devlog-desktop')
48
+ })
49
+ })
50
+
51
+ // ─── Built with AgentKit ──────────────────────────────────────────────────────
52
+
53
+ describe('generateReadme — Built with AgentKit section', () => {
54
+ it('always contains the Built with AgentKit section', () => {
55
+ for (const framework of ['react', 'nextjs', 'tauri', 'fastapi', 'express', 'node', 'unknown'] as StackInfo['framework'][]) {
56
+ const result = generateReadme({ projectName: 'test', stack: makeStack(framework) })
57
+ expect(result).toContain('Built with AgentKit')
58
+ }
59
+ })
60
+
61
+ it('contains the npmjs link', () => {
62
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
63
+ expect(result).toContain('https://www.npmjs.com/package/@patricksardinha/agentkit-cli')
64
+ })
65
+
66
+ it('contains the PLAYBOOK.md one-liner', () => {
67
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
68
+ expect(result).toContain('PLAYBOOK.md')
69
+ })
70
+ })
71
+
72
+ // ─── Getting Started — commands per stack ─────────────────────────────────────
73
+
74
+ describe('generateReadme — Getting Started commands', () => {
75
+ it('Tauri: contains tauri:dev and tauri:build', () => {
76
+ const result = generateReadme({ projectName: 'test', stack: makeStack('tauri', { hasTypeScript: true }) })
77
+ expect(result).toContain('npm run tauri:dev')
78
+ expect(result).toContain('npm run tauri:build')
79
+ })
80
+
81
+ it('React: contains npm run dev and npm run build', () => {
82
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
83
+ expect(result).toContain('npm run dev')
84
+ expect(result).toContain('npm run build')
85
+ expect(result).not.toContain('tauri')
86
+ })
87
+
88
+ it('Next.js: contains npm run dev and npm run build', () => {
89
+ const result = generateReadme({ projectName: 'test', stack: makeStack('nextjs') })
90
+ expect(result).toContain('npm run dev')
91
+ expect(result).toContain('npm run build')
92
+ })
93
+
94
+ it('FastAPI: contains pip install and uvicorn', () => {
95
+ const result = generateReadme({ projectName: 'test', stack: makeStack('fastapi') })
96
+ expect(result).toContain('pip install -r requirements.txt')
97
+ expect(result).toContain('uvicorn main:app --reload')
98
+ })
99
+
100
+ it('Express: contains npm install and npm start', () => {
101
+ const result = generateReadme({ projectName: 'test', stack: makeStack('express') })
102
+ expect(result).toContain('npm install')
103
+ expect(result).toContain('npm start')
104
+ })
105
+
106
+ it('Node.js: contains npm install and npm start', () => {
107
+ const result = generateReadme({ projectName: 'test', stack: makeStack('node') })
108
+ expect(result).toContain('npm install')
109
+ expect(result).toContain('npm start')
110
+ })
111
+
112
+ it('Unknown: contains npm install and npm run dev', () => {
113
+ const result = generateReadme({ projectName: 'test', stack: makeStack('unknown') })
114
+ expect(result).toContain('npm install')
115
+ expect(result).toContain('npm run dev')
116
+ })
117
+ })
118
+
119
+ // ─── Blueprint: Goal extraction ───────────────────────────────────────────────
120
+
121
+ describe('generateReadme — Goal extraction from blueprint', () => {
122
+ it('uses the Goal section as the tagline when blueprint is present', () => {
123
+ const result = generateReadme({
124
+ projectName: 'devlog',
125
+ blueprintContent: sampleBlueprint,
126
+ stack: makeStack('tauri', { hasTypeScript: true }),
127
+ })
128
+ expect(result).toContain('A desktop app for logging development sessions')
129
+ })
130
+
131
+ it('uses a generic tagline when no blueprint is provided', () => {
132
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
133
+ expect(result).toContain('React + Vite')
134
+ })
135
+
136
+ it('includes Goal content in the Overview section', () => {
137
+ const result = generateReadme({
138
+ projectName: 'devlog',
139
+ blueprintContent: sampleBlueprint,
140
+ stack: makeStack('tauri', { hasTypeScript: true }),
141
+ })
142
+ expect(result).toContain('## Overview')
143
+ expect(result).toContain('A desktop app for logging development sessions')
144
+ })
145
+ })
146
+
147
+ // ─── Blueprint: Features extraction ──────────────────────────────────────────
148
+
149
+ describe('generateReadme — Features extraction from blueprint', () => {
150
+ it('extracts Features section from blueprint', () => {
151
+ const result = generateReadme({
152
+ projectName: 'devlog',
153
+ blueprintContent: sampleBlueprint,
154
+ stack: makeStack('tauri', { hasTypeScript: true }),
155
+ })
156
+ expect(result).toContain('## Features')
157
+ expect(result).toContain('Session logging with timestamps')
158
+ expect(result).toContain('Weekly summary reports')
159
+ expect(result).toContain('Natural language search via Ollama')
160
+ })
161
+
162
+ it('uses placeholder when no blueprint is provided', () => {
163
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
164
+ expect(result).toContain('## Features')
165
+ expect(result).toContain('Features will be described here')
166
+ })
167
+
168
+ it('includes tech constraints from blueprint in the Tech Stack section', () => {
169
+ const result = generateReadme({
170
+ projectName: 'devlog',
171
+ blueprintContent: sampleBlueprint,
172
+ stack: makeStack('tauri', { hasTypeScript: true }),
173
+ })
174
+ expect(result).toContain('Tauri v2, all local, no cloud')
175
+ })
176
+ })
177
+
178
+ // ─── Project structure ────────────────────────────────────────────────────────
179
+
180
+ describe('generateReadme — Project Structure section', () => {
181
+ it('always contains the Project Structure section', () => {
182
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
183
+ expect(result).toContain('## Project Structure')
184
+ })
185
+
186
+ it('React structure contains src/components/', () => {
187
+ const result = generateReadme({ projectName: 'test', stack: makeStack('react') })
188
+ expect(result).toContain('components/')
189
+ })
190
+
191
+ it('Tauri structure mentions src-tauri/', () => {
192
+ const result = generateReadme({ projectName: 'test', stack: makeStack('tauri', { hasTypeScript: true }) })
193
+ expect(result).toContain('src-tauri/')
194
+ })
195
+
196
+ it('FastAPI structure mentions app/routers/', () => {
197
+ const result = generateReadme({ projectName: 'test', stack: makeStack('fastapi') })
198
+ expect(result).toContain('routers/')
199
+ })
200
+ })