@output.ai/cli 0.5.6 → 0.6.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/commands/agents/init.d.ts +0 -1
- package/dist/commands/agents/init.js +9 -16
- package/dist/commands/agents/init.spec.js +49 -224
- package/dist/commands/workflow/generate.js +2 -2
- package/dist/commands/workflow/plan.js +3 -3
- package/dist/commands/workflow/plan.spec.js +13 -13
- package/dist/services/claude_client.d.ts +4 -4
- package/dist/services/claude_client.integration.test.js +2 -2
- package/dist/services/claude_client.js +7 -7
- package/dist/services/claude_client.spec.js +3 -3
- package/dist/services/coding_agents.d.ts +10 -24
- package/dist/services/coding_agents.js +112 -368
- package/dist/services/coding_agents.spec.js +101 -290
- package/dist/services/project_scaffold.js +3 -3
- package/dist/services/workflow_builder.d.ts +1 -1
- package/dist/services/workflow_builder.js +1 -1
- package/dist/services/workflow_planner.js +1 -2
- package/dist/services/workflow_planner.spec.js +4 -5
- package/dist/templates/agent_instructions/dotclaude/settings.json.template +29 -0
- package/dist/templates/agent_instructions/{AGENTS.md.template → dotoutputai/AGENTS.md.template} +12 -10
- package/dist/utils/claude.d.ts +5 -0
- package/dist/utils/claude.js +19 -0
- package/dist/utils/claude.spec.d.ts +1 -0
- package/dist/utils/claude.spec.js +119 -0
- package/dist/utils/paths.d.ts +0 -4
- package/dist/utils/paths.js +0 -6
- package/package.json +3 -3
- package/dist/templates/agent_instructions/agents/workflow_context_fetcher.md.template +0 -82
- package/dist/templates/agent_instructions/agents/workflow_debugger.md.template +0 -98
- package/dist/templates/agent_instructions/agents/workflow_planner.md.template +0 -113
- package/dist/templates/agent_instructions/agents/workflow_prompt_writer.md.template +0 -595
- package/dist/templates/agent_instructions/agents/workflow_quality.md.template +0 -244
- package/dist/templates/agent_instructions/commands/build_workflow.md.template +0 -290
- package/dist/templates/agent_instructions/commands/debug_workflow.md.template +0 -198
- package/dist/templates/agent_instructions/commands/plan_workflow.md.template +0 -261
- package/dist/templates/agent_instructions/meta/post_flight.md.template +0 -94
- package/dist/templates/agent_instructions/meta/pre_flight.md.template +0 -60
- package/dist/templates/agent_instructions/skills/output-error-direct-io/SKILL.md.template +0 -249
- package/dist/templates/agent_instructions/skills/output-error-http-client/SKILL.md.template +0 -298
- package/dist/templates/agent_instructions/skills/output-error-missing-schemas/SKILL.md.template +0 -265
- package/dist/templates/agent_instructions/skills/output-error-nondeterminism/SKILL.md.template +0 -252
- package/dist/templates/agent_instructions/skills/output-error-try-catch/SKILL.md.template +0 -226
- package/dist/templates/agent_instructions/skills/output-error-zod-import/SKILL.md.template +0 -209
- package/dist/templates/agent_instructions/skills/output-services-check/SKILL.md.template +0 -128
- package/dist/templates/agent_instructions/skills/output-workflow-list/SKILL.md.template +0 -117
- package/dist/templates/agent_instructions/skills/output-workflow-result/SKILL.md.template +0 -199
- package/dist/templates/agent_instructions/skills/output-workflow-run/SKILL.md.template +0 -228
- package/dist/templates/agent_instructions/skills/output-workflow-runs-list/SKILL.md.template +0 -141
- package/dist/templates/agent_instructions/skills/output-workflow-start/SKILL.md.template +0 -201
- package/dist/templates/agent_instructions/skills/output-workflow-status/SKILL.md.template +0 -151
- package/dist/templates/agent_instructions/skills/output-workflow-stop/SKILL.md.template +0 -164
- package/dist/templates/agent_instructions/skills/output-workflow-trace/SKILL.md.template +0 -134
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { checkAgentConfigDirExists, checkAgentStructure, getAgentConfigDir, prepareTemplateVariables, initializeAgentConfig, ensureOutputAISystem } from './coding_agents.js';
|
|
3
3
|
import { access } from 'node:fs/promises';
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
|
-
import { getErrorMessage } from '#utils/error_utils.js';
|
|
6
5
|
vi.mock('node:fs/promises');
|
|
7
6
|
vi.mock('../utils/paths.js', () => ({
|
|
8
7
|
getTemplateDir: vi.fn().mockReturnValue('/templates')
|
|
@@ -10,8 +9,8 @@ vi.mock('../utils/paths.js', () => ({
|
|
|
10
9
|
vi.mock('../utils/template.js', () => ({
|
|
11
10
|
processTemplate: vi.fn().mockImplementation((content) => content)
|
|
12
11
|
}));
|
|
13
|
-
vi.mock('
|
|
14
|
-
|
|
12
|
+
vi.mock('../utils/claude.js', () => ({
|
|
13
|
+
executeClaudeCommand: vi.fn().mockResolvedValue(undefined)
|
|
15
14
|
}));
|
|
16
15
|
vi.mock('@oclif/core', () => ({
|
|
17
16
|
ux: {
|
|
@@ -24,37 +23,6 @@ describe('coding_agents service', () => {
|
|
|
24
23
|
beforeEach(() => {
|
|
25
24
|
vi.clearAllMocks();
|
|
26
25
|
});
|
|
27
|
-
describe('getRequiredFiles', () => {
|
|
28
|
-
it('should derive required files from both AGENT_CONFIGS', () => {
|
|
29
|
-
const files = getRequiredFiles();
|
|
30
|
-
expect(files).toContain('.outputai/AGENTS.md');
|
|
31
|
-
expect(files).toContain('.outputai/agents/workflow_planner.md');
|
|
32
|
-
expect(files).toContain('.outputai/commands/plan_workflow.md');
|
|
33
|
-
expect(files).toContain('CLAUDE.md');
|
|
34
|
-
expect(files).toContain('.claude/agents/workflow_planner.md');
|
|
35
|
-
expect(files).toContain('.claude/commands/plan_workflow.md');
|
|
36
|
-
// Spot check skill files (all at top level - no nesting)
|
|
37
|
-
expect(files).toContain('.outputai/skills/output-services-check/SKILL.md');
|
|
38
|
-
expect(files).toContain('.outputai/skills/output-workflow-list/SKILL.md');
|
|
39
|
-
expect(files).toContain('.outputai/skills/output-error-zod-import/SKILL.md');
|
|
40
|
-
expect(files).toContain('.claude/skills/output-workflow-trace/SKILL.md');
|
|
41
|
-
expect(files).toContain('.claude/skills/output-workflow-run/SKILL.md');
|
|
42
|
-
expect(files).toContain('.claude/skills/output-error-nondeterminism/SKILL.md');
|
|
43
|
-
});
|
|
44
|
-
it('should include both outputai and claude-code files', () => {
|
|
45
|
-
const files = getRequiredFiles();
|
|
46
|
-
const expectedCount = AGENT_CONFIGS.outputai.mappings.length +
|
|
47
|
-
AGENT_CONFIGS['claude-code'].mappings.length;
|
|
48
|
-
expect(files.length).toBe(expectedCount);
|
|
49
|
-
});
|
|
50
|
-
it('should have outputai files with .outputai prefix', () => {
|
|
51
|
-
const files = getRequiredFiles();
|
|
52
|
-
const outputaiFiles = files.slice(0, 26);
|
|
53
|
-
outputaiFiles.forEach(file => {
|
|
54
|
-
expect(file).toMatch(/^\.outputai\//);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
26
|
describe('getAgentConfigDir', () => {
|
|
59
27
|
it('should return the correct path to .outputai directory', () => {
|
|
60
28
|
const result = getAgentConfigDir('/test/project');
|
|
@@ -75,128 +43,85 @@ describe('coding_agents service', () => {
|
|
|
75
43
|
});
|
|
76
44
|
});
|
|
77
45
|
describe('checkAgentStructure', () => {
|
|
78
|
-
it('should return
|
|
46
|
+
it('should return needsInit true when directory does not exist', async () => {
|
|
79
47
|
vi.mocked(access).mockRejectedValue({ code: 'ENOENT' });
|
|
80
48
|
const result = await checkAgentStructure('/test/project');
|
|
81
49
|
expect(result).toEqual({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'.outputai/AGENTS.md',
|
|
85
|
-
'.outputai/agents/workflow_planner.md',
|
|
86
|
-
'.outputai/agents/workflow_quality.md',
|
|
87
|
-
'.outputai/agents/workflow_context_fetcher.md',
|
|
88
|
-
'.outputai/agents/workflow_prompt_writer.md',
|
|
89
|
-
'.outputai/agents/workflow_debugger.md',
|
|
90
|
-
'.outputai/commands/plan_workflow.md',
|
|
91
|
-
'.outputai/commands/debug_workflow.md',
|
|
92
|
-
'.outputai/commands/build_workflow.md',
|
|
93
|
-
'.outputai/meta/pre_flight.md',
|
|
94
|
-
'.outputai/meta/post_flight.md',
|
|
95
|
-
// Skills (all at top level - no nesting allowed)
|
|
96
|
-
'.outputai/skills/output-services-check/SKILL.md',
|
|
97
|
-
'.outputai/skills/output-workflow-trace/SKILL.md',
|
|
98
|
-
'.outputai/skills/output-workflow-list/SKILL.md',
|
|
99
|
-
'.outputai/skills/output-workflow-runs-list/SKILL.md',
|
|
100
|
-
'.outputai/skills/output-workflow-run/SKILL.md',
|
|
101
|
-
'.outputai/skills/output-workflow-start/SKILL.md',
|
|
102
|
-
'.outputai/skills/output-workflow-status/SKILL.md',
|
|
103
|
-
'.outputai/skills/output-workflow-result/SKILL.md',
|
|
104
|
-
'.outputai/skills/output-workflow-stop/SKILL.md',
|
|
105
|
-
'.outputai/skills/output-error-zod-import/SKILL.md',
|
|
106
|
-
'.outputai/skills/output-error-nondeterminism/SKILL.md',
|
|
107
|
-
'.outputai/skills/output-error-try-catch/SKILL.md',
|
|
108
|
-
'.outputai/skills/output-error-missing-schemas/SKILL.md',
|
|
109
|
-
'.outputai/skills/output-error-direct-io/SKILL.md',
|
|
110
|
-
'.outputai/skills/output-error-http-client/SKILL.md',
|
|
111
|
-
// Claude-code symlinks
|
|
112
|
-
'CLAUDE.md',
|
|
113
|
-
'.claude/agents/workflow_planner.md',
|
|
114
|
-
'.claude/agents/workflow_quality.md',
|
|
115
|
-
'.claude/agents/workflow_context_fetcher.md',
|
|
116
|
-
'.claude/agents/workflow_prompt_writer.md',
|
|
117
|
-
'.claude/agents/workflow_debugger.md',
|
|
118
|
-
'.claude/commands/plan_workflow.md',
|
|
119
|
-
'.claude/commands/debug_workflow.md',
|
|
120
|
-
'.claude/commands/build_workflow.md',
|
|
121
|
-
// Claude-code skill symlinks (all at top level - no nesting allowed)
|
|
122
|
-
'.claude/skills/output-services-check/SKILL.md',
|
|
123
|
-
'.claude/skills/output-workflow-trace/SKILL.md',
|
|
124
|
-
'.claude/skills/output-workflow-list/SKILL.md',
|
|
125
|
-
'.claude/skills/output-workflow-runs-list/SKILL.md',
|
|
126
|
-
'.claude/skills/output-workflow-run/SKILL.md',
|
|
127
|
-
'.claude/skills/output-workflow-start/SKILL.md',
|
|
128
|
-
'.claude/skills/output-workflow-status/SKILL.md',
|
|
129
|
-
'.claude/skills/output-workflow-result/SKILL.md',
|
|
130
|
-
'.claude/skills/output-workflow-stop/SKILL.md',
|
|
131
|
-
'.claude/skills/output-error-zod-import/SKILL.md',
|
|
132
|
-
'.claude/skills/output-error-nondeterminism/SKILL.md',
|
|
133
|
-
'.claude/skills/output-error-try-catch/SKILL.md',
|
|
134
|
-
'.claude/skills/output-error-missing-schemas/SKILL.md',
|
|
135
|
-
'.claude/skills/output-error-direct-io/SKILL.md',
|
|
136
|
-
'.claude/skills/output-error-http-client/SKILL.md'
|
|
137
|
-
],
|
|
138
|
-
isComplete: false
|
|
50
|
+
isComplete: false,
|
|
51
|
+
needsInit: true
|
|
139
52
|
});
|
|
140
53
|
});
|
|
141
|
-
it('should return complete when all files exist', async () => {
|
|
142
|
-
// Mock directory exists
|
|
54
|
+
it('should return complete when all files exist with valid settings', async () => {
|
|
143
55
|
vi.mocked(access).mockResolvedValue(undefined);
|
|
56
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
|
|
57
|
+
extraKnownMarketplaces: {
|
|
58
|
+
'team-tools': {
|
|
59
|
+
source: {
|
|
60
|
+
source: 'github',
|
|
61
|
+
repo: 'growthxai/output-claude-plugins'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
enabledPlugins: {
|
|
66
|
+
'outputai@outputai': true
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
144
69
|
const result = await checkAgentStructure('/test/project');
|
|
145
70
|
expect(result).toEqual({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
isComplete: true
|
|
71
|
+
isComplete: true,
|
|
72
|
+
needsInit: false
|
|
149
73
|
});
|
|
150
|
-
expect(access).toHaveBeenCalledTimes(51); // dir + 26 outputai + 24 claude-code
|
|
151
74
|
});
|
|
152
|
-
it('should return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (pathStr.endsWith(missing)) {
|
|
162
|
-
throw { code: 'ENOENT' };
|
|
75
|
+
it('should return needsInit true when settings.json has wrong marketplace repo', async () => {
|
|
76
|
+
vi.mocked(access).mockResolvedValue(undefined);
|
|
77
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
|
|
78
|
+
extraKnownMarketplaces: {
|
|
79
|
+
'team-tools': {
|
|
80
|
+
source: {
|
|
81
|
+
source: 'github',
|
|
82
|
+
repo: 'wrong/repo'
|
|
83
|
+
}
|
|
163
84
|
}
|
|
85
|
+
},
|
|
86
|
+
enabledPlugins: {
|
|
87
|
+
'outputai@outputai': true
|
|
164
88
|
}
|
|
165
|
-
|
|
166
|
-
});
|
|
89
|
+
}));
|
|
167
90
|
const result = await checkAgentStructure('/test/project');
|
|
168
|
-
expect(result).
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
91
|
+
expect(result.isComplete).toBe(false);
|
|
92
|
+
expect(result.needsInit).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it('should return needsInit true when plugin is not enabled', async () => {
|
|
95
|
+
vi.mocked(access).mockResolvedValue(undefined);
|
|
96
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
|
|
97
|
+
extraKnownMarketplaces: {
|
|
98
|
+
'team-tools': {
|
|
99
|
+
source: {
|
|
100
|
+
source: 'github',
|
|
101
|
+
repo: 'growthxai/output-claude-plugins'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
enabledPlugins: {
|
|
106
|
+
'outputai@outputai': false
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
const result = await checkAgentStructure('/test/project');
|
|
110
|
+
expect(result.isComplete).toBe(false);
|
|
111
|
+
expect(result.needsInit).toBe(true);
|
|
176
112
|
});
|
|
177
|
-
it('should
|
|
178
|
-
const missingFiles = new Set([
|
|
179
|
-
'.outputai/agents/workflow_planner.md',
|
|
180
|
-
'.outputai/commands/plan_workflow.md',
|
|
181
|
-
'.claude/commands/plan_workflow.md'
|
|
182
|
-
]);
|
|
113
|
+
it('should return needsInit true when settings.json does not exist', async () => {
|
|
183
114
|
vi.mocked(access).mockImplementation(async (path) => {
|
|
184
115
|
const pathStr = path.toString();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
throw { code: 'ENOENT' };
|
|
188
|
-
}
|
|
116
|
+
if (pathStr.endsWith('.claude/settings.json')) {
|
|
117
|
+
throw { code: 'ENOENT' };
|
|
189
118
|
}
|
|
190
119
|
return undefined;
|
|
191
120
|
});
|
|
121
|
+
vi.mocked(fs.readFile).mockRejectedValue({ code: 'ENOENT' });
|
|
192
122
|
const result = await checkAgentStructure('/test/project');
|
|
193
|
-
expect(result.dirExists).toBe(true);
|
|
194
|
-
expect(result.missingFiles).toEqual([
|
|
195
|
-
'.outputai/agents/workflow_planner.md',
|
|
196
|
-
'.outputai/commands/plan_workflow.md',
|
|
197
|
-
'.claude/commands/plan_workflow.md'
|
|
198
|
-
]);
|
|
199
123
|
expect(result.isComplete).toBe(false);
|
|
124
|
+
expect(result.needsInit).toBe(true);
|
|
200
125
|
});
|
|
201
126
|
});
|
|
202
127
|
describe('prepareTemplateVariables', () => {
|
|
@@ -204,97 +129,60 @@ describe('coding_agents service', () => {
|
|
|
204
129
|
const variables = prepareTemplateVariables();
|
|
205
130
|
expect(variables).toHaveProperty('date');
|
|
206
131
|
expect(typeof variables.date).toBe('string');
|
|
207
|
-
// Should be in format like "January 1, 2025"
|
|
208
132
|
expect(variables.date).toMatch(/^[A-Z][a-z]+ \d{1,2}, \d{4}$/);
|
|
209
133
|
});
|
|
210
134
|
});
|
|
211
135
|
describe('initializeAgentConfig', () => {
|
|
212
136
|
beforeEach(() => {
|
|
213
|
-
// Mock fs operations
|
|
214
137
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
215
|
-
vi.mocked(access).mockRejectedValue({ code: 'ENOENT' });
|
|
138
|
+
vi.mocked(access).mockRejectedValue({ code: 'ENOENT' });
|
|
216
139
|
vi.mocked(fs.readFile).mockResolvedValue('template content');
|
|
217
140
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
218
141
|
vi.mocked(fs.symlink).mockResolvedValue(undefined);
|
|
219
142
|
});
|
|
220
|
-
it('should
|
|
143
|
+
it('should create exactly 3 outputs: AGENTS.md, settings.json, and CLAUDE.md symlink', async () => {
|
|
221
144
|
await initializeAgentConfig({
|
|
222
145
|
projectRoot: '/test/project',
|
|
223
|
-
force: false
|
|
224
|
-
agentProvider: 'claude-code'
|
|
146
|
+
force: false
|
|
225
147
|
});
|
|
226
|
-
|
|
227
|
-
expect(fs.
|
|
228
|
-
|
|
229
|
-
expect(fs.
|
|
148
|
+
expect(fs.mkdir).toHaveBeenCalledTimes(2);
|
|
149
|
+
expect(fs.mkdir).toHaveBeenCalledWith('/test/project/.outputai', expect.objectContaining({ recursive: true }));
|
|
150
|
+
expect(fs.mkdir).toHaveBeenCalledWith('/test/project/.claude', expect.objectContaining({ recursive: true }));
|
|
151
|
+
expect(fs.writeFile).toHaveBeenCalledWith('/test/project/.outputai/AGENTS.md', expect.any(String), 'utf-8');
|
|
152
|
+
expect(fs.writeFile).toHaveBeenCalledWith('/test/project/.claude/settings.json', expect.any(String), 'utf-8');
|
|
153
|
+
expect(fs.symlink).toHaveBeenCalledTimes(1);
|
|
230
154
|
});
|
|
231
155
|
it('should skip existing files when force is false', async () => {
|
|
232
|
-
|
|
233
|
-
vi.mocked(access).mockImplementation(async (path, _mode) => {
|
|
234
|
-
const pathStr = path.toString();
|
|
235
|
-
if (pathStr.includes('AGENTS.md')) {
|
|
236
|
-
return; // File exists
|
|
237
|
-
}
|
|
238
|
-
throw { code: 'ENOENT' }; // File doesn't exist
|
|
239
|
-
});
|
|
156
|
+
vi.mocked(access).mockResolvedValue(undefined);
|
|
240
157
|
await initializeAgentConfig({
|
|
241
158
|
projectRoot: '/test/project',
|
|
242
|
-
force: false
|
|
243
|
-
agentProvider: 'claude-code'
|
|
159
|
+
force: false
|
|
244
160
|
});
|
|
245
|
-
|
|
246
|
-
expect(fs.
|
|
247
|
-
const writeFileCalls = vi.mocked(fs.writeFile).mock.calls;
|
|
248
|
-
const agentsWriteCalls = writeFileCalls.filter(call => call[0].toString().includes('AGENTS.md'));
|
|
249
|
-
expect(agentsWriteCalls).toHaveLength(0);
|
|
161
|
+
expect(fs.writeFile).not.toHaveBeenCalled();
|
|
162
|
+
expect(fs.symlink).not.toHaveBeenCalled();
|
|
250
163
|
});
|
|
251
164
|
it('should overwrite existing files when force is true', async () => {
|
|
252
|
-
// Mock all files exist
|
|
253
165
|
vi.mocked(access).mockResolvedValue(undefined);
|
|
166
|
+
vi.mocked(fs.unlink).mockResolvedValue(undefined);
|
|
254
167
|
await initializeAgentConfig({
|
|
255
168
|
projectRoot: '/test/project',
|
|
256
|
-
force: true
|
|
257
|
-
agentProvider: 'claude-code'
|
|
169
|
+
force: true
|
|
258
170
|
});
|
|
259
|
-
// Should still write files
|
|
260
171
|
expect(fs.writeFile).toHaveBeenCalled();
|
|
261
172
|
});
|
|
262
|
-
it('should create files and output to stdout', async () => {
|
|
263
|
-
await initializeAgentConfig({
|
|
264
|
-
projectRoot: '/test/project',
|
|
265
|
-
force: false,
|
|
266
|
-
agentProvider: 'claude-code'
|
|
267
|
-
});
|
|
268
|
-
// Should create files
|
|
269
|
-
expect(fs.writeFile).toHaveBeenCalled();
|
|
270
|
-
expect(fs.symlink).toHaveBeenCalled();
|
|
271
|
-
});
|
|
272
|
-
it('should create directories for nested files', async () => {
|
|
273
|
-
await initializeAgentConfig({
|
|
274
|
-
projectRoot: '/test/project',
|
|
275
|
-
force: false,
|
|
276
|
-
agentProvider: 'claude-code'
|
|
277
|
-
});
|
|
278
|
-
// Should create directories like .outputai/agents, .outputai/commands
|
|
279
|
-
expect(fs.mkdir).toHaveBeenCalledWith(expect.stringContaining('agents'), expect.objectContaining({ recursive: true }));
|
|
280
|
-
});
|
|
281
173
|
it('should handle symlink errors by falling back to copy', async () => {
|
|
282
174
|
vi.mocked(fs.symlink).mockRejectedValue({ code: 'ENOTSUP' });
|
|
283
175
|
await initializeAgentConfig({
|
|
284
176
|
projectRoot: '/test/project',
|
|
285
|
-
force: false
|
|
286
|
-
agentProvider: 'claude-code'
|
|
177
|
+
force: false
|
|
287
178
|
});
|
|
288
|
-
// Should fall back to copying via writeFile
|
|
289
|
-
expect(fs.writeFile).toHaveBeenCalled();
|
|
290
179
|
const writeFileCalls = vi.mocked(fs.writeFile).mock.calls;
|
|
291
|
-
const
|
|
292
|
-
expect(
|
|
180
|
+
const claudeMdCalls = writeFileCalls.filter(call => call[0].toString().includes('CLAUDE.md'));
|
|
181
|
+
expect(claudeMdCalls.length).toBe(1);
|
|
293
182
|
});
|
|
294
183
|
});
|
|
295
|
-
describe('
|
|
184
|
+
describe('ensureOutputAISystem', () => {
|
|
296
185
|
beforeEach(() => {
|
|
297
|
-
// Mock fs operations
|
|
298
186
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
299
187
|
vi.mocked(access).mockRejectedValue({ code: 'ENOENT' });
|
|
300
188
|
vi.mocked(fs.readFile).mockResolvedValue('template content');
|
|
@@ -302,113 +190,36 @@ describe('coding_agents service', () => {
|
|
|
302
190
|
vi.mocked(fs.symlink).mockResolvedValue(undefined);
|
|
303
191
|
});
|
|
304
192
|
it('should return immediately when agent structure is complete', async () => {
|
|
305
|
-
// Mock complete structure
|
|
306
193
|
vi.mocked(access).mockResolvedValue(undefined);
|
|
307
|
-
|
|
308
|
-
|
|
194
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
|
|
195
|
+
extraKnownMarketplaces: {
|
|
196
|
+
'team-tools': {
|
|
197
|
+
source: { source: 'github', repo: 'growthxai/output-claude-plugins' }
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
enabledPlugins: { 'outputai@outputai': true }
|
|
201
|
+
}));
|
|
202
|
+
await ensureOutputAISystem('/test/project');
|
|
309
203
|
expect(fs.mkdir).not.toHaveBeenCalled();
|
|
310
|
-
expect(fs.writeFile).not.toHaveBeenCalled();
|
|
311
204
|
});
|
|
312
205
|
it('should auto-initialize when directory does not exist', async () => {
|
|
313
|
-
// Mock directory doesn't exist
|
|
314
206
|
vi.mocked(access).mockRejectedValue({ code: 'ENOENT' });
|
|
315
|
-
await
|
|
316
|
-
// Should call init functions to create structure
|
|
207
|
+
await ensureOutputAISystem('/test/project');
|
|
317
208
|
expect(fs.mkdir).toHaveBeenCalled();
|
|
318
209
|
expect(fs.writeFile).toHaveBeenCalled();
|
|
319
210
|
});
|
|
320
|
-
it('should
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (callNum === 2) {
|
|
332
|
-
return undefined; // First file exists
|
|
333
|
-
}
|
|
334
|
-
throw { code: 'ENOENT' }; // Rest are missing
|
|
335
|
-
});
|
|
336
|
-
vi.mocked(confirm).mockResolvedValue(true);
|
|
337
|
-
await ensureOutputAIStructure('/test/project');
|
|
338
|
-
// Should warn about missing files
|
|
339
|
-
expect(ux.warn).toHaveBeenCalledWith(expect.stringContaining('Agent configuration is incomplete'));
|
|
340
|
-
// Should prompt user
|
|
341
|
-
expect(confirm).toHaveBeenCalledWith({
|
|
342
|
-
message: 'Would you like to run "agents init --force" to recreate missing files?',
|
|
343
|
-
default: true
|
|
344
|
-
});
|
|
345
|
-
// Should reinitialize with force=true
|
|
346
|
-
expect(fs.writeFile).toHaveBeenCalled();
|
|
347
|
-
});
|
|
348
|
-
it('should throw error when user declines to reinitialize', async () => {
|
|
349
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
350
|
-
// Mock directory exists but files missing
|
|
351
|
-
const calls = [];
|
|
352
|
-
vi.mocked(access).mockImplementation(async () => {
|
|
353
|
-
const callNum = calls.length + 1;
|
|
354
|
-
calls.push(callNum);
|
|
355
|
-
if (callNum === 1) {
|
|
356
|
-
return undefined; // Directory exists
|
|
357
|
-
}
|
|
358
|
-
throw { code: 'ENOENT' }; // Files are missing
|
|
359
|
-
});
|
|
360
|
-
vi.mocked(confirm).mockResolvedValue(false);
|
|
361
|
-
await expect(ensureOutputAIStructure('/test/project')).rejects.toThrow('Agent configuration incomplete');
|
|
362
|
-
// Should not call init functions
|
|
363
|
-
expect(fs.mkdir).not.toHaveBeenCalled();
|
|
364
|
-
expect(fs.writeFile).not.toHaveBeenCalled();
|
|
365
|
-
});
|
|
366
|
-
it('should list all missing files in error message when user declines', async () => {
|
|
367
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
368
|
-
// Mock directory exists but all files missing
|
|
369
|
-
const calls = [];
|
|
370
|
-
vi.mocked(access).mockImplementation(async () => {
|
|
371
|
-
const callNum = calls.length + 1;
|
|
372
|
-
calls.push(callNum);
|
|
373
|
-
if (callNum === 1) {
|
|
374
|
-
return undefined; // Directory exists
|
|
375
|
-
}
|
|
376
|
-
throw { code: 'ENOENT' }; // All files missing
|
|
377
|
-
});
|
|
378
|
-
vi.mocked(confirm).mockResolvedValue(false);
|
|
379
|
-
try {
|
|
380
|
-
await ensureOutputAIStructure('/test/project');
|
|
381
|
-
expect.fail('Should have thrown an error');
|
|
382
|
-
}
|
|
383
|
-
catch (error) {
|
|
384
|
-
const message = getErrorMessage(error);
|
|
385
|
-
expect(message).toContain('.outputai/AGENTS.md');
|
|
386
|
-
expect(message).toContain('.outputai/agents/workflow_planner.md');
|
|
387
|
-
expect(message).toContain('Run "output-cli agents init --force"');
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
it('should call initializeAgentConfig with force=false when dir does not exist', async () => {
|
|
391
|
-
vi.mocked(access).mockRejectedValue({ code: 'ENOENT' });
|
|
392
|
-
await ensureOutputAIStructure('/test/project');
|
|
393
|
-
// Should have called mkdir (part of init with force=false)
|
|
211
|
+
it('should auto-initialize when settings.json is invalid', async () => {
|
|
212
|
+
vi.mocked(access).mockResolvedValue(undefined);
|
|
213
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
|
|
214
|
+
extraKnownMarketplaces: {
|
|
215
|
+
'team-tools': {
|
|
216
|
+
source: { source: 'github', repo: 'wrong/repo' }
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
enabledPlugins: { 'outputai@outputai': true }
|
|
220
|
+
}));
|
|
221
|
+
await ensureOutputAISystem('/test/project');
|
|
394
222
|
expect(fs.mkdir).toHaveBeenCalled();
|
|
395
223
|
});
|
|
396
|
-
it('should call initializeAgentConfig with force=true after user confirmation', async () => {
|
|
397
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
398
|
-
// Mock directory exists but files missing
|
|
399
|
-
const calls = [];
|
|
400
|
-
vi.mocked(access).mockImplementation(async () => {
|
|
401
|
-
const callNum = calls.length + 1;
|
|
402
|
-
calls.push(callNum);
|
|
403
|
-
if (callNum === 1) {
|
|
404
|
-
return undefined; // Directory exists
|
|
405
|
-
}
|
|
406
|
-
throw { code: 'ENOENT' }; // Files missing
|
|
407
|
-
});
|
|
408
|
-
vi.mocked(confirm).mockResolvedValue(true);
|
|
409
|
-
await ensureOutputAIStructure('/test/project');
|
|
410
|
-
// Should have called writeFile (part of init with force=true)
|
|
411
|
-
expect(fs.writeFile).toHaveBeenCalled();
|
|
412
|
-
});
|
|
413
224
|
});
|
|
414
225
|
});
|
|
@@ -9,7 +9,7 @@ import { getSDKVersions } from '#utils/sdk_versions.js';
|
|
|
9
9
|
import { getErrorMessage, getErrorCode } from '#utils/error_utils.js';
|
|
10
10
|
import { configureEnvironmentVariables } from './env_configurator.js';
|
|
11
11
|
import { getTemplateFiles, processTemplateFile } from './template_processor.js';
|
|
12
|
-
import {
|
|
12
|
+
import { ensureOutputAISystem } from './coding_agents.js';
|
|
13
13
|
import { getProjectSuccessMessage } from './messages.js';
|
|
14
14
|
const getProjectConfig = async () => {
|
|
15
15
|
try {
|
|
@@ -59,7 +59,7 @@ async function executeNpmInstall(projectPath) {
|
|
|
59
59
|
await executeCommand('npm', ['install'], projectPath);
|
|
60
60
|
}
|
|
61
61
|
async function initializeAgents(projectPath) {
|
|
62
|
-
await
|
|
62
|
+
await ensureOutputAISystem(projectPath);
|
|
63
63
|
}
|
|
64
64
|
function extractProjectPath(error) {
|
|
65
65
|
if (error instanceof FolderAlreadyExistsError) {
|
|
@@ -144,7 +144,7 @@ export async function runInit(log, _warn, skipEnv = false) {
|
|
|
144
144
|
if (envConfigured) {
|
|
145
145
|
log('🔐 Environment variables configured in .env');
|
|
146
146
|
}
|
|
147
|
-
await executeCommandWithMessages(() => initializeAgents(config.projectPath), '🤖 Initializing agent system...', '✅ Agent system initialized
|
|
147
|
+
await executeCommandWithMessages(() => initializeAgents(config.projectPath), '🤖 Initializing agent system...', '✅ Agent system initialized');
|
|
148
148
|
const installSuccess = await executeCommandWithMessages(() => executeNpmInstall(config.projectPath), '📦 Installing dependencies...', '✅ Dependencies installed');
|
|
149
149
|
const nextSteps = getProjectSuccessMessage(config.folderName, installSuccess, envConfigured);
|
|
150
150
|
log('✅ Project created successfully!');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build a workflow from a plan file using the /build_workflow slash command
|
|
2
|
+
* Build a workflow from a plan file using the /outputai:build_workflow slash command
|
|
3
3
|
* @param planFilePath - Absolute path to the plan file
|
|
4
4
|
* @param workflowDir - Absolute path to the workflow directory
|
|
5
5
|
* @param workflowName - Name of the workflow
|
|
@@ -31,7 +31,7 @@ function isEmpty(modification) {
|
|
|
31
31
|
return modification.trim() === '';
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Build a workflow from a plan file using the /build_workflow slash command
|
|
34
|
+
* Build a workflow from a plan file using the /outputai:build_workflow slash command
|
|
35
35
|
* @param planFilePath - Absolute path to the plan file
|
|
36
36
|
* @param workflowDir - Absolute path to the workflow directory
|
|
37
37
|
* @param workflowName - Name of the workflow
|
|
@@ -75,7 +75,7 @@ describe('workflow-planner service', () => {
|
|
|
75
75
|
it('should handle UTF-8 content with special characters', async () => {
|
|
76
76
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
77
77
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
78
|
-
const content = '# Plan
|
|
78
|
+
const content = '# Plan\n\nSpecial characters: quotes and dashes';
|
|
79
79
|
await writePlanFile('unicode_test', content, '/test');
|
|
80
80
|
expect(fs.writeFile).toHaveBeenCalledWith(expect.any(String), content, 'utf-8');
|
|
81
81
|
});
|
|
@@ -95,11 +95,10 @@ describe('workflow-planner service', () => {
|
|
|
95
95
|
it('should invoke agents init with force flag', async () => {
|
|
96
96
|
vi.mocked(initializeAgentConfig).mockResolvedValue();
|
|
97
97
|
await updateAgentTemplates('/test/project');
|
|
98
|
-
expect(initializeAgentConfig).toHaveBeenCalledWith(
|
|
98
|
+
expect(initializeAgentConfig).toHaveBeenCalledWith({
|
|
99
99
|
projectRoot: '/test/project',
|
|
100
|
-
force: true
|
|
101
|
-
|
|
102
|
-
}));
|
|
100
|
+
force: true
|
|
101
|
+
});
|
|
103
102
|
});
|
|
104
103
|
it('should propagate errors from agents init', async () => {
|
|
105
104
|
vi.mocked(initializeAgentConfig).mockRejectedValue(new Error('Failed to write templates'));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebFetch",
|
|
5
|
+
"Bash(npx output:*)",
|
|
6
|
+
"Bash(npm run dev)",
|
|
7
|
+
"Bash(npm run output:*)",
|
|
8
|
+
"Skills(output*)",
|
|
9
|
+
"Skills(flow*)"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"enabledPlugins": {
|
|
13
|
+
"outputai@outputai": true
|
|
14
|
+
},
|
|
15
|
+
"extraKnownMarketplaces": {
|
|
16
|
+
"team-tools": {
|
|
17
|
+
"source": {
|
|
18
|
+
"source": "github",
|
|
19
|
+
"repo": "growthxai/output-claude-plugins"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"companyAnnouncements": [
|
|
24
|
+
"Output.ai: Stay up to date with the latest Output.ai announcements and updates at https://output.ai",
|
|
25
|
+
"Output.ai: Ensure the latest version of the Output.ai CLI is installed and up to date with `npm update @output.ai/cli`",
|
|
26
|
+
"Output.ai: Learn more about how to use the Output Framework at https://docs.output.ai/",
|
|
27
|
+
"Output.ai: Ensure your Claude Code Plugins are up to date with `/plugin marketplace update outputai` and `/plugin install @outputai@outputai`"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/dist/templates/agent_instructions/{AGENTS.md.template → dotoutputai/AGENTS.md.template}
RENAMED
|
@@ -205,21 +205,23 @@ throw new FatalError('Critical failure - do not retry');
|
|
|
205
205
|
throw new ValidationError('Invalid input format');
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
## Sub-Agents
|
|
208
|
+
## Claude Code Sub-Agents
|
|
209
209
|
|
|
210
210
|
For workflow planning and implementation:
|
|
211
|
-
- workflow-planner: `.claude/agents/workflow_planner.md` - Workflow architecture specialist
|
|
212
|
-
- workflow-quality: `.claude/agents/workflow_quality.md` - Workflow quality and best practices specialist
|
|
213
|
-
- workflow-prompt-writer: `.claude/agents/workflow_prompt_writer.md` - Prompt file creation and review specialist
|
|
214
|
-
- workflow-context-fetcher: `.claude/agents/workflow_context_fetcher.md` - Efficient context retrieval (used by other agents)
|
|
215
|
-
- workflow-debugger: `.claude/agents/workflow_debugger.md` - Workflow debugging specialist
|
|
216
211
|
|
|
217
|
-
|
|
212
|
+
- workflow-planner: Workflow architecture specialist
|
|
213
|
+
- workflow-quality: Workflow quality and best practices specialist
|
|
214
|
+
- workflow-prompt-writer: Prompt file creation and review specialist
|
|
215
|
+
- workflow-context-fetcher: Efficient context retrieval (used by other agents)
|
|
216
|
+
- workflow-debugger: Workflow debugging specialist
|
|
217
|
+
|
|
218
|
+
## Claude Code Commands
|
|
218
219
|
|
|
219
220
|
For workflow planning and implementation:
|
|
220
|
-
|
|
221
|
-
- /
|
|
222
|
-
- /
|
|
221
|
+
|
|
222
|
+
- /outputai:plan_workflow: Workflow Planning command
|
|
223
|
+
- /outputai:build_workflow: Workflow Implementation command
|
|
224
|
+
- /outputai:debug_workflow: Workflow Debugging command
|
|
223
225
|
|
|
224
226
|
## Configuration
|
|
225
227
|
|