@patricksardinha/agentkit-cli 0.1.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.
Files changed (52) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/workflows/release.yml +31 -0
  3. package/AGENT_WORKFLOW.md +55 -0
  4. package/CLAUDE.md +45 -0
  5. package/README.md +327 -0
  6. package/dist/cli.cjs +1079 -0
  7. package/dist/cli.cjs.map +1 -0
  8. package/dist/cli.d.cts +1 -0
  9. package/dist/cli.d.ts +1 -0
  10. package/dist/cli.js +1056 -0
  11. package/dist/cli.js.map +1 -0
  12. package/package.json +45 -0
  13. package/src/cli.ts +18 -0
  14. package/src/commands/add.ts +166 -0
  15. package/src/commands/init.ts +130 -0
  16. package/src/commands/status.ts +57 -0
  17. package/src/detectors/gitDetector.ts +11 -0
  18. package/src/detectors/stackDetector.ts +88 -0
  19. package/src/generators/claudeMdGenerator.ts +42 -0
  20. package/src/generators/playbookGenerator.ts +76 -0
  21. package/src/generators/skillsGenerator.ts +56 -0
  22. package/src/generators/workflowGenerator.ts +62 -0
  23. package/src/templates/express.ts +64 -0
  24. package/src/templates/fastapi.ts +63 -0
  25. package/src/templates/nextjs.ts +63 -0
  26. package/src/templates/node.ts +54 -0
  27. package/src/templates/react.ts +61 -0
  28. package/src/templates/tauri.ts +65 -0
  29. package/src/templates/unknown.ts +45 -0
  30. package/src/types/agent.ts +9 -0
  31. package/src/types/inquirer.d.ts +36 -0
  32. package/src/utils/agentParser.ts +67 -0
  33. package/src/utils/blueprintParser.ts +28 -0
  34. package/src/utils/logger.ts +16 -0
  35. package/tests/commands/add.test.ts +130 -0
  36. package/tests/detectors/fixtures/express-app/package.json +9 -0
  37. package/tests/detectors/fixtures/fastapi-app/requirements.txt +3 -0
  38. package/tests/detectors/fixtures/nextjs-app/package.json +13 -0
  39. package/tests/detectors/fixtures/no-git/README.md +2 -0
  40. package/tests/detectors/fixtures/react-app/package.json +10 -0
  41. package/tests/detectors/fixtures/tauri-app/package.json +10 -0
  42. package/tests/detectors/fixtures/tauri-app/src-tauri/tauri.conf.json +12 -0
  43. package/tests/detectors/gitDetector.test.ts +29 -0
  44. package/tests/detectors/stackDetector.test.ts +50 -0
  45. package/tests/generators/blueprintSupport.test.ts +130 -0
  46. package/tests/generators/claudeMdGenerator.test.ts +86 -0
  47. package/tests/generators/playbookGenerator.test.ts +152 -0
  48. package/tests/generators/skillsGenerator.test.ts +94 -0
  49. package/tests/generators/workflowGenerator.test.ts +84 -0
  50. package/tsconfig.json +19 -0
  51. package/tsup.config.ts +11 -0
  52. package/vitest.config.ts +9 -0
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { generatePlaybook } from '../../src/generators/playbookGenerator.js'
3
+ import { extractAgentsFromWorkflow } from '../../src/utils/agentParser.js'
4
+ import { toSlug } from '../../src/utils/agentParser.js'
5
+ import type { Agent } from '../../src/types/agent.js'
6
+
7
+ const sampleAgents: Agent[] = [
8
+ {
9
+ number: 1,
10
+ name: 'Infra & Setup',
11
+ fullName: 'Agent 1 · Infra & Setup',
12
+ slug: 'infra-setup',
13
+ scope: 'scaffolding du projet',
14
+ outputs: ['package.json', 'tsconfig.json'],
15
+ criterion: 'npm run build',
16
+ },
17
+ {
18
+ number: 2,
19
+ name: 'Components',
20
+ fullName: 'Agent 2 · Components',
21
+ slug: 'components',
22
+ scope: 'composants UI réutilisables',
23
+ outputs: ['src/components/'],
24
+ criterion: 'npm test',
25
+ },
26
+ ]
27
+
28
+ describe('generatePlaybook', () => {
29
+ it('returns a non-empty string', () => {
30
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
31
+ expect(typeof result).toBe('string')
32
+ expect(result.length).toBeGreaterThan(0)
33
+ })
34
+
35
+ it('contains the project name', () => {
36
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
37
+ expect(result).toContain('my-app')
38
+ })
39
+
40
+ it('starts with the unique instruction', () => {
41
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
42
+ expect(result).toContain("Donne cette instruction à Claude Code")
43
+ expect(result).toContain("Lis PLAYBOOK.md et exécute la procédure")
44
+ })
45
+
46
+ it('contains global execution rules', () => {
47
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
48
+ expect(result).toContain('Règles d\'exécution globales')
49
+ expect(result).toContain('3 tentatives')
50
+ expect(result).toContain('validation humaine')
51
+ expect(result).toContain('Ne jamais passer')
52
+ })
53
+
54
+ it('contains a block for each agent', () => {
55
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
56
+ for (const agent of sampleAgents) {
57
+ expect(result).toContain(agent.fullName)
58
+ expect(result).toContain(agent.scope)
59
+ expect(result).toContain(`agents/agent-${agent.number}-${agent.slug}/skills.md`)
60
+ }
61
+ })
62
+
63
+ it('each agent block contains outputs and criterion', () => {
64
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
65
+ expect(result).toContain('package.json')
66
+ expect(result).toContain('npm run build')
67
+ expect(result).toContain('src/components/')
68
+ expect(result).toContain('npm test')
69
+ })
70
+
71
+ it('contains the "Itérations futures" section', () => {
72
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
73
+ expect(result).toContain('Itérations futures')
74
+ expect(result).toContain('agentkit add --feature')
75
+ })
76
+
77
+ it('contains the human validation section', () => {
78
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
79
+ expect(result).toContain('Validation humaine requise')
80
+ expect(result).toContain('3 échecs consécutifs')
81
+ expect(result).toContain('Dépendance externe manquante')
82
+ expect(result).toContain('Conflit')
83
+ })
84
+
85
+ it('handles an empty agents list gracefully', () => {
86
+ const result = generatePlaybook({ agents: [], projectName: 'empty-project' })
87
+ expect(typeof result).toBe('string')
88
+ expect(result).toContain('empty-project')
89
+ })
90
+ })
91
+
92
+ describe('toSlug', () => {
93
+ it('converts name to lowercase hyphenated slug', () => {
94
+ expect(toSlug('Infra & Setup')).toBe('infra-setup')
95
+ expect(toSlug('Components')).toBe('components')
96
+ expect(toSlug('State & Hooks')).toBe('state-hooks')
97
+ expect(toSlug('Pages & Routing')).toBe('pages-routing')
98
+ expect(toSlug('Tests & CI')).toBe('tests-ci')
99
+ })
100
+
101
+ it('handles extra spaces and special chars', () => {
102
+ expect(toSlug('Data Layer')).toBe('data-layer')
103
+ expect(toSlug('API · Routes')).toBe('api-routes')
104
+ })
105
+ })
106
+
107
+ describe('extractAgentsFromWorkflow', () => {
108
+ it('extracts agents from a workflow string matching template format', () => {
109
+ const workflow = `# Agent Workflow — React Project
110
+
111
+ ## Stack détectée
112
+ Framework: React
113
+
114
+ ## Agents
115
+
116
+ ### Agent 1 · Components
117
+ Périmètre : composants UI réutilisables
118
+ Produit : src/components/
119
+ Critère : composants documentés et testés
120
+
121
+ ### Agent 2 · State & Hooks
122
+ Périmètre : state management, hooks personnalisés
123
+ Produit : src/hooks/
124
+ Critère : hooks testés unitairement
125
+ `
126
+ const agents = extractAgentsFromWorkflow(workflow)
127
+ expect(agents).toHaveLength(2)
128
+ expect(agents[0].number).toBe(1)
129
+ expect(agents[0].name).toBe('Components')
130
+ expect(agents[0].fullName).toBe('Agent 1 · Components')
131
+ expect(agents[0].slug).toBe('components')
132
+ expect(agents[0].scope).toBe('composants UI réutilisables')
133
+ expect(agents[0].criterion).toBe('composants documentés et testés')
134
+ expect(agents[0].outputs).toEqual(['src/components/'])
135
+ })
136
+
137
+ it('handles multi-line outputs', () => {
138
+ const workflow = `### Agent 1 · Setup
139
+ Périmètre : configuration initiale
140
+ Produit :
141
+ - package.json
142
+ - tsconfig.json
143
+ Critère : npm run build
144
+ `
145
+ const agents = extractAgentsFromWorkflow(workflow)
146
+ expect(agents[0].outputs).toEqual(['package.json', 'tsconfig.json'])
147
+ })
148
+
149
+ it('returns empty array for content with no agent blocks', () => {
150
+ expect(extractAgentsFromWorkflow('# Just a title\nNo agents here.')).toEqual([])
151
+ })
152
+ })
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { generateSkills } from '../../src/generators/skillsGenerator.js'
3
+ import type { Agent } from '../../src/types/agent.js'
4
+ import { readFile, rm, mkdtemp } from 'node:fs/promises'
5
+ import { join } from 'node:path'
6
+ import { tmpdir } from 'node:os'
7
+
8
+ const sampleAgents: Agent[] = [
9
+ {
10
+ number: 1,
11
+ name: 'Infra & Setup',
12
+ fullName: 'Agent 1 · Infra & Setup',
13
+ slug: 'infra-setup',
14
+ scope: 'scaffolding du projet',
15
+ outputs: ['package.json', 'tsconfig.json'],
16
+ criterion: 'npm run build',
17
+ },
18
+ {
19
+ number: 2,
20
+ name: 'Components',
21
+ fullName: 'Agent 2 · Components',
22
+ slug: 'components',
23
+ scope: 'composants UI réutilisables',
24
+ outputs: ['src/components/'],
25
+ criterion: 'npm test',
26
+ },
27
+ ]
28
+
29
+ let tempDir = ''
30
+
31
+ afterEach(async () => {
32
+ if (tempDir) {
33
+ await rm(tempDir, { recursive: true, force: true })
34
+ tempDir = ''
35
+ }
36
+ })
37
+
38
+ describe('generateSkills', () => {
39
+ it('creates an agents/ directory with one folder per agent', async () => {
40
+ tempDir = await mkdtemp(join(tmpdir(), 'agentkit-test-'))
41
+ await generateSkills(sampleAgents, tempDir)
42
+
43
+ for (const agent of sampleAgents) {
44
+ const dirPath = join(tempDir, 'agents', `agent-${agent.number}-${agent.slug}`)
45
+ // readFile would throw if dir doesn't exist — use access via readFile on a known file
46
+ const skills = await readFile(join(dirPath, 'skills.md'), 'utf-8')
47
+ expect(skills).toBeTruthy()
48
+ }
49
+ })
50
+
51
+ it('skills.md contains the agent fullName', async () => {
52
+ tempDir = await mkdtemp(join(tmpdir(), 'agentkit-test-'))
53
+ await generateSkills(sampleAgents, tempDir)
54
+
55
+ const skills1 = await readFile(
56
+ join(tempDir, 'agents', 'agent-1-infra-setup', 'skills.md'),
57
+ 'utf-8',
58
+ )
59
+ expect(skills1).toContain('Agent 1 · Infra & Setup')
60
+ expect(skills1).toContain('## Contexte technique')
61
+ expect(skills1).toContain('## Documentation de référence')
62
+ expect(skills1).toContain('## Conventions spécifiques')
63
+ })
64
+
65
+ it('context.md contains scope and criterion', async () => {
66
+ tempDir = await mkdtemp(join(tmpdir(), 'agentkit-test-'))
67
+ await generateSkills(sampleAgents, tempDir)
68
+
69
+ const ctx = await readFile(
70
+ join(tempDir, 'agents', 'agent-1-infra-setup', 'context.md'),
71
+ 'utf-8',
72
+ )
73
+ expect(ctx).toContain('Agent 1 · Infra & Setup')
74
+ expect(ctx).toContain('scaffolding du projet')
75
+ expect(ctx).toContain('npm run build')
76
+ expect(ctx).toContain('package.json')
77
+ })
78
+
79
+ it('generates correct folder name from agent slug', async () => {
80
+ tempDir = await mkdtemp(join(tmpdir(), 'agentkit-test-'))
81
+ await generateSkills(sampleAgents, tempDir)
82
+
83
+ const skills2 = await readFile(
84
+ join(tempDir, 'agents', 'agent-2-components', 'skills.md'),
85
+ 'utf-8',
86
+ )
87
+ expect(skills2).toContain('Agent 2 · Components')
88
+ })
89
+
90
+ it('handles empty agents list without error', async () => {
91
+ tempDir = await mkdtemp(join(tmpdir(), 'agentkit-test-'))
92
+ await expect(generateSkills([], tempDir)).resolves.toBeUndefined()
93
+ })
94
+ })
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { generateWorkflow } from '../../src/generators/workflowGenerator.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
+ describe('generateWorkflow', () => {
19
+ it('returns a non-empty string for every supported framework', () => {
20
+ const frameworks: StackInfo['framework'][] = [
21
+ 'react', 'nextjs', 'tauri', 'fastapi', 'express', 'node', 'unknown',
22
+ ]
23
+ for (const framework of frameworks) {
24
+ const result = generateWorkflow(makeStack(framework))
25
+ expect(typeof result).toBe('string')
26
+ expect(result.length).toBeGreaterThan(0)
27
+ }
28
+ })
29
+
30
+ it('all outputs start with a markdown heading', () => {
31
+ const frameworks: StackInfo['framework'][] = [
32
+ 'react', 'nextjs', 'tauri', 'fastapi', 'express', 'node', 'unknown',
33
+ ]
34
+ for (const framework of frameworks) {
35
+ const result = generateWorkflow(makeStack(framework))
36
+ expect(result.trimStart()).toMatch(/^#/)
37
+ }
38
+ })
39
+
40
+ it('React — contains Agent sections', () => {
41
+ const result = generateWorkflow(makeStack('react'))
42
+ expect(result).toContain('Agent')
43
+ expect(result).toContain('React')
44
+ })
45
+
46
+ it('React — reflects TypeScript language', () => {
47
+ const result = generateWorkflow(makeStack('react', { hasTypeScript: true }))
48
+ expect(result).toContain('TypeScript')
49
+ })
50
+
51
+ it('Next.js — mentions Next.js and Prisma when extra present', () => {
52
+ const result = generateWorkflow(makeStack('nextjs', { extras: ['prisma'] }))
53
+ expect(result).toContain('Next.js')
54
+ expect(result).toContain('Prisma')
55
+ })
56
+
57
+ it('Tauri — mentions Rust and Cargo', () => {
58
+ const result = generateWorkflow(makeStack('tauri'))
59
+ expect(result).toContain('Tauri')
60
+ expect(result).toContain('Rust')
61
+ })
62
+
63
+ it('FastAPI — mentions FastAPI and Python', () => {
64
+ const result = generateWorkflow(makeStack('fastapi'))
65
+ expect(result).toContain('FastAPI')
66
+ expect(result).toContain('Python')
67
+ })
68
+
69
+ it('Express — mentions Express', () => {
70
+ const result = generateWorkflow(makeStack('express'))
71
+ expect(result).toContain('Express')
72
+ })
73
+
74
+ it('Node — mentions Node.js', () => {
75
+ const result = generateWorkflow(makeStack('node'))
76
+ expect(result).toContain('Node.js')
77
+ })
78
+
79
+ it('unknown — returns a generic fallback', () => {
80
+ const result = generateWorkflow(makeStack('unknown'))
81
+ expect(typeof result).toBe('string')
82
+ expect(result.length).toBeGreaterThan(0)
83
+ })
84
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": ["ES2022"],
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "tests"]
19
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/cli.ts'],
5
+ format: ['esm', 'cjs'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ splitting: false,
10
+ shims: true,
11
+ })
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['tests/**/*.test.ts'],
8
+ },
9
+ })