s9n-devops-agent 1.0.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/LICENSE +21 -0
- package/README.md +318 -0
- package/bin/cs-devops-agent +151 -0
- package/cleanup-sessions.sh +70 -0
- package/docs/PROJECT_INFO.md +115 -0
- package/docs/RELEASE_NOTES.md +189 -0
- package/docs/SESSION_MANAGEMENT.md +120 -0
- package/docs/TESTING.md +331 -0
- package/docs/houserules.md +267 -0
- package/docs/infrastructure.md +68 -0
- package/docs/testing-guide.md +224 -0
- package/package.json +68 -0
- package/src/agent-commands.js +211 -0
- package/src/claude-session-manager.js +488 -0
- package/src/close-session.js +316 -0
- package/src/cs-devops-agent-worker.js +1660 -0
- package/src/run-with-agent.js +372 -0
- package/src/session-coordinator.js +1207 -0
- package/src/setup-cs-devops-agent.js +985 -0
- package/src/worktree-manager.js +768 -0
- package/start-devops-session.sh +299 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* RUN WITH AGENT - Helper script to run CS_DevOpsAgent with agent worktrees
|
|
6
|
+
* ============================================================================
|
|
7
|
+
*
|
|
8
|
+
* This script simplifies running CS_DevOpsAgent with automatic worktree creation
|
|
9
|
+
* for different AI agents. It sets up the necessary environment variables
|
|
10
|
+
* and launches the cs-devops-agent worker in the appropriate context.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node run-with-agent.js --agent claude --repo /path/to/repo --task feature-x
|
|
14
|
+
* node run-with-agent.js --agent copilot --repo ../my-project
|
|
15
|
+
* node run-with-agent.js --detect --repo /path/to/repo
|
|
16
|
+
*
|
|
17
|
+
* Options:
|
|
18
|
+
* --agent <name> Specify the agent name (claude, copilot, cursor, aider, custom)
|
|
19
|
+
* --repo <path> Path to the target repository
|
|
20
|
+
* --task <name> Task or feature name (default: development)
|
|
21
|
+
* --detect Auto-detect agent from environment
|
|
22
|
+
* --no-worktree Disable worktree creation (work directly in repo)
|
|
23
|
+
* --list List all active agent worktrees
|
|
24
|
+
* ============================================================================
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import fs from 'fs';
|
|
28
|
+
import path from 'path';
|
|
29
|
+
import { spawn, execSync } from 'child_process';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
import { dirname } from 'path';
|
|
32
|
+
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = dirname(__filename);
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// CONFIGURATION
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
const KNOWN_AGENTS = {
|
|
41
|
+
claude: {
|
|
42
|
+
envVars: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
|
|
43
|
+
msgFile: '.claude-commit-msg',
|
|
44
|
+
color: '\x1b[35m' // Magenta
|
|
45
|
+
},
|
|
46
|
+
copilot: {
|
|
47
|
+
envVars: ['COPILOT_API_KEY', 'GITHUB_COPILOT_ENABLED'],
|
|
48
|
+
msgFile: '.copilot-commit-msg',
|
|
49
|
+
color: '\x1b[36m' // Cyan
|
|
50
|
+
},
|
|
51
|
+
cursor: {
|
|
52
|
+
envVars: ['CURSOR_API_KEY', 'CURSOR_ENABLED'],
|
|
53
|
+
msgFile: '.cursor-commit-msg',
|
|
54
|
+
color: '\x1b[34m' // Blue
|
|
55
|
+
},
|
|
56
|
+
aider: {
|
|
57
|
+
envVars: ['AIDER_API_KEY', 'OPENAI_API_KEY'],
|
|
58
|
+
msgFile: '.aider-commit-msg',
|
|
59
|
+
color: '\x1b[33m' // Yellow
|
|
60
|
+
},
|
|
61
|
+
warp: {
|
|
62
|
+
envVars: ['WARP_API_KEY', 'WARP_ENABLED'],
|
|
63
|
+
msgFile: '.warp-commit-msg',
|
|
64
|
+
color: '\x1b[32m' // Green
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const RESET_COLOR = '\x1b[0m';
|
|
69
|
+
const BRIGHT = '\x1b[1m';
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// UTILITY FUNCTIONS
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
function log(agent, msg) {
|
|
76
|
+
const config = KNOWN_AGENTS[agent] || { color: '\x1b[37m' };
|
|
77
|
+
console.log(`${config.color}${BRIGHT}[${agent.toUpperCase()}]${RESET_COLOR} ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function error(msg) {
|
|
81
|
+
console.error(`\x1b[31m[ERROR]${RESET_COLOR} ${msg}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function detectAgent() {
|
|
86
|
+
// Check environment variables for agent indicators
|
|
87
|
+
for (const [agent, config] of Object.entries(KNOWN_AGENTS)) {
|
|
88
|
+
for (const envVar of config.envVars) {
|
|
89
|
+
if (process.env[envVar]) {
|
|
90
|
+
return agent;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check for agent-specific tools in PATH
|
|
96
|
+
try {
|
|
97
|
+
execSync('which copilot', { stdio: 'pipe' });
|
|
98
|
+
return 'copilot';
|
|
99
|
+
} catch {}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
execSync('which cursor', { stdio: 'pipe' });
|
|
103
|
+
return 'cursor';
|
|
104
|
+
} catch {}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
execSync('which aider', { stdio: 'pipe' });
|
|
108
|
+
return 'aider';
|
|
109
|
+
} catch {}
|
|
110
|
+
|
|
111
|
+
// Check terminal environment
|
|
112
|
+
if (process.env.TERM_PROGRAM === 'WarpTerminal') {
|
|
113
|
+
return 'warp';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseArgs() {
|
|
120
|
+
const args = process.argv.slice(2);
|
|
121
|
+
const options = {
|
|
122
|
+
agent: null,
|
|
123
|
+
repo: null,
|
|
124
|
+
task: 'development',
|
|
125
|
+
detect: false,
|
|
126
|
+
noWorktree: false,
|
|
127
|
+
list: false,
|
|
128
|
+
help: false
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < args.length; i++) {
|
|
132
|
+
switch (args[i]) {
|
|
133
|
+
case '--agent':
|
|
134
|
+
case '-a':
|
|
135
|
+
options.agent = args[++i];
|
|
136
|
+
break;
|
|
137
|
+
case '--repo':
|
|
138
|
+
case '-r':
|
|
139
|
+
options.repo = args[++i];
|
|
140
|
+
break;
|
|
141
|
+
case '--task':
|
|
142
|
+
case '-t':
|
|
143
|
+
options.task = args[++i];
|
|
144
|
+
break;
|
|
145
|
+
case '--detect':
|
|
146
|
+
case '-d':
|
|
147
|
+
options.detect = true;
|
|
148
|
+
break;
|
|
149
|
+
case '--no-worktree':
|
|
150
|
+
options.noWorktree = true;
|
|
151
|
+
break;
|
|
152
|
+
case '--list':
|
|
153
|
+
case '-l':
|
|
154
|
+
options.list = true;
|
|
155
|
+
break;
|
|
156
|
+
case '--help':
|
|
157
|
+
case '-h':
|
|
158
|
+
options.help = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return options;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function showHelp() {
|
|
167
|
+
console.log(`
|
|
168
|
+
${BRIGHT}Run CS_DevOpsAgent with Agent Worktrees${RESET_COLOR}
|
|
169
|
+
|
|
170
|
+
Usage:
|
|
171
|
+
node run-with-agent.js --agent <name> --repo <path> [options]
|
|
172
|
+
node run-with-agent.js --detect --repo <path> [options]
|
|
173
|
+
|
|
174
|
+
Options:
|
|
175
|
+
-a, --agent <name> Specify the agent name (claude, copilot, cursor, aider, warp, custom)
|
|
176
|
+
-r, --repo <path> Path to the target repository
|
|
177
|
+
-t, --task <name> Task or feature name (default: development)
|
|
178
|
+
-d, --detect Auto-detect agent from environment
|
|
179
|
+
--no-worktree Disable worktree creation (work directly in repo)
|
|
180
|
+
-l, --list List all active agent worktrees
|
|
181
|
+
-h, --help Show this help message
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
# Run with specific agent
|
|
185
|
+
node run-with-agent.js --agent claude --repo /path/to/repo --task auth-feature
|
|
186
|
+
|
|
187
|
+
# Auto-detect agent
|
|
188
|
+
node run-with-agent.js --detect --repo ../my-project
|
|
189
|
+
|
|
190
|
+
# List active worktrees
|
|
191
|
+
node run-with-agent.js --list --repo /path/to/repo
|
|
192
|
+
|
|
193
|
+
# Run without worktree
|
|
194
|
+
node run-with-agent.js --agent copilot --repo . --no-worktree
|
|
195
|
+
|
|
196
|
+
Known Agents:
|
|
197
|
+
${Object.keys(KNOWN_AGENTS).join(', ')}
|
|
198
|
+
|
|
199
|
+
Environment Variables:
|
|
200
|
+
AGENT_NAME Override agent name
|
|
201
|
+
AGENT_TASK Override task name
|
|
202
|
+
AC_USE_WORKTREE Enable/disable worktrees (true/false/auto)
|
|
203
|
+
`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function listWorktrees(repoPath) {
|
|
207
|
+
try {
|
|
208
|
+
process.chdir(repoPath);
|
|
209
|
+
const result = execSync('git worktree list', { encoding: 'utf8' });
|
|
210
|
+
|
|
211
|
+
console.log(`\n${BRIGHT}Active Worktrees in ${repoPath}:${RESET_COLOR}\n`);
|
|
212
|
+
|
|
213
|
+
const lines = result.split('\n').filter(l => l);
|
|
214
|
+
for (const line of lines) {
|
|
215
|
+
const match = line.match(/(.+?)\s+([a-f0-9]+)\s+\[(.+?)\]/);
|
|
216
|
+
if (match) {
|
|
217
|
+
const [, path, commit, branch] = match;
|
|
218
|
+
const isAgent = branch.includes('agent/');
|
|
219
|
+
|
|
220
|
+
if (isAgent) {
|
|
221
|
+
const agentMatch = branch.match(/agent\/([^\/]+)\//);
|
|
222
|
+
const agent = agentMatch ? agentMatch[1] : 'unknown';
|
|
223
|
+
const config = KNOWN_AGENTS[agent] || { color: '\x1b[37m' };
|
|
224
|
+
|
|
225
|
+
console.log(`${config.color}[${agent.toUpperCase()}]${RESET_COLOR}`);
|
|
226
|
+
console.log(` Path: ${path}`);
|
|
227
|
+
console.log(` Branch: ${branch}`);
|
|
228
|
+
console.log(` Commit: ${commit}`);
|
|
229
|
+
console.log('');
|
|
230
|
+
} else if (path === repoPath) {
|
|
231
|
+
console.log(`${BRIGHT}[MAIN]${RESET_COLOR}`);
|
|
232
|
+
console.log(` Path: ${path}`);
|
|
233
|
+
console.log(` Branch: ${branch}`);
|
|
234
|
+
console.log(` Commit: ${commit}`);
|
|
235
|
+
console.log('');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for agent configs
|
|
241
|
+
const worktreesDir = path.join(repoPath, '.worktrees');
|
|
242
|
+
if (fs.existsSync(worktreesDir)) {
|
|
243
|
+
const agentsFile = path.join(worktreesDir, 'agents.json');
|
|
244
|
+
if (fs.existsSync(agentsFile)) {
|
|
245
|
+
const agents = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
246
|
+
console.log(`${BRIGHT}Registered Agents:${RESET_COLOR}`);
|
|
247
|
+
for (const [agent, data] of Object.entries(agents)) {
|
|
248
|
+
const active = (data.worktrees || []).filter(w => w.status === 'active').length;
|
|
249
|
+
console.log(` ${agent}: ${active} active worktree(s)`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
} catch (err) {
|
|
255
|
+
error(`Failed to list worktrees: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// MAIN EXECUTION
|
|
261
|
+
// ============================================================================
|
|
262
|
+
|
|
263
|
+
async function main() {
|
|
264
|
+
const options = parseArgs();
|
|
265
|
+
|
|
266
|
+
if (options.help) {
|
|
267
|
+
showHelp();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Validate repository path
|
|
272
|
+
if (!options.repo) {
|
|
273
|
+
error('Repository path is required. Use --repo <path>');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const repoPath = path.resolve(options.repo);
|
|
277
|
+
if (!fs.existsSync(repoPath)) {
|
|
278
|
+
error(`Repository path does not exist: ${repoPath}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check if it's a git repository
|
|
282
|
+
try {
|
|
283
|
+
execSync('git rev-parse --git-dir', { cwd: repoPath, stdio: 'pipe' });
|
|
284
|
+
} catch {
|
|
285
|
+
error(`Not a git repository: ${repoPath}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Handle list command
|
|
289
|
+
if (options.list) {
|
|
290
|
+
listWorktrees(repoPath);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Determine agent
|
|
295
|
+
let agent = options.agent;
|
|
296
|
+
if (options.detect && !agent) {
|
|
297
|
+
agent = detectAgent();
|
|
298
|
+
if (!agent) {
|
|
299
|
+
console.log('No agent detected. Using generic agent name.');
|
|
300
|
+
agent = `agent-${Date.now().toString(36)}`;
|
|
301
|
+
} else {
|
|
302
|
+
console.log(`Detected agent: ${agent}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!agent) {
|
|
307
|
+
error('Agent name is required. Use --agent <name> or --detect');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Prepare environment variables
|
|
311
|
+
const env = { ...process.env };
|
|
312
|
+
|
|
313
|
+
env.AGENT_NAME = agent;
|
|
314
|
+
env.AGENT_TASK = options.task;
|
|
315
|
+
env.AI_AGENT = agent; // Alternative env var
|
|
316
|
+
env.AI_TASK = options.task; // Alternative env var
|
|
317
|
+
|
|
318
|
+
if (options.noWorktree) {
|
|
319
|
+
env.AC_USE_WORKTREE = 'false';
|
|
320
|
+
} else {
|
|
321
|
+
env.AC_USE_WORKTREE = 'true';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Set agent-specific message file
|
|
325
|
+
const agentConfig = KNOWN_AGENTS[agent];
|
|
326
|
+
if (agentConfig && agentConfig.msgFile) {
|
|
327
|
+
env.AC_MSG_FILE = agentConfig.msgFile;
|
|
328
|
+
} else {
|
|
329
|
+
env.AC_MSG_FILE = `.${agent}-commit-msg`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Set branch prefix for agent
|
|
333
|
+
env.AC_BRANCH_PREFIX = `agent_${agent}_`;
|
|
334
|
+
|
|
335
|
+
log(agent, `Starting CS_DevOpsAgent worker`);
|
|
336
|
+
log(agent, `Repository: ${repoPath}`);
|
|
337
|
+
log(agent, `Task: ${options.task}`);
|
|
338
|
+
log(agent, `Worktrees: ${options.noWorktree ? 'disabled' : 'enabled'}`);
|
|
339
|
+
log(agent, `Message file: ${env.AC_MSG_FILE}`);
|
|
340
|
+
|
|
341
|
+
// Launch cs-devops-agent worker
|
|
342
|
+
const workerPath = path.join(__dirname, 'cs-devops-agent-worker.js');
|
|
343
|
+
|
|
344
|
+
const worker = spawn('node', [workerPath], {
|
|
345
|
+
cwd: repoPath,
|
|
346
|
+
env,
|
|
347
|
+
stdio: 'inherit'
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Handle graceful shutdown
|
|
351
|
+
process.on('SIGINT', () => {
|
|
352
|
+
log(agent, 'Stopping CS_DevOpsAgent worker...');
|
|
353
|
+
worker.kill('SIGINT');
|
|
354
|
+
setTimeout(() => process.exit(0), 1000);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
process.on('SIGTERM', () => {
|
|
358
|
+
log(agent, 'Stopping CS_DevOpsAgent worker...');
|
|
359
|
+
worker.kill('SIGTERM');
|
|
360
|
+
setTimeout(() => process.exit(0), 1000);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
worker.on('exit', (code) => {
|
|
364
|
+
log(agent, `Worker exited with code: ${code}`);
|
|
365
|
+
process.exit(code);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Run the main function
|
|
370
|
+
main().catch(err => {
|
|
371
|
+
error(`Fatal error: ${err.message}`);
|
|
372
|
+
});
|