beth-copilot 1.0.13 → 1.0.15

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 (104) hide show
  1. package/CHANGELOG.md +195 -170
  2. package/README.md +408 -185
  3. package/bin/cli.js +65 -4
  4. package/dist/cli/commands/doctor.e2e.test.d.ts +8 -0
  5. package/dist/cli/commands/doctor.e2e.test.d.ts.map +1 -0
  6. package/dist/cli/commands/doctor.e2e.test.js +428 -0
  7. package/dist/cli/commands/doctor.e2e.test.js.map +1 -0
  8. package/dist/cli/commands/doctor.test.js +1 -1
  9. package/dist/cli/commands/help.e2e.test.d.ts +9 -0
  10. package/dist/cli/commands/help.e2e.test.d.ts.map +1 -0
  11. package/dist/cli/commands/help.e2e.test.js +150 -0
  12. package/dist/cli/commands/help.e2e.test.js.map +1 -0
  13. package/dist/cli/commands/init.test.d.ts +6 -0
  14. package/dist/cli/commands/init.test.d.ts.map +1 -0
  15. package/dist/cli/commands/init.test.js +289 -0
  16. package/dist/cli/commands/init.test.js.map +1 -0
  17. package/dist/cli/commands/mcp.e2e.test.d.ts +9 -0
  18. package/dist/cli/commands/mcp.e2e.test.d.ts.map +1 -0
  19. package/dist/cli/commands/mcp.e2e.test.js +139 -0
  20. package/dist/cli/commands/mcp.e2e.test.js.map +1 -0
  21. package/dist/cli/commands/pipeline.e2e.test.d.ts +9 -0
  22. package/dist/cli/commands/pipeline.e2e.test.d.ts.map +1 -0
  23. package/dist/cli/commands/pipeline.e2e.test.js +192 -0
  24. package/dist/cli/commands/pipeline.e2e.test.js.map +1 -0
  25. package/dist/cli/commands/quickstart.test.d.ts +6 -0
  26. package/dist/cli/commands/quickstart.test.d.ts.map +1 -0
  27. package/dist/cli/commands/quickstart.test.js +232 -0
  28. package/dist/cli/commands/quickstart.test.js.map +1 -0
  29. package/dist/core/agents/frontmatter.test.d.ts +8 -0
  30. package/dist/core/agents/frontmatter.test.d.ts.map +1 -0
  31. package/dist/core/agents/frontmatter.test.js +589 -0
  32. package/dist/core/agents/frontmatter.test.js.map +1 -0
  33. package/dist/core/agents/handoffs.test.d.ts +8 -0
  34. package/dist/core/agents/handoffs.test.d.ts.map +1 -0
  35. package/dist/core/agents/handoffs.test.js +320 -0
  36. package/dist/core/agents/handoffs.test.js.map +1 -0
  37. package/dist/core/agents/loader.test.js +1 -1
  38. package/dist/core/agents/suite.test.d.ts +8 -0
  39. package/dist/core/agents/suite.test.d.ts.map +1 -0
  40. package/dist/core/agents/suite.test.js +207 -0
  41. package/dist/core/agents/suite.test.js.map +1 -0
  42. package/dist/core/agents/tools.test.d.ts +8 -0
  43. package/dist/core/agents/tools.test.d.ts.map +1 -0
  44. package/dist/core/agents/tools.test.js +332 -0
  45. package/dist/core/agents/tools.test.js.map +1 -0
  46. package/dist/init.test.js +288 -0
  47. package/dist/providers/azure.d.ts +147 -0
  48. package/dist/providers/azure.d.ts.map +1 -0
  49. package/dist/providers/azure.js +491 -0
  50. package/dist/providers/azure.js.map +1 -0
  51. package/dist/providers/azure.test.d.ts +11 -0
  52. package/dist/providers/azure.test.d.ts.map +1 -0
  53. package/dist/providers/azure.test.js +330 -0
  54. package/dist/providers/azure.test.js.map +1 -0
  55. package/dist/providers/config.d.ts +87 -0
  56. package/dist/providers/config.d.ts.map +1 -0
  57. package/dist/providers/config.js +193 -0
  58. package/dist/providers/config.js.map +1 -0
  59. package/dist/providers/config.test.d.ts +7 -0
  60. package/dist/providers/config.test.d.ts.map +1 -0
  61. package/dist/providers/config.test.js +370 -0
  62. package/dist/providers/config.test.js.map +1 -0
  63. package/dist/providers/index.d.ts +18 -0
  64. package/dist/providers/index.d.ts.map +1 -0
  65. package/dist/providers/index.js +14 -0
  66. package/dist/providers/index.js.map +1 -0
  67. package/dist/providers/interface.d.ts +191 -0
  68. package/dist/providers/interface.d.ts.map +1 -0
  69. package/dist/providers/interface.js +94 -0
  70. package/dist/providers/interface.js.map +1 -0
  71. package/dist/providers/retry.d.ts +128 -0
  72. package/dist/providers/retry.d.ts.map +1 -0
  73. package/dist/providers/retry.js +205 -0
  74. package/dist/providers/retry.js.map +1 -0
  75. package/dist/providers/retry.test.d.ts +7 -0
  76. package/dist/providers/retry.test.d.ts.map +1 -0
  77. package/dist/providers/retry.test.js +439 -0
  78. package/dist/providers/retry.test.js.map +1 -0
  79. package/dist/providers/streaming.d.ts +157 -0
  80. package/dist/providers/streaming.d.ts.map +1 -0
  81. package/dist/providers/streaming.js +233 -0
  82. package/dist/providers/streaming.js.map +1 -0
  83. package/dist/providers/streaming.test.d.ts +7 -0
  84. package/dist/providers/streaming.test.d.ts.map +1 -0
  85. package/dist/providers/streaming.test.js +372 -0
  86. package/dist/providers/streaming.test.js.map +1 -0
  87. package/dist/providers/types.d.ts +209 -0
  88. package/dist/providers/types.d.ts.map +1 -0
  89. package/dist/providers/types.js +53 -0
  90. package/dist/providers/types.js.map +1 -0
  91. package/dist/providers/types.test.d.ts +7 -0
  92. package/dist/providers/types.test.d.ts.map +1 -0
  93. package/dist/providers/types.test.js +141 -0
  94. package/dist/providers/types.test.js.map +1 -0
  95. package/package.json +60 -56
  96. package/sbom.json +3302 -8
  97. package/templates/.github/agents/beth.agent.md +329 -329
  98. package/templates/.github/agents/developer.agent.md +572 -572
  99. package/templates/.github/agents/product-manager.agent.md +272 -272
  100. package/templates/.github/agents/researcher.agent.md +338 -338
  101. package/templates/.github/agents/security-reviewer.agent.md +465 -465
  102. package/templates/.github/agents/tester.agent.md +496 -496
  103. package/templates/.github/agents/ux-designer.agent.md +393 -393
  104. package/templates/mcp.json.example +4 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Agent Tools and Model Validation Tests
3
+ *
4
+ * Tests for agent tools array and model field parsing/validation.
5
+ * Run with: node --test dist/core/agents/tools.test.js
6
+ */
7
+ import { describe, it } from 'node:test';
8
+ import assert from 'node:assert';
9
+ import { join } from 'node:path';
10
+ import { loadAgents, loadAgent } from './loader.js';
11
+ // Test against templates directory for actual agent file tests
12
+ const TEMPLATES_AGENTS_DIR = join(process.cwd(), 'templates', '.github', 'agents');
13
+ /**
14
+ * All known tools defined in AgentTool type.
15
+ */
16
+ const KNOWN_TOOLS = [
17
+ 'codebase',
18
+ 'readFile',
19
+ 'editFiles',
20
+ 'createFile',
21
+ 'listDirectory',
22
+ 'fileSearch',
23
+ 'textSearch',
24
+ 'runInTerminal',
25
+ 'getTerminalOutput',
26
+ 'problems',
27
+ 'usages',
28
+ 'runSubagent',
29
+ ];
30
+ describe('Tools array validation', () => {
31
+ it('should accept valid tools array with known tools', () => {
32
+ const frontmatter = {
33
+ name: 'test-agent',
34
+ tools: ['codebase', 'readFile', 'editFiles'],
35
+ };
36
+ assert.ok(Array.isArray(frontmatter.tools));
37
+ assert.strictEqual(frontmatter.tools?.length, 3);
38
+ assert.ok(frontmatter.tools?.includes('codebase'));
39
+ assert.ok(frontmatter.tools?.includes('readFile'));
40
+ assert.ok(frontmatter.tools?.includes('editFiles'));
41
+ });
42
+ it('should accept empty tools array as valid', () => {
43
+ const frontmatter = {
44
+ name: 'test-agent',
45
+ tools: [],
46
+ };
47
+ assert.ok(Array.isArray(frontmatter.tools));
48
+ assert.strictEqual(frontmatter.tools?.length, 0);
49
+ });
50
+ it('should accept single tool in array', () => {
51
+ const frontmatter = {
52
+ name: 'test-agent',
53
+ tools: ['readFile'],
54
+ };
55
+ assert.ok(Array.isArray(frontmatter.tools));
56
+ assert.strictEqual(frontmatter.tools?.length, 1);
57
+ assert.strictEqual(frontmatter.tools?.[0], 'readFile');
58
+ });
59
+ it('should accept multiple tools in array', () => {
60
+ const tools = [
61
+ 'codebase',
62
+ 'readFile',
63
+ 'editFiles',
64
+ 'createFile',
65
+ 'listDirectory',
66
+ 'fileSearch',
67
+ 'textSearch',
68
+ 'runInTerminal',
69
+ 'getTerminalOutput',
70
+ 'problems',
71
+ 'usages',
72
+ 'runSubagent',
73
+ ];
74
+ const frontmatter = {
75
+ name: 'test-agent',
76
+ tools,
77
+ };
78
+ assert.strictEqual(frontmatter.tools?.length, 12);
79
+ assert.deepStrictEqual(frontmatter.tools, KNOWN_TOOLS);
80
+ });
81
+ it('should accept unknown/custom tools (MCP tools)', () => {
82
+ const frontmatter = {
83
+ name: 'test-agent',
84
+ tools: [
85
+ 'readFile',
86
+ 'mcp_brave_search', // Custom MCP tool
87
+ 'mcp_azure_deploy', // Custom MCP tool
88
+ 'custom_internal_tool', // Custom internal tool
89
+ 'fetch', // Non-standard tool
90
+ 'githubRepo', // Non-standard tool
91
+ ],
92
+ };
93
+ assert.strictEqual(frontmatter.tools?.length, 6);
94
+ assert.ok(frontmatter.tools?.includes('mcp_brave_search'));
95
+ assert.ok(frontmatter.tools?.includes('mcp_azure_deploy'));
96
+ assert.ok(frontmatter.tools?.includes('custom_internal_tool'));
97
+ });
98
+ it('should allow duplicate tools in array (no dedup in loader)', () => {
99
+ const frontmatter = {
100
+ name: 'test-agent',
101
+ tools: ['readFile', 'readFile', 'editFiles', 'editFiles', 'editFiles'],
102
+ };
103
+ assert.strictEqual(frontmatter.tools?.length, 5);
104
+ // Count duplicates
105
+ const readFileCount = frontmatter.tools?.filter((t) => t === 'readFile').length;
106
+ const editFilesCount = frontmatter.tools?.filter((t) => t === 'editFiles').length;
107
+ assert.strictEqual(readFileCount, 2);
108
+ assert.strictEqual(editFilesCount, 3);
109
+ });
110
+ });
111
+ describe('Tools validation via loader', () => {
112
+ it('should fail when tools is not an array', async () => {
113
+ // Create a mock YAML content where tools is a string instead of array
114
+ // We test this by directly checking the validateFrontmatter behavior
115
+ // Since we can't easily create temp files, we verify the loader validates properly
116
+ // The loader validates in validateFrontmatter that tools must be an array if present
117
+ // This is documented behavior - tools: "readFile" should fail
118
+ // We can verify this behavior by checking a successfully loaded agent
119
+ const result = loadAgent(join(TEMPLATES_AGENTS_DIR, 'developer.agent.md'));
120
+ assert.ok(!('error' in result), 'Developer agent should load successfully');
121
+ const { agent } = result;
122
+ assert.ok(Array.isArray(agent.frontmatter.tools), 'tools should be an array');
123
+ });
124
+ it('should convert non-string values in tools array to strings', () => {
125
+ // The normalizeFrontmatter function in loader.ts does:
126
+ // frontmatter.tools = data.tools.map(String);
127
+ // This converts any value to string
128
+ // Test that the type system allows this and loader handles it
129
+ // When YAML parses numbers or other types, they get converted to strings
130
+ // Create frontmatter as if it came from YAML with mixed types
131
+ const rawData = {
132
+ name: 'test-agent',
133
+ tools: [123, true, 'readFile', null],
134
+ };
135
+ // Simulate what normalizeFrontmatter does
136
+ const normalizedTools = rawData.tools.map(String);
137
+ assert.deepStrictEqual(normalizedTools, ['123', 'true', 'readFile', 'null']);
138
+ assert.ok(normalizedTools.every((t) => typeof t === 'string'));
139
+ });
140
+ });
141
+ describe('Model field validation', () => {
142
+ it('should accept Claude Opus 4.6 model', () => {
143
+ const frontmatter = {
144
+ name: 'test-agent',
145
+ model: 'Claude Opus 4.6',
146
+ };
147
+ assert.strictEqual(frontmatter.model, 'Claude Opus 4.6');
148
+ });
149
+ it('should accept claude-3-opus model', () => {
150
+ const frontmatter = {
151
+ name: 'test-agent',
152
+ model: 'claude-3-opus',
153
+ };
154
+ assert.strictEqual(frontmatter.model, 'claude-3-opus');
155
+ });
156
+ it('should accept any arbitrary string as model', () => {
157
+ const arbitraryModels = [
158
+ 'gpt-4-turbo',
159
+ 'gpt-4o',
160
+ 'claude-3-sonnet',
161
+ 'claude-3-haiku',
162
+ 'gemini-pro',
163
+ 'custom-fine-tuned-model-v2',
164
+ 'my-local-llama',
165
+ 'o1-preview',
166
+ 'claude-3.5-sonnet-20241022',
167
+ ];
168
+ for (const model of arbitraryModels) {
169
+ const frontmatter = {
170
+ name: 'test-agent',
171
+ model,
172
+ };
173
+ assert.strictEqual(frontmatter.model, model, `Should accept model: ${model}`);
174
+ }
175
+ });
176
+ it('should result in undefined when model field is missing', () => {
177
+ const frontmatter = {
178
+ name: 'test-agent',
179
+ // model is intentionally omitted
180
+ };
181
+ assert.strictEqual(frontmatter.model, undefined);
182
+ assert.ok(!('model' in frontmatter) || frontmatter.model === undefined);
183
+ });
184
+ it('should accept empty string as model (edge case)', () => {
185
+ const frontmatter = {
186
+ name: 'test-agent',
187
+ model: '',
188
+ };
189
+ assert.strictEqual(frontmatter.model, '');
190
+ });
191
+ });
192
+ describe('Actual agent files tool configurations', () => {
193
+ it('should load all template agents without errors', () => {
194
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
195
+ assert.strictEqual(result.errors.length, 0, `Unexpected errors: ${JSON.stringify(result.errors)}`);
196
+ assert.ok(result.agents.length >= 7, `Expected at least 7 agents, got ${result.agents.length}`);
197
+ });
198
+ it('developer agent should have comprehensive tool set', () => {
199
+ const result = loadAgent(join(TEMPLATES_AGENTS_DIR, 'developer.agent.md'));
200
+ assert.ok(!('error' in result));
201
+ const { agent } = result;
202
+ const tools = agent.frontmatter.tools ?? [];
203
+ // Developer should have file operations
204
+ assert.ok(tools.includes('readFile'), 'Developer should have readFile');
205
+ assert.ok(tools.includes('editFiles'), 'Developer should have editFiles');
206
+ assert.ok(tools.includes('createFile'), 'Developer should have createFile');
207
+ // Developer should have search capabilities
208
+ assert.ok(tools.includes('codebase'), 'Developer should have codebase');
209
+ assert.ok(tools.includes('fileSearch'), 'Developer should have fileSearch');
210
+ assert.ok(tools.includes('textSearch'), 'Developer should have textSearch');
211
+ // Developer should have terminal access
212
+ assert.ok(tools.includes('runInTerminal'), 'Developer should have runInTerminal');
213
+ assert.ok(tools.includes('getTerminalOutput'), 'Developer should have getTerminalOutput');
214
+ // Developer should be able to spawn subagents
215
+ assert.ok(tools.includes('runSubagent'), 'Developer should have runSubagent');
216
+ });
217
+ it('all agents should have tools as arrays', () => {
218
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
219
+ for (const agent of result.agents) {
220
+ if (agent.frontmatter.tools !== undefined) {
221
+ assert.ok(Array.isArray(agent.frontmatter.tools), `${agent.id} tools should be an array`);
222
+ }
223
+ }
224
+ });
225
+ it('all agents should have valid model fields', () => {
226
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
227
+ for (const agent of result.agents) {
228
+ // Model should either be undefined or a non-empty string
229
+ if (agent.frontmatter.model !== undefined) {
230
+ assert.strictEqual(typeof agent.frontmatter.model, 'string', `${agent.id} model should be a string`);
231
+ assert.ok(agent.frontmatter.model.length > 0, `${agent.id} model should not be empty if specified`);
232
+ }
233
+ }
234
+ });
235
+ it('tester agent should have testing-specific tools', () => {
236
+ const result = loadAgent(join(TEMPLATES_AGENTS_DIR, 'tester.agent.md'));
237
+ assert.ok(!('error' in result));
238
+ const { agent } = result;
239
+ const tools = agent.frontmatter.tools ?? [];
240
+ // Tester should have diagnostic tools
241
+ assert.ok(tools.includes('problems'), 'Tester should have problems tool');
242
+ // Tester may have testing-specific tools (custom)
243
+ // testFailure and runTests are custom tools used by tester
244
+ });
245
+ it('security-reviewer should have code analysis tools', () => {
246
+ const result = loadAgent(join(TEMPLATES_AGENTS_DIR, 'security-reviewer.agent.md'));
247
+ assert.ok(!('error' in result));
248
+ const { agent } = result;
249
+ const tools = agent.frontmatter.tools ?? [];
250
+ // Security reviewer should have code reading/searching
251
+ assert.ok(tools.includes('codebase'), 'Security reviewer should have codebase');
252
+ assert.ok(tools.includes('readFile'), 'Security reviewer should have readFile');
253
+ assert.ok(tools.includes('textSearch'), 'Security reviewer should have textSearch');
254
+ assert.ok(tools.includes('usages'), 'Security reviewer should have usages for tracking code usage');
255
+ });
256
+ });
257
+ describe('runSubagent capability validation', () => {
258
+ it('agents with runSubagent tool should have infer: true', () => {
259
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
260
+ for (const agent of result.agents) {
261
+ const tools = agent.frontmatter.tools ?? [];
262
+ const hasRunSubagent = tools.includes('runSubagent');
263
+ const hasInfer = agent.frontmatter.infer === true;
264
+ if (hasRunSubagent) {
265
+ // An agent with runSubagent should either:
266
+ // 1. Have infer: true (can be spawned as subagent itself), OR
267
+ // 2. Have handoffs (can delegate to other agents)
268
+ const hasHandoffs = (agent.frontmatter.handoffs?.length ?? 0) > 0;
269
+ assert.ok(hasInfer || hasHandoffs, `${agent.id} has runSubagent but no infer:true or handoffs - may not be able to participate in multi-agent workflows`);
270
+ }
271
+ }
272
+ });
273
+ it('Beth agent should be able to orchestrate all other agents', () => {
274
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
275
+ const beth = result.agents.find((a) => a.id === 'beth');
276
+ assert.ok(beth, 'Beth agent should exist');
277
+ assert.ok(beth?.frontmatter.infer === true, 'Beth should be inferable');
278
+ // Beth should have handoffs to all specialist agents
279
+ const handoffs = beth?.frontmatter.handoffs ?? [];
280
+ const handoffAgents = handoffs.map((h) => h.agent);
281
+ // Beth should be able to hand off to key specialists
282
+ assert.ok(handoffAgents.includes('developer'), 'Beth should have developer handoff');
283
+ assert.ok(handoffAgents.includes('tester'), 'Beth should have tester handoff');
284
+ assert.ok(handoffAgents.includes('ux-designer'), 'Beth should have ux-designer handoff');
285
+ assert.ok(handoffAgents.includes('product-manager'), 'Beth should have product-manager handoff');
286
+ assert.ok(handoffAgents.includes('researcher'), 'Beth should have researcher handoff');
287
+ assert.ok(handoffAgents.includes('security-reviewer'), 'Beth should have security-reviewer handoff');
288
+ });
289
+ it('all inferable agents can be spawned as subagents', () => {
290
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
291
+ const inferableAgents = result.agents.filter((a) => a.frontmatter.infer === true);
292
+ assert.ok(inferableAgents.length > 0, 'Should have at least one inferable agent');
293
+ // All inferable agents should have a name (required for subagent invocation)
294
+ for (const agent of inferableAgents) {
295
+ assert.ok(agent.frontmatter.name, `${agent.id} should have a name for subagent invocation`);
296
+ assert.strictEqual(typeof agent.frontmatter.name, 'string', `${agent.id} name should be a string`);
297
+ }
298
+ });
299
+ it('agents with handoffs should reference valid agent names', () => {
300
+ const result = loadAgents(TEMPLATES_AGENTS_DIR);
301
+ const agentIds = result.agents.map((a) => a.id);
302
+ for (const agent of result.agents) {
303
+ const handoffs = agent.frontmatter.handoffs ?? [];
304
+ for (const handoff of handoffs) {
305
+ assert.ok(agentIds.includes(handoff.agent), `${agent.id} has handoff to unknown agent: ${handoff.agent}`);
306
+ }
307
+ }
308
+ });
309
+ });
310
+ describe('Known tools completeness', () => {
311
+ it('should include all 12 known tools', () => {
312
+ assert.strictEqual(KNOWN_TOOLS.length, 12);
313
+ // Verify each known tool
314
+ assert.ok(KNOWN_TOOLS.includes('codebase'));
315
+ assert.ok(KNOWN_TOOLS.includes('readFile'));
316
+ assert.ok(KNOWN_TOOLS.includes('editFiles'));
317
+ assert.ok(KNOWN_TOOLS.includes('createFile'));
318
+ assert.ok(KNOWN_TOOLS.includes('listDirectory'));
319
+ assert.ok(KNOWN_TOOLS.includes('fileSearch'));
320
+ assert.ok(KNOWN_TOOLS.includes('textSearch'));
321
+ assert.ok(KNOWN_TOOLS.includes('runInTerminal'));
322
+ assert.ok(KNOWN_TOOLS.includes('getTerminalOutput'));
323
+ assert.ok(KNOWN_TOOLS.includes('problems'));
324
+ assert.ok(KNOWN_TOOLS.includes('usages'));
325
+ assert.ok(KNOWN_TOOLS.includes('runSubagent'));
326
+ });
327
+ it('known tools should have no duplicates', () => {
328
+ const uniqueTools = new Set(KNOWN_TOOLS);
329
+ assert.strictEqual(uniqueTools.size, KNOWN_TOOLS.length, 'KNOWN_TOOLS should have no duplicates');
330
+ });
331
+ });
332
+ //# sourceMappingURL=tools.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../../src/core/agents/tools.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,288 @@
1
+ /**
2
+ * E2E tests for init command.
3
+ * Run with: node --test dist/cli/commands/init.test.js
4
+ */
5
+ import { describe, it, beforeEach, afterEach } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import { execSync } from 'child_process';
8
+ import { existsSync, mkdirSync, writeFileSync, rmSync, readFileSync, readdirSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { tmpdir } from 'os';
11
+ // Expected files and directories from templates
12
+ const EXPECTED_AGENTS = [
13
+ 'beth.agent.md',
14
+ 'developer.agent.md',
15
+ 'product-manager.agent.md',
16
+ 'researcher.agent.md',
17
+ 'security-reviewer.agent.md',
18
+ 'tester.agent.md',
19
+ 'ux-designer.agent.md',
20
+ ];
21
+ const EXPECTED_SKILLS = [
22
+ 'framer-components',
23
+ 'prd',
24
+ 'security-analysis',
25
+ 'shadcn-ui',
26
+ 'vercel-react-best-practices',
27
+ 'web-design-guidelines',
28
+ ];
29
+ // Path to CLI binary
30
+ const CLI_PATH = join(process.cwd(), 'bin', 'cli.js');
31
+ /**
32
+ * Run the init command in a specified directory.
33
+ * Uses --skip-beads to avoid interactive prompts during testing.
34
+ */
35
+ function runInit(cwd, flags = []) {
36
+ // Always include --skip-beads to avoid interactive prompts
37
+ const allFlags = ['--skip-beads', ...flags];
38
+ const command = `node "${CLI_PATH}" init ${allFlags.join(' ')}`;
39
+ try {
40
+ const stdout = execSync(command, {
41
+ cwd,
42
+ encoding: 'utf-8',
43
+ env: { ...process.env, NO_COLOR: '1' }, // Disable colors and animations
44
+ stdio: ['pipe', 'pipe', 'pipe'],
45
+ });
46
+ return { stdout, stderr: '', exitCode: 0 };
47
+ }
48
+ catch (error) {
49
+ const execError = error;
50
+ return {
51
+ stdout: execError.stdout || '',
52
+ stderr: execError.stderr || '',
53
+ exitCode: execError.status || 1,
54
+ };
55
+ }
56
+ }
57
+ describe('init command E2E', () => {
58
+ let testDir;
59
+ beforeEach(() => {
60
+ // Create a temp directory for each test
61
+ testDir = join(tmpdir(), `beth-init-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
62
+ mkdirSync(testDir, { recursive: true });
63
+ });
64
+ afterEach(() => {
65
+ // Clean up temp directory
66
+ if (existsSync(testDir)) {
67
+ rmSync(testDir, { recursive: true, force: true });
68
+ }
69
+ });
70
+ describe('directory structure creation', () => {
71
+ it('should create .github/agents directory with all 7 agent files', () => {
72
+ runInit(testDir);
73
+ const agentsDir = join(testDir, '.github', 'agents');
74
+ assert.strictEqual(existsSync(agentsDir), true, '.github/agents directory should exist');
75
+ const files = readdirSync(agentsDir).filter(f => f.endsWith('.agent.md')).sort();
76
+ assert.deepStrictEqual(files, EXPECTED_AGENTS, 'All 7 agent files should be created');
77
+ });
78
+ it('should create .github/skills directory with all skill directories', () => {
79
+ runInit(testDir);
80
+ const skillsDir = join(testDir, '.github', 'skills');
81
+ assert.strictEqual(existsSync(skillsDir), true, '.github/skills directory should exist');
82
+ const dirs = readdirSync(skillsDir).filter(f => {
83
+ return existsSync(join(skillsDir, f, 'SKILL.md')) ||
84
+ existsSync(join(skillsDir, f, 'AGENTS.md'));
85
+ }).sort();
86
+ assert.deepStrictEqual(dirs, EXPECTED_SKILLS, 'All skill directories should be created');
87
+ });
88
+ it('should create AGENTS.md in project root', () => {
89
+ runInit(testDir);
90
+ const agentsMd = join(testDir, 'AGENTS.md');
91
+ assert.strictEqual(existsSync(agentsMd), true, 'AGENTS.md should exist');
92
+ const content = readFileSync(agentsMd, 'utf-8');
93
+ assert.ok(content.includes('beads'), 'AGENTS.md should mention beads');
94
+ });
95
+ it('should create Backlog.md in project root', () => {
96
+ runInit(testDir);
97
+ const backlogMd = join(testDir, 'Backlog.md');
98
+ assert.strictEqual(existsSync(backlogMd), true, 'Backlog.md should exist');
99
+ });
100
+ it('should create mcp.json.example in project root', () => {
101
+ runInit(testDir);
102
+ const mcpJson = join(testDir, 'mcp.json.example');
103
+ assert.strictEqual(existsSync(mcpJson), true, 'mcp.json.example should exist');
104
+ });
105
+ it('should create .vscode/settings.json', () => {
106
+ runInit(testDir);
107
+ const settingsJson = join(testDir, '.vscode', 'settings.json');
108
+ assert.strictEqual(existsSync(settingsJson), true, '.vscode/settings.json should exist');
109
+ });
110
+ it('should create copilot-instructions.md in .github', () => {
111
+ runInit(testDir);
112
+ const copilotInstructions = join(testDir, '.github', 'copilot-instructions.md');
113
+ assert.strictEqual(existsSync(copilotInstructions), true, '.github/copilot-instructions.md should exist');
114
+ });
115
+ });
116
+ describe('--force flag', () => {
117
+ it('should overwrite existing files when --force is used', () => {
118
+ // Create existing AGENTS.md with different content
119
+ const agentsMd = join(testDir, 'AGENTS.md');
120
+ writeFileSync(agentsMd, 'ORIGINAL CONTENT');
121
+ // Run init without --force
122
+ runInit(testDir);
123
+ let content = readFileSync(agentsMd, 'utf-8');
124
+ assert.strictEqual(content, 'ORIGINAL CONTENT', 'Should not overwrite without --force');
125
+ // Run init with --force
126
+ runInit(testDir, ['--force']);
127
+ content = readFileSync(agentsMd, 'utf-8');
128
+ assert.notStrictEqual(content, 'ORIGINAL CONTENT', 'Should overwrite with --force');
129
+ assert.ok(content.includes('beads'), 'Content should be from template');
130
+ });
131
+ it('should overwrite existing agent files with --force', () => {
132
+ // Create existing .github/agents directory with modified file
133
+ const agentsDir = join(testDir, '.github', 'agents');
134
+ mkdirSync(agentsDir, { recursive: true });
135
+ const bethAgent = join(agentsDir, 'beth.agent.md');
136
+ writeFileSync(bethAgent, 'MODIFIED BETH');
137
+ // Run init without --force
138
+ runInit(testDir);
139
+ let content = readFileSync(bethAgent, 'utf-8');
140
+ assert.strictEqual(content, 'MODIFIED BETH', 'Should not overwrite without --force');
141
+ // Run init with --force
142
+ runInit(testDir, ['--force']);
143
+ content = readFileSync(bethAgent, 'utf-8');
144
+ assert.notStrictEqual(content, 'MODIFIED BETH', 'Should overwrite with --force');
145
+ });
146
+ });
147
+ describe('--skip-backlog flag', () => {
148
+ it('should not create Backlog.md when --skip-backlog is used', () => {
149
+ runInit(testDir, ['--skip-backlog']);
150
+ const backlogMd = join(testDir, 'Backlog.md');
151
+ assert.strictEqual(existsSync(backlogMd), false, 'Backlog.md should not exist');
152
+ });
153
+ it('should still create other files when --skip-backlog is used', () => {
154
+ runInit(testDir, ['--skip-backlog']);
155
+ assert.strictEqual(existsSync(join(testDir, 'AGENTS.md')), true, 'AGENTS.md should exist');
156
+ assert.strictEqual(existsSync(join(testDir, 'mcp.json.example')), true, 'mcp.json.example should exist');
157
+ assert.strictEqual(existsSync(join(testDir, '.github', 'agents')), true, '.github/agents should exist');
158
+ });
159
+ });
160
+ describe('--skip-mcp flag', () => {
161
+ it('should not create mcp.json.example when --skip-mcp is used', () => {
162
+ runInit(testDir, ['--skip-mcp']);
163
+ const mcpJson = join(testDir, 'mcp.json.example');
164
+ assert.strictEqual(existsSync(mcpJson), false, 'mcp.json.example should not exist');
165
+ });
166
+ it('should still create other files when --skip-mcp is used', () => {
167
+ runInit(testDir, ['--skip-mcp']);
168
+ assert.strictEqual(existsSync(join(testDir, 'AGENTS.md')), true, 'AGENTS.md should exist');
169
+ assert.strictEqual(existsSync(join(testDir, 'Backlog.md')), true, 'Backlog.md should exist');
170
+ assert.strictEqual(existsSync(join(testDir, '.github', 'agents')), true, '.github/agents should exist');
171
+ });
172
+ });
173
+ describe('--skip-beads flag', () => {
174
+ it('should complete without beads check when --skip-beads is used', () => {
175
+ const result = runInit(testDir);
176
+ // Should complete successfully (exit 0)
177
+ assert.strictEqual(result.exitCode, 0, 'Should exit with code 0');
178
+ // Should have created files
179
+ assert.strictEqual(existsSync(join(testDir, '.github', 'agents')), true, 'Should create agents');
180
+ });
181
+ it('should show warning about skipping beads', () => {
182
+ const result = runInit(testDir);
183
+ // Output should mention skipping beads
184
+ assert.ok(result.stdout.includes('Skipped beads check') || result.stdout.includes('skip-beads'), 'Should warn about skipping beads');
185
+ });
186
+ });
187
+ describe('existing files handling', () => {
188
+ it('should warn about existing files without --force', () => {
189
+ // Create existing file
190
+ writeFileSync(join(testDir, 'AGENTS.md'), 'EXISTING');
191
+ const result = runInit(testDir);
192
+ assert.ok(result.stdout.includes('Skipped') || result.stdout.includes('exists'), 'Should mention that file was skipped');
193
+ });
194
+ it('should not overwrite existing files without --force', () => {
195
+ const agentsMd = join(testDir, 'AGENTS.md');
196
+ writeFileSync(agentsMd, 'ORIGINAL');
197
+ runInit(testDir);
198
+ const content = readFileSync(agentsMd, 'utf-8');
199
+ assert.strictEqual(content, 'ORIGINAL', 'Should preserve original content');
200
+ });
201
+ it('should count files correctly when some are skipped', () => {
202
+ // Create some existing files
203
+ writeFileSync(join(testDir, 'AGENTS.md'), 'EXISTING');
204
+ writeFileSync(join(testDir, 'Backlog.md'), 'EXISTING');
205
+ const result = runInit(testDir);
206
+ // Should have installed some files but not all
207
+ assert.ok(result.stdout.includes('Installed'), 'Should report installed files');
208
+ });
209
+ });
210
+ describe('empty directory behavior', () => {
211
+ it('should work correctly in an empty directory', () => {
212
+ const result = runInit(testDir);
213
+ assert.strictEqual(result.exitCode, 0, 'Should exit with code 0');
214
+ assert.strictEqual(existsSync(join(testDir, '.github', 'agents')), true, '.github/agents should exist');
215
+ assert.strictEqual(existsSync(join(testDir, '.github', 'skills')), true, '.github/skills should exist');
216
+ assert.strictEqual(existsSync(join(testDir, 'AGENTS.md')), true, 'AGENTS.md should exist');
217
+ });
218
+ it('should install all expected files in empty directory', () => {
219
+ runInit(testDir);
220
+ // Check all expected files exist
221
+ const agentsDir = join(testDir, '.github', 'agents');
222
+ const agentFiles = readdirSync(agentsDir).filter(f => f.endsWith('.agent.md'));
223
+ assert.strictEqual(agentFiles.length, 7, 'Should create 7 agent files');
224
+ const skillsDir = join(testDir, '.github', 'skills');
225
+ const skillDirs = readdirSync(skillsDir);
226
+ assert.strictEqual(skillDirs.length, 6, 'Should create 6 skill directories');
227
+ });
228
+ });
229
+ describe('existing .github folder behavior', () => {
230
+ it('should work in directory with existing .github folder', () => {
231
+ // Create existing .github folder with some content
232
+ const existingGithub = join(testDir, '.github', 'workflows');
233
+ mkdirSync(existingGithub, { recursive: true });
234
+ writeFileSync(join(existingGithub, 'ci.yml'), 'name: CI');
235
+ const result = runInit(testDir);
236
+ assert.strictEqual(result.exitCode, 0, 'Should exit with code 0');
237
+ // Should preserve existing content
238
+ assert.strictEqual(existsSync(join(existingGithub, 'ci.yml')), true, 'Should preserve existing .github/workflows/ci.yml');
239
+ // Should add new content
240
+ assert.strictEqual(existsSync(join(testDir, '.github', 'agents')), true, 'Should create .github/agents');
241
+ });
242
+ it('should merge with existing .github/agents folder', () => {
243
+ // Create existing agents folder with custom agent
244
+ const agentsDir = join(testDir, '.github', 'agents');
245
+ mkdirSync(agentsDir, { recursive: true });
246
+ writeFileSync(join(agentsDir, 'custom.agent.md'), 'CUSTOM AGENT');
247
+ runInit(testDir);
248
+ // Should preserve custom agent
249
+ assert.strictEqual(existsSync(join(agentsDir, 'custom.agent.md')), true, 'Should preserve custom.agent.md');
250
+ // Custom agent content should be unchanged
251
+ const customContent = readFileSync(join(agentsDir, 'custom.agent.md'), 'utf-8');
252
+ assert.strictEqual(customContent, 'CUSTOM AGENT', 'Custom agent content preserved');
253
+ // Should have added Beth agents (they don't exist yet)
254
+ assert.strictEqual(existsSync(join(agentsDir, 'beth.agent.md')), true, 'Should create beth.agent.md');
255
+ });
256
+ });
257
+ describe('multiple flag combinations', () => {
258
+ it('should handle --skip-backlog and --skip-mcp together', () => {
259
+ runInit(testDir, ['--skip-backlog', '--skip-mcp']);
260
+ assert.strictEqual(existsSync(join(testDir, 'Backlog.md')), false, 'Backlog.md should not exist');
261
+ assert.strictEqual(existsSync(join(testDir, 'mcp.json.example')), false, 'mcp.json.example should not exist');
262
+ assert.strictEqual(existsSync(join(testDir, 'AGENTS.md')), true, 'AGENTS.md should exist');
263
+ assert.strictEqual(existsSync(join(testDir, '.github', 'agents')), true, '.github/agents should exist');
264
+ });
265
+ it('should handle --force with skip flags', () => {
266
+ // Create existing file
267
+ writeFileSync(join(testDir, 'AGENTS.md'), 'ORIGINAL');
268
+ runInit(testDir, ['--force', '--skip-backlog', '--skip-mcp']);
269
+ // Force should overwrite
270
+ const content = readFileSync(join(testDir, 'AGENTS.md'), 'utf-8');
271
+ assert.notStrictEqual(content, 'ORIGINAL', 'Should overwrite with --force');
272
+ // Skip flags should still work
273
+ assert.strictEqual(existsSync(join(testDir, 'Backlog.md')), false, 'Backlog.md should not exist');
274
+ assert.strictEqual(existsSync(join(testDir, 'mcp.json.example')), false, 'mcp.json.example should not exist');
275
+ });
276
+ });
277
+ describe('output messages', () => {
278
+ it('should report number of installed files', () => {
279
+ const result = runInit(testDir);
280
+ assert.ok(result.stdout.includes('Installed'), 'Should mention installed files');
281
+ assert.ok(/Installed \d+ files/.test(result.stdout), 'Should report file count');
282
+ });
283
+ it('should show next steps after installation', () => {
284
+ const result = runInit(testDir);
285
+ assert.ok(result.stdout.includes('Next steps') || result.stdout.includes('VS Code'), 'Should show next steps');
286
+ });
287
+ });
288
+ });