s9n-devops-agent 1.2.0 ā 1.3.1
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/README.md +210 -56
- package/docs/INSTALLATION_GUIDE.md +366 -0
- package/docs/houserules.md +51 -0
- package/package.json +5 -1
- package/src/cs-devops-agent-worker.js +65 -10
- package/src/display-utils.cjs +350 -0
- package/src/file-coordinator.cjs +356 -0
- package/src/file-monitor-enhanced.cjs +338 -0
- package/src/house-rules-manager.js +443 -0
- package/src/session-coordinator.js +289 -15
- package/src/setup-cs-devops-agent.js +5 -3
- package/start-devops-session.sh +139 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced File Monitor for Real-time Conflict Detection
|
|
5
|
+
*
|
|
6
|
+
* Monitors file changes and immediately alerts when:
|
|
7
|
+
* 1. Files are changed without declaration
|
|
8
|
+
* 2. Files are changed that are declared by another agent
|
|
9
|
+
* 3. Provides copy-paste instructions for the coding agent
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const FileCoordinator = require('./file-coordinator.cjs');
|
|
16
|
+
const display = require('./display-utils.cjs');
|
|
17
|
+
|
|
18
|
+
class EnhancedFileMonitor {
|
|
19
|
+
constructor(sessionId, workingDir = process.cwd()) {
|
|
20
|
+
this.sessionId = sessionId;
|
|
21
|
+
this.workingDir = workingDir;
|
|
22
|
+
this.coordinator = new FileCoordinator(sessionId, workingDir);
|
|
23
|
+
|
|
24
|
+
// Track files we've seen changed
|
|
25
|
+
this.changedFiles = new Set();
|
|
26
|
+
this.lastAlertTime = {};
|
|
27
|
+
this.alertCooldown = 30000; // 30 seconds between alerts for same file
|
|
28
|
+
|
|
29
|
+
// Track our declared files
|
|
30
|
+
this.ourDeclaredFiles = new Set();
|
|
31
|
+
this.lastDeclarationCheck = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get current git status and detect changes
|
|
36
|
+
*/
|
|
37
|
+
async detectChanges() {
|
|
38
|
+
try {
|
|
39
|
+
// Get modified files
|
|
40
|
+
const modifiedFiles = execSync('git diff --name-only', {
|
|
41
|
+
cwd: this.workingDir,
|
|
42
|
+
encoding: 'utf8'
|
|
43
|
+
}).trim().split('\n').filter(f => f);
|
|
44
|
+
|
|
45
|
+
const stagedFiles = execSync('git diff --cached --name-only', {
|
|
46
|
+
cwd: this.workingDir,
|
|
47
|
+
encoding: 'utf8'
|
|
48
|
+
}).trim().split('\n').filter(f => f);
|
|
49
|
+
|
|
50
|
+
const allChangedFiles = [...new Set([...modifiedFiles, ...stagedFiles])];
|
|
51
|
+
|
|
52
|
+
// Check for new changes
|
|
53
|
+
for (const file of allChangedFiles) {
|
|
54
|
+
if (!this.changedFiles.has(file)) {
|
|
55
|
+
// New file change detected!
|
|
56
|
+
this.changedFiles.add(file);
|
|
57
|
+
await this.handleFileChange(file);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Clean up files that are no longer changed
|
|
62
|
+
for (const file of this.changedFiles) {
|
|
63
|
+
if (!allChangedFiles.includes(file)) {
|
|
64
|
+
this.changedFiles.delete(file);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return allChangedFiles;
|
|
69
|
+
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// No changes or git error
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handle a newly detected file change
|
|
78
|
+
*/
|
|
79
|
+
async handleFileChange(file) {
|
|
80
|
+
console.log(`š File change detected: ${file}`);
|
|
81
|
+
|
|
82
|
+
// Update our declared files list
|
|
83
|
+
await this.updateOurDeclaredFiles();
|
|
84
|
+
|
|
85
|
+
// Check if this file was declared by us
|
|
86
|
+
if (this.ourDeclaredFiles.has(file)) {
|
|
87
|
+
console.log(`ā
File properly declared by this session`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if file is declared by another agent
|
|
92
|
+
const conflicts = this.coordinator.checkFilesForConflicts([file]);
|
|
93
|
+
|
|
94
|
+
if (conflicts.length > 0) {
|
|
95
|
+
// File is being edited by another agent!
|
|
96
|
+
await this.alertConflict(file, conflicts[0]);
|
|
97
|
+
} else {
|
|
98
|
+
// File was not declared at all!
|
|
99
|
+
await this.alertUndeclared(file);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Update our list of declared files
|
|
105
|
+
*/
|
|
106
|
+
async updateOurDeclaredFiles() {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
|
|
109
|
+
// Only check every 5 seconds to avoid excessive reads
|
|
110
|
+
if (now - this.lastDeclarationCheck < 5000) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.lastDeclarationCheck = now;
|
|
115
|
+
this.ourDeclaredFiles.clear();
|
|
116
|
+
|
|
117
|
+
const ourDeclaration = this.coordinator.findOurDeclaration();
|
|
118
|
+
if (ourDeclaration) {
|
|
119
|
+
try {
|
|
120
|
+
const content = fs.readFileSync(ourDeclaration, 'utf8');
|
|
121
|
+
const declaration = JSON.parse(content);
|
|
122
|
+
(declaration.files || []).forEach(f => this.ourDeclaredFiles.add(f));
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// Invalid declaration
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Alert user about undeclared file edit
|
|
131
|
+
*/
|
|
132
|
+
async alertUndeclared(file) {
|
|
133
|
+
// Check cooldown
|
|
134
|
+
const lastAlert = this.lastAlertTime[file] || 0;
|
|
135
|
+
if (Date.now() - lastAlert < this.alertCooldown) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.lastAlertTime[file] = Date.now();
|
|
140
|
+
|
|
141
|
+
// Orange box for warning
|
|
142
|
+
console.log('\n' + 'š§'.repeat(30));
|
|
143
|
+
console.log('š§ UNDECLARED FILE EDIT DETECTED!');
|
|
144
|
+
console.log('š§'.repeat(30));
|
|
145
|
+
|
|
146
|
+
console.log(`\nā File "${file}" was modified WITHOUT declaration!`);
|
|
147
|
+
console.log('\nš COPY THIS INSTRUCTION TO YOUR CODING AGENT:');
|
|
148
|
+
console.log('ā'.repeat(60));
|
|
149
|
+
|
|
150
|
+
const instruction = `
|
|
151
|
+
STOP! You are editing files without declaring them first.
|
|
152
|
+
|
|
153
|
+
ā ļø YOU MUST FOLLOW THE HOUSE RULES! ā ļø
|
|
154
|
+
Read them now: cat houserules.md
|
|
155
|
+
|
|
156
|
+
File Coordination Protocol (from house rules):
|
|
157
|
+
|
|
158
|
+
1. BEFORE editing "${file}", create this declaration:
|
|
159
|
+
|
|
160
|
+
\`\`\`json
|
|
161
|
+
// Save to: .file-coordination/active-edits/agent-${this.sessionId}.json
|
|
162
|
+
{
|
|
163
|
+
"agent": "YOUR_AGENT_NAME",
|
|
164
|
+
"session": "${this.sessionId}",
|
|
165
|
+
"files": ["${file}"],
|
|
166
|
+
"operation": "edit",
|
|
167
|
+
"reason": "Describe what you're doing",
|
|
168
|
+
"declaredAt": "${new Date().toISOString()}",
|
|
169
|
+
"estimatedDuration": 300
|
|
170
|
+
}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
2. Check if any other agent has declared this file:
|
|
174
|
+
- Look in .file-coordination/active-edits/ for other agents' declarations
|
|
175
|
+
- If the file appears in another declaration, WAIT or choose a different file
|
|
176
|
+
|
|
177
|
+
3. Only proceed with editing after declaring and confirming no conflicts
|
|
178
|
+
|
|
179
|
+
This prevents merge conflicts and wasted work!
|
|
180
|
+
|
|
181
|
+
REMEMBER: Always follow the house rules - cat houserules.md`;
|
|
182
|
+
|
|
183
|
+
console.log(instruction);
|
|
184
|
+
console.log('ā'.repeat(60));
|
|
185
|
+
|
|
186
|
+
// Also save to a file the user can easily access
|
|
187
|
+
const alertFile = path.join(this.workingDir, `.coordination-alert-${Date.now()}.md`);
|
|
188
|
+
fs.writeFileSync(alertFile, `# File Coordination Alert\n\n${instruction}`);
|
|
189
|
+
|
|
190
|
+
console.log(`\nš¾ Full instructions saved to: ${alertFile}`);
|
|
191
|
+
console.log('š§'.repeat(30) + '\n');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Alert user about conflict with another agent
|
|
196
|
+
*/
|
|
197
|
+
async alertConflict(file, conflict) {
|
|
198
|
+
// Check cooldown
|
|
199
|
+
const lastAlert = this.lastAlertTime[file] || 0;
|
|
200
|
+
if (Date.now() - lastAlert < this.alertCooldown) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.lastAlertTime[file] = Date.now();
|
|
205
|
+
|
|
206
|
+
// Red box for conflict
|
|
207
|
+
console.log('\n' + 'š“'.repeat(30));
|
|
208
|
+
console.log('š“ FILE CONFLICT DETECTED!');
|
|
209
|
+
console.log('š“'.repeat(30));
|
|
210
|
+
|
|
211
|
+
console.log(`\nā File "${file}" is being edited by: ${conflict.conflictsWith}`);
|
|
212
|
+
console.log(` Session: ${conflict.session}`);
|
|
213
|
+
console.log(` Since: ${conflict.declaredAt}`);
|
|
214
|
+
console.log(` Reason: ${conflict.reason}`);
|
|
215
|
+
|
|
216
|
+
console.log('\nš COPY THIS INSTRUCTION TO YOUR CODING AGENT:');
|
|
217
|
+
console.log('ā'.repeat(60));
|
|
218
|
+
|
|
219
|
+
const instruction = `
|
|
220
|
+
STOP! File conflict detected.
|
|
221
|
+
|
|
222
|
+
ā ļø YOU MUST FOLLOW THE HOUSE RULES! ā ļø
|
|
223
|
+
Read them now: cat houserules.md
|
|
224
|
+
|
|
225
|
+
The file "${file}" is currently being edited by another agent:
|
|
226
|
+
- Agent: ${conflict.conflictsWith}
|
|
227
|
+
- Session: ${conflict.session}
|
|
228
|
+
- Reason: ${conflict.reason}
|
|
229
|
+
|
|
230
|
+
You have three options:
|
|
231
|
+
|
|
232
|
+
1. WAIT for the other agent to complete and release the file
|
|
233
|
+
2. CHOOSE a different file to edit instead
|
|
234
|
+
3. COORDINATE with the other agent (not recommended)
|
|
235
|
+
|
|
236
|
+
DO NOT continue editing this file as it will cause merge conflicts.
|
|
237
|
+
|
|
238
|
+
To check when the file is available:
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
ls -la .file-coordination/active-edits/
|
|
241
|
+
# Look for when ${conflict.conflictsWith}'s declaration is removed
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
To work on different files instead:
|
|
245
|
+
1. Revert changes to "${file}"
|
|
246
|
+
2. Choose alternative files
|
|
247
|
+
3. Declare the new files before editing them
|
|
248
|
+
|
|
249
|
+
REMEMBER: Always follow the house rules - cat houserules.md`;
|
|
250
|
+
|
|
251
|
+
console.log(instruction);
|
|
252
|
+
console.log('ā'.repeat(60));
|
|
253
|
+
|
|
254
|
+
// Save alert
|
|
255
|
+
const alertFile = path.join(this.workingDir, `.coordination-conflict-${Date.now()}.md`);
|
|
256
|
+
fs.writeFileSync(alertFile, `# File Conflict Alert\n\n${instruction}`);
|
|
257
|
+
|
|
258
|
+
console.log(`\nš¾ Full instructions saved to: ${alertFile}`);
|
|
259
|
+
console.log('š“'.repeat(30) + '\n');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Start monitoring for file changes
|
|
264
|
+
*/
|
|
265
|
+
async startMonitoring(intervalMs = 2000) {
|
|
266
|
+
display.header('FILE MONITOR ACTIVE', `Session: ${this.sessionId}`);
|
|
267
|
+
display.info(`Checking every ${intervalMs/1000} seconds for undeclared changes`);
|
|
268
|
+
display.keyValue('Coordination', 'ENABLED', 0);
|
|
269
|
+
display.keyValue('Alert Mode', 'Orange = Undeclared | Red = Conflict', 0);
|
|
270
|
+
|
|
271
|
+
// Initial check
|
|
272
|
+
await this.detectChanges();
|
|
273
|
+
|
|
274
|
+
// Set up periodic monitoring
|
|
275
|
+
this.monitorInterval = setInterval(async () => {
|
|
276
|
+
await this.detectChanges();
|
|
277
|
+
}, intervalMs);
|
|
278
|
+
|
|
279
|
+
// Also monitor declaration directory for changes
|
|
280
|
+
this.watchDeclarations();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Watch for changes in declarations
|
|
285
|
+
*/
|
|
286
|
+
watchDeclarations() {
|
|
287
|
+
const declDir = path.join(this.workingDir, '.file-coordination/active-edits');
|
|
288
|
+
|
|
289
|
+
// Ensure directory exists
|
|
290
|
+
if (!fs.existsSync(declDir)) {
|
|
291
|
+
fs.mkdirSync(declDir, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Watch for new/removed declarations
|
|
295
|
+
try {
|
|
296
|
+
fs.watch(declDir, async (eventType, filename) => {
|
|
297
|
+
if (filename && filename.endsWith('.json')) {
|
|
298
|
+
console.log(`š Declaration change: ${eventType} ${filename}`);
|
|
299
|
+
|
|
300
|
+
// Re-check our current changed files
|
|
301
|
+
for (const file of this.changedFiles) {
|
|
302
|
+
await this.handleFileChange(file);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
} catch (err) {
|
|
307
|
+
console.error('Could not watch declarations directory:', err.message);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Stop monitoring
|
|
313
|
+
*/
|
|
314
|
+
stopMonitoring() {
|
|
315
|
+
if (this.monitorInterval) {
|
|
316
|
+
clearInterval(this.monitorInterval);
|
|
317
|
+
console.log('ā ļø File monitoring stopped');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Export for use in DevOps agent
|
|
323
|
+
module.exports = EnhancedFileMonitor;
|
|
324
|
+
|
|
325
|
+
// If run directly, start monitoring
|
|
326
|
+
if (require.main === module) {
|
|
327
|
+
const sessionId = process.env.DEVOPS_SESSION_ID || 'manual-monitor';
|
|
328
|
+
const monitor = new EnhancedFileMonitor(sessionId);
|
|
329
|
+
|
|
330
|
+
monitor.startMonitoring(2000);
|
|
331
|
+
|
|
332
|
+
// Handle graceful shutdown
|
|
333
|
+
process.on('SIGINT', () => {
|
|
334
|
+
console.log('\nShutting down monitor...');
|
|
335
|
+
monitor.stopMonitoring();
|
|
336
|
+
process.exit(0);
|
|
337
|
+
});
|
|
338
|
+
}
|