s9n-devops-agent 1.2.1 → 1.3.3

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.
@@ -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
+ }