@patricksardinha/agentkit-cli 0.1.0 → 0.3.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.
@@ -3,46 +3,146 @@ import type { Agent } from '../types/agent.js'
3
3
  export interface PlaybookInput {
4
4
  agents: Agent[]
5
5
  projectName: string
6
+ hasBlueprint: boolean
6
7
  }
7
8
 
8
- export function generatePlaybook({ agents, projectName }: PlaybookInput): string {
9
+ export function generatePlaybook({ agents, projectName, hasBlueprint }: PlaybookInput): string {
9
10
  const agentBlocks = agents.map((a) => agentBlock(a)).join('\n---\n\n')
10
11
 
12
+ const phase0 = hasBlueprint
13
+ ? `## Phase 0 — Agent Decomposition (run this first)
14
+
15
+ > A \`PROJECT_BLUEPRINT.md\` was provided.
16
+ > Claude Code reads it and decomposes the project into specialized agents
17
+ > before writing a single line of code.
18
+
19
+ **Read these files in order:**
20
+ 1. \`CLAUDE.md\`
21
+ 2. \`PROJECT_BLUEPRINT.md\`
22
+
23
+ **Then decompose the project into agents** following these rules:
24
+
25
+ - One agent = one coherent technical layer (never mix two layers)
26
+ - Each agent must have a runnable success criterion (\`npm test\`, \`cargo build\`…)
27
+ - Agents must be ordered by dependency (no feature without infra first)
28
+ - Maximum 6 agents — if you have more, group related ones
29
+ - Always respect this order:
30
+ 1. Infra & Configuration
31
+ 2. Data layer (DB schema, models, services)
32
+ 3. External integrations (auth, APIs, local services like Ollama)
33
+ 4. UI & pages
34
+ 5. Advanced features (RAG, export, realtime…)
35
+ 6. Build & release (CI/CD, packaging, installers)
36
+
37
+ **Write the result directly into \`AGENT_WORKFLOW.md\`** — replace its current
38
+ content with your decomposition.
39
+
40
+ **Then ask for human validation:**
41
+ > "I have decomposed the project into N agents: [list them].
42
+ > Should I proceed with execution?"
43
+
44
+ Wait for confirmation before moving to Phase 1.
45
+
46
+ ---
47
+
48
+ `
49
+ : `## Phase 0 — Project Discovery (run this first)
50
+
51
+ > No \`PROJECT_BLUEPRINT.md\` was provided.
52
+ > Before writing any code, Claude Code asks the user what they want to build,
53
+ > then decomposes the project into agents — exactly as if a blueprint had been provided.
54
+
55
+ **Ask the user these questions and wait for their answers:**
56
+
57
+ 1. What is this project? (one sentence describing the goal)
58
+ 2. What are the main features you want to build? (list them)
59
+ 3. Are there any tech constraints or architecture preferences?
60
+ (e.g. offline-only, specific DB, no auth, specific framework)
61
+
62
+ **Once you have the answers, decompose the project into agents**
63
+ following these rules:
64
+
65
+ - One agent = one coherent technical layer (never mix two layers)
66
+ - Each agent must have a runnable success criterion (\`npm test\`, \`cargo build\`…)
67
+ - Agents must be ordered by dependency (no feature without infra first)
68
+ - Maximum 6 agents — if you have more, group related ones
69
+ - Always respect this order:
70
+ 1. Infra & Configuration
71
+ 2. Data layer (DB schema, models, services)
72
+ 3. External integrations (auth, APIs, local services like Ollama)
73
+ 4. UI & pages
74
+ 5. Advanced features (RAG, export, realtime…)
75
+ 6. Build & release (CI/CD, packaging, installers)
76
+
77
+ **Write the result directly into \`AGENT_WORKFLOW.md\`** — replace its current
78
+ content with your decomposition.
79
+
80
+ **Then ask for human validation:**
81
+ > "I have decomposed the project into N agents: [list them].
82
+ > Should I proceed with execution?"
83
+
84
+ Wait for confirmation before moving to Phase 1.
85
+
86
+ ---
87
+
88
+ `
89
+
11
90
  return `# PLAYBOOK.md — ${projectName}
12
91
 
13
- > Donne cette instruction à Claude Code : 'Lis PLAYBOOK.md et exécute la procédure.'
92
+ > **One instruction to give Claude Code:**
93
+ > "Read PLAYBOOK.md and execute the procedure."
94
+ >
95
+ > Claude Code handles the rest autonomously — project discovery or blueprint reading,
96
+ > agent decomposition, execution, success validation, retries, and human escalation.
97
+ > No API key required. No additional cost beyond your LLM subscription.
14
98
 
15
- ## Règles d'exécution globales
99
+ ---
16
100
 
17
- Avant chaque agent :
18
- 1. Lire \`CLAUDE.md\`
19
- 2. Lire \`agents/agent-{N}-{slug}/skills.md\` (le fichier de l'agent courant)
101
+ ## Global Execution Rules
20
102
 
21
- Après chaque agent :
22
- - Exécuter le critère de succès
23
- - Si succès → annoncer "✅ Agent N terminé" et passer au suivant
24
- - Si échec analyser la cause racine, corriger, réexécuter (max 3 tentatives)
25
- - Après 3 échecs consécutifs → pause et demander validation humaine
26
- - **Ne jamais passer à l'agent suivant sans critère validé**
103
+ Before each agent:
104
+ 1. Read \`CLAUDE.md\`
105
+ 2. Read \`agents/agent-{N}-{slug}/skills.md\` (current agent's file)
106
+ 3. Read the agent's section in \`AGENT_WORKFLOW.md\`
27
107
 
28
- ## Agents
108
+ After each agent:
109
+ - Run the success criterion command
110
+ - ✅ Passes → announce "✅ Agent N complete" and move to the next
111
+ - ❌ Fails → analyze the root cause, fix, rerun (max 3 attempts)
112
+ - After 3 consecutive failures → stop and ask for human validation
113
+
114
+ **Never move to the next agent without a passing success criterion.**
115
+ **Stay strictly within your current agent's defined scope.**
116
+
117
+ ---
118
+
119
+ ${phase0}## Phase 1 — Execution
29
120
 
30
121
  ${agentBlocks}
31
122
 
32
- ## Itérations futures
123
+ ---
124
+
125
+ ## Future Iterations
33
126
 
34
- Lorsqu'un nouvel agent est ajouté via \`agentkit add --feature <description>\` :
35
- 1. Un nouveau bloc agent est ajouté à la fin de \`AGENT_WORKFLOW.md\`
36
- 2. Le dossier \`agents/agent-{N}-{slug}/\` est créé avec \`skills.md\` et \`context.md\`
37
- 3. Ce \`PLAYBOOK.md\` est régénéré automatiquement pour inclure le nouvel agent
38
- 4. L'exécution reprend à ce nouvel agent uniquementles agents précédents ne sont pas réexécutés
127
+ When a new agent is added via \`agentkit add --feature <description>\`:
128
+ 1. A new agent block is appended to \`AGENT_WORKFLOW.md\`
129
+ 2. The folder \`agents/agent-{N}-{slug}/\` is created with \`skills.md\`
130
+ 3. This \`PLAYBOOK.md\` is regenerated to include the new agent
131
+ 4. Execution resumes at the new agent onlycompleted agents are not rerun
39
132
 
40
- ## Validation humaine requise
133
+ When you receive the instruction to continue after an iteration:
134
+ > "Read PLAYBOOK.md and execute only the agents that haven't been completed yet."
41
135
 
42
- Les cas suivants nécessitent une pause et une confirmation humaine avant de continuer :
43
- - **3 échecs consécutifs** sur le critère de succès d'un agent
44
- - **Dépendance externe manquante** : clé API, variable d'environnement non définie, service tiers inaccessible
45
- - **Conflit** entre le blueprint fourni (\`--blueprint\`) et la stack détectée automatiquement
136
+ ---
137
+
138
+ ## Human Validation Required
139
+
140
+ Stop and wait for confirmation in these situations:
141
+ - **3 consecutive failures** on the same success criterion
142
+ - **Missing external dependency**: API key, env variable, unavailable service
143
+ - **Conflict** between the detected stack and the user's stated constraints
144
+ - **Destructive operation**: overwriting files not listed in deliverables
145
+ - **End of Phase 0**: agent decomposition must be validated before execution
46
146
  `
47
147
  }
48
148
 
@@ -51,26 +151,26 @@ function agentBlock(agent: Agent): string {
51
151
  const outputLines =
52
152
  agent.outputs.length > 0
53
153
  ? agent.outputs.map((o) => `- ${o}`).join('\n')
54
- : '- (voir skills.md pour le détail)'
154
+ : '- (see skills.md for details)'
55
155
 
56
- return `### ${agent.fullName}
156
+ return `### Agent ${agent.number} · ${agent.name}
57
157
 
58
- **Périmètre** : ${agent.scope}
158
+ **Scope**: ${agent.scope}
59
159
 
60
- **Skills** : \`${skillsPath}\`
160
+ **Skills**: \`${skillsPath}\`
61
161
 
62
- **Fichiers produits** :
162
+ **Deliverables**:
63
163
  ${outputLines}
64
164
 
65
- **Critère de succès** :
165
+ **Success criterion**:
66
166
  \`\`\`bash
67
167
  ${agent.criterion || 'npm run build && npm test'}
68
168
  \`\`\`
69
169
 
70
- **En cas d'échec** :
71
- 1. Analyser le message d'erreur complet
72
- 2. Corriger la cause racine (pas les symptômes)
73
- 3. Réexécuter le critère (max 3 tentatives)
74
- 4. Après 3 échecsdemander validation humaine
170
+ **On failure**:
171
+ 1. Read the full error output
172
+ 2. Fix the root cause not the symptoms
173
+ 3. Rerun the success criterion (max 3 attempts)
174
+ 4. After 3 failuresask for human validation
75
175
  `
76
- }
176
+ }
@@ -116,7 +116,7 @@ describe('addFeatureToProject', () => {
116
116
  it('throws an error when AGENT_WORKFLOW.md is missing', async () => {
117
117
  tempDir = await mkdtemp(join(tmpdir(), 'agentkit-add-test-'))
118
118
  await expect(addFeatureToProject('Add feature', tempDir)).rejects.toThrow(
119
- 'AGENT_WORKFLOW.md introuvable',
119
+ 'AGENT_WORKFLOW.md not found in',
120
120
  )
121
121
  })
122
122
 
@@ -10,7 +10,7 @@ const sampleAgents: Agent[] = [
10
10
  name: 'Infra & Setup',
11
11
  fullName: 'Agent 1 · Infra & Setup',
12
12
  slug: 'infra-setup',
13
- scope: 'scaffolding du projet',
13
+ scope: 'project scaffolding',
14
14
  outputs: ['package.json', 'tsconfig.json'],
15
15
  criterion: 'npm run build',
16
16
  },
@@ -19,76 +19,166 @@ const sampleAgents: Agent[] = [
19
19
  name: 'Components',
20
20
  fullName: 'Agent 2 · Components',
21
21
  slug: 'components',
22
- scope: 'composants UI réutilisables',
22
+ scope: 'reusable UI components',
23
23
  outputs: ['src/components/'],
24
24
  criterion: 'npm test',
25
25
  },
26
26
  ]
27
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)
28
+ // ─── Common behaviour (both modes) ────────────────────────────────────────────
29
+
30
+ describe('generatePlaybook (common)', () => {
31
+ for (const hasBlueprint of [true, false]) {
32
+ const label = hasBlueprint ? 'hasBlueprint: true' : 'hasBlueprint: false'
33
+
34
+ it(`[${label}] returns a non-empty string`, () => {
35
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
36
+ expect(typeof result).toBe('string')
37
+ expect(result.length).toBeGreaterThan(0)
38
+ })
39
+
40
+ it(`[${label}] contains the project name`, () => {
41
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
42
+ expect(result).toContain('my-app')
43
+ })
44
+
45
+ it(`[${label}] contains the one-instruction block`, () => {
46
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
47
+ expect(result).toContain('Read PLAYBOOK.md and execute the procedure')
48
+ })
49
+
50
+ it(`[${label}] contains global execution rules`, () => {
51
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
52
+ expect(result).toContain('Global Execution Rules')
53
+ expect(result).toContain('max 3 attempts')
54
+ expect(result).toContain('human validation')
55
+ expect(result).toContain('Never move to the next agent')
56
+ })
57
+
58
+ it(`[${label}] always contains Phase 0`, () => {
59
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
60
+ expect(result).toContain('Phase 0')
61
+ })
62
+
63
+ it(`[${label}] always contains Phase 1`, () => {
64
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
65
+ expect(result).toContain('Phase 1')
66
+ })
67
+
68
+ it(`[${label}] Phase 0 comes before Phase 1`, () => {
69
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
70
+ expect(result.indexOf('Phase 0')).toBeLessThan(result.indexOf('Phase 1'))
71
+ })
72
+
73
+ it(`[${label}] contains a block for each agent`, () => {
74
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
75
+ for (const agent of sampleAgents) {
76
+ expect(result).toContain(agent.fullName)
77
+ expect(result).toContain(agent.scope)
78
+ expect(result).toContain(`agents/agent-${agent.number}-${agent.slug}/skills.md`)
79
+ }
80
+ })
81
+
82
+ it(`[${label}] each agent block contains outputs and criterion`, () => {
83
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
84
+ expect(result).toContain('package.json')
85
+ expect(result).toContain('npm run build')
86
+ expect(result).toContain('src/components/')
87
+ expect(result).toContain('npm test')
88
+ })
89
+
90
+ it(`[${label}] contains the future iterations section`, () => {
91
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
92
+ expect(result).toContain('Future Iterations')
93
+ expect(result).toContain('agentkit add --feature')
94
+ })
95
+
96
+ it(`[${label}] contains the human validation section`, () => {
97
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint })
98
+ expect(result).toContain('Human Validation Required')
99
+ expect(result).toContain('3 consecutive failures')
100
+ expect(result).toContain('Missing external dependency')
101
+ expect(result).toContain('End of Phase 0')
102
+ })
103
+
104
+ it(`[${label}] handles an empty agents list gracefully`, () => {
105
+ const result = generatePlaybook({ agents: [], projectName: 'empty-project', hasBlueprint })
106
+ expect(typeof result).toBe('string')
107
+ expect(result).toContain('empty-project')
108
+ })
109
+ }
110
+ })
111
+
112
+ // ─── Phase 0 with blueprint (Decomposition mode) ──────────────────────────────
113
+
114
+ describe('generatePlaybook Phase 0 — Decomposition (hasBlueprint: true)', () => {
115
+ it('contains the decomposition title', () => {
116
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: true })
117
+ expect(result).toContain('Agent Decomposition')
118
+ })
119
+
120
+ it('references PROJECT_BLUEPRINT.md', () => {
121
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: true })
122
+ expect(result).toContain('PROJECT_BLUEPRINT.md')
123
+ })
124
+
125
+ it('contains decomposition rules', () => {
126
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: true })
127
+ expect(result).toContain('Maximum 6 agents')
128
+ expect(result).toContain('AGENT_WORKFLOW.md')
33
129
  })
34
130
 
35
- it('contains the project name', () => {
36
- const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app' })
37
- expect(result).toContain('my-app')
131
+ it('contains the validation gate', () => {
132
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: true })
133
+ expect(result).toContain('Should I proceed')
38
134
  })
39
135
 
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")
136
+ it('does NOT contain Project Discovery language', () => {
137
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: true })
138
+ expect(result).not.toContain('Project Discovery')
139
+ expect(result).not.toContain('Ask the user these questions')
44
140
  })
141
+ })
142
+
143
+ // ─── Phase 0 without blueprint (Discovery mode) ───────────────────────────────
45
144
 
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')
145
+ describe('generatePlaybook Phase 0 — Discovery (hasBlueprint: false)', () => {
146
+ it('contains the discovery title', () => {
147
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: false })
148
+ expect(result).toContain('Project Discovery')
52
149
  })
53
150
 
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
- }
151
+ it('instructs Claude Code to ask the user questions', () => {
152
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: false })
153
+ expect(result).toContain('Ask the user these questions')
154
+ expect(result).toContain('What is this project')
155
+ expect(result).toContain('What are the main features')
61
156
  })
62
157
 
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')
158
+ it('still contains decomposition rules', () => {
159
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: false })
160
+ expect(result).toContain('Maximum 6 agents')
161
+ expect(result).toContain('AGENT_WORKFLOW.md')
69
162
  })
70
163
 
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')
164
+ it('still contains the validation gate', () => {
165
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: false })
166
+ expect(result).toContain('Should I proceed')
75
167
  })
76
168
 
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')
169
+ it('does NOT reference PROJECT_BLUEPRINT.md as a file to read', () => {
170
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: false })
171
+ expect(result).not.toContain('A `PROJECT_BLUEPRINT.md` was provided')
83
172
  })
84
173
 
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')
174
+ it('does NOT contain Agent Decomposition title', () => {
175
+ const result = generatePlaybook({ agents: sampleAgents, projectName: 'my-app', hasBlueprint: false })
176
+ expect(result).not.toContain('Agent Decomposition (run this first)')
89
177
  })
90
178
  })
91
179
 
180
+ // ─── toSlug ───────────────────────────────────────────────────────────────────
181
+
92
182
  describe('toSlug', () => {
93
183
  it('converts name to lowercase hyphenated slug', () => {
94
184
  expect(toSlug('Infra & Setup')).toBe('infra-setup')
@@ -104,6 +194,8 @@ describe('toSlug', () => {
104
194
  })
105
195
  })
106
196
 
197
+ // ─── extractAgentsFromWorkflow ────────────────────────────────────────────────
198
+
107
199
  describe('extractAgentsFromWorkflow', () => {
108
200
  it('extracts agents from a workflow string matching template format', () => {
109
201
  const workflow = `# Agent Workflow — React Project
@@ -149,4 +241,4 @@ Critère : npm run build
149
241
  it('returns empty array for content with no agent blocks', () => {
150
242
  expect(extractAgentsFromWorkflow('# Just a title\nNo agents here.')).toEqual([])
151
243
  })
152
- })
244
+ })