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,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* House Rules Version Manager
|
|
3
|
+
* ============================
|
|
4
|
+
*
|
|
5
|
+
* Manages versioning and updates of DevOps Agent sections in house rules.
|
|
6
|
+
* Allows intelligent updates while preserving user customizations.
|
|
7
|
+
*
|
|
8
|
+
* Each managed section includes:
|
|
9
|
+
* - Version marker: <!-- DEVOPS_AGENT_SECTION:name:version:checksum -->
|
|
10
|
+
* - Content checksum for change detection
|
|
11
|
+
* - End marker: <!-- END_DEVOPS_AGENT_SECTION:name -->
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import crypto from 'crypto';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname } from 'path';
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// Version of our house rules sections
|
|
24
|
+
const HOUSERULES_VERSION = '1.3.0';
|
|
25
|
+
|
|
26
|
+
// Managed sections with their content
|
|
27
|
+
const MANAGED_SECTIONS = {
|
|
28
|
+
'file-coordination': {
|
|
29
|
+
version: '1.3.0',
|
|
30
|
+
title: '## 🚨 CRITICAL: File Coordination Protocol (MUST FOLLOW)',
|
|
31
|
+
content: `**IMPORTANT: Always check the house rules at the beginning of each session!**
|
|
32
|
+
|
|
33
|
+
To prevent conflicts with other agents editing the same files, you MUST follow this protocol:
|
|
34
|
+
|
|
35
|
+
### Before Editing ANY Files:
|
|
36
|
+
|
|
37
|
+
1. **DECLARE YOUR INTENT FIRST**
|
|
38
|
+
Create a file at \`.file-coordination/active-edits/<your-name>-<session>.json\` with:
|
|
39
|
+
\`\`\`json
|
|
40
|
+
{
|
|
41
|
+
"agent": "<your-name>",
|
|
42
|
+
"session": "<session-id>",
|
|
43
|
+
"files": ["list", "of", "files", "you", "will", "edit"],
|
|
44
|
+
"operation": "edit",
|
|
45
|
+
"reason": "Brief description of what you're doing",
|
|
46
|
+
"declaredAt": "<current-ISO-8601-timestamp>",
|
|
47
|
+
"estimatedDuration": 300
|
|
48
|
+
}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
2. **CHECK FOR CONFLICTS**
|
|
52
|
+
- Read ALL files in \`.file-coordination/active-edits/\`
|
|
53
|
+
- If ANY other agent has declared the same files, you must:
|
|
54
|
+
- WAIT for them to finish, OR
|
|
55
|
+
- Choose different files to edit
|
|
56
|
+
|
|
57
|
+
3. **ONLY EDIT DECLARED FILES**
|
|
58
|
+
- Never edit files you haven't declared
|
|
59
|
+
- Stay within your declared scope
|
|
60
|
+
|
|
61
|
+
4. **RELEASE WHEN DONE**
|
|
62
|
+
- Delete your declaration file after completing edits
|
|
63
|
+
- Or move it to \`.file-coordination/completed-edits/\`
|
|
64
|
+
|
|
65
|
+
### If You Detect a Conflict:
|
|
66
|
+
- DO NOT proceed with edits
|
|
67
|
+
- Report the conflict to the user
|
|
68
|
+
- Wait or choose alternative files
|
|
69
|
+
|
|
70
|
+
### Helper Scripts Available:
|
|
71
|
+
- \`./scripts/coordination/check-file-availability.sh <files>\` - Check if files are available
|
|
72
|
+
- \`./scripts/coordination/declare-file-edits.sh <agent> <session> <files>\` - Declare your intent
|
|
73
|
+
- \`./scripts/coordination/release-file-edits.sh <agent> <session>\` - Release files after editing
|
|
74
|
+
|
|
75
|
+
**This coordination prevents wasted work and merge conflicts!**`
|
|
76
|
+
},
|
|
77
|
+
'core-principles': {
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
title: '## Core Principles',
|
|
80
|
+
content: `1. **Always preserve existing functionality** - Never break working code
|
|
81
|
+
2. **Follow existing patterns** - Match the codebase style and conventions
|
|
82
|
+
3. **Communicate clearly** - Document your changes and reasoning
|
|
83
|
+
4. **Coordinate with others** - Follow the file coordination protocol below`
|
|
84
|
+
},
|
|
85
|
+
'project-conventions': {
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
title: '## Project Conventions',
|
|
88
|
+
content: `### Code Style
|
|
89
|
+
- Follow existing indentation and formatting patterns
|
|
90
|
+
- Maintain consistent naming conventions used in the project
|
|
91
|
+
- Keep functions small and focused
|
|
92
|
+
- Write clear, descriptive variable and function names
|
|
93
|
+
|
|
94
|
+
### Git Workflow
|
|
95
|
+
- Write clear, descriptive commit messages
|
|
96
|
+
- Follow conventional commit format when applicable (feat:, fix:, docs:, etc.)
|
|
97
|
+
- Keep commits atomic and focused on a single change
|
|
98
|
+
- Never commit sensitive information or credentials
|
|
99
|
+
|
|
100
|
+
### Testing
|
|
101
|
+
- Write tests for new functionality
|
|
102
|
+
- Ensure existing tests pass before committing
|
|
103
|
+
- Update tests when changing functionality
|
|
104
|
+
|
|
105
|
+
### Documentation
|
|
106
|
+
- Update README files when adding new features
|
|
107
|
+
- Document complex logic with clear comments
|
|
108
|
+
- Keep API documentation up to date
|
|
109
|
+
- Update CHANGELOG for significant changes`
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
class HouseRulesManager {
|
|
114
|
+
constructor(projectRoot) {
|
|
115
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
116
|
+
this.houseRulesPath = null;
|
|
117
|
+
this.findHouseRulesFile();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Find existing house rules file
|
|
122
|
+
*/
|
|
123
|
+
findHouseRulesFile() {
|
|
124
|
+
const possiblePaths = [
|
|
125
|
+
'houserules.md',
|
|
126
|
+
'HOUSERULES.md',
|
|
127
|
+
'.github/HOUSERULES.md',
|
|
128
|
+
'docs/houserules.md',
|
|
129
|
+
'docs/HOUSERULES.md'
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const relativePath of possiblePaths) {
|
|
133
|
+
const fullPath = path.join(this.projectRoot, relativePath);
|
|
134
|
+
if (fs.existsSync(fullPath)) {
|
|
135
|
+
this.houseRulesPath = fullPath;
|
|
136
|
+
return fullPath;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Calculate checksum for content
|
|
145
|
+
*/
|
|
146
|
+
calculateChecksum(content) {
|
|
147
|
+
return crypto.createHash('md5').update(content.trim()).digest('hex').substring(0, 8);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create section marker
|
|
152
|
+
*/
|
|
153
|
+
createSectionMarker(sectionName, version, checksum) {
|
|
154
|
+
return `<!-- DEVOPS_AGENT_SECTION:${sectionName}:${version}:${checksum} -->`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create end marker
|
|
159
|
+
*/
|
|
160
|
+
createEndMarker(sectionName) {
|
|
161
|
+
return `<!-- END_DEVOPS_AGENT_SECTION:${sectionName} -->`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Extract managed sections from existing house rules
|
|
166
|
+
*/
|
|
167
|
+
extractManagedSections(content) {
|
|
168
|
+
const sections = {};
|
|
169
|
+
const pattern = /<!-- DEVOPS_AGENT_SECTION:(\w+[-\w]*):([0-9.]+):([a-f0-9]+) -->([\s\S]*?)<!-- END_DEVOPS_AGENT_SECTION:\1 -->/g;
|
|
170
|
+
|
|
171
|
+
let match;
|
|
172
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
173
|
+
sections[match[1]] = {
|
|
174
|
+
name: match[1],
|
|
175
|
+
version: match[2],
|
|
176
|
+
checksum: match[3],
|
|
177
|
+
content: match[4].trim(),
|
|
178
|
+
startIndex: match.index,
|
|
179
|
+
endIndex: match.index + match[0].length
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return sections;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Check if a section needs updating
|
|
188
|
+
*/
|
|
189
|
+
needsUpdate(sectionName, existingVersion, existingChecksum) {
|
|
190
|
+
const currentSection = MANAGED_SECTIONS[sectionName];
|
|
191
|
+
if (!currentSection) return false;
|
|
192
|
+
|
|
193
|
+
// Check version
|
|
194
|
+
if (this.compareVersions(currentSection.version, existingVersion) > 0) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check checksum (in case we updated content without version bump)
|
|
199
|
+
const currentChecksum = this.calculateChecksum(currentSection.content);
|
|
200
|
+
return currentChecksum !== existingChecksum;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Compare semantic versions
|
|
205
|
+
*/
|
|
206
|
+
compareVersions(v1, v2) {
|
|
207
|
+
const parts1 = v1.split('.').map(Number);
|
|
208
|
+
const parts2 = v2.split('.').map(Number);
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < 3; i++) {
|
|
211
|
+
if (parts1[i] > parts2[i]) return 1;
|
|
212
|
+
if (parts1[i] < parts2[i]) return -1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Format a managed section with markers
|
|
220
|
+
*/
|
|
221
|
+
formatSection(sectionName) {
|
|
222
|
+
const section = MANAGED_SECTIONS[sectionName];
|
|
223
|
+
if (!section) return '';
|
|
224
|
+
|
|
225
|
+
const checksum = this.calculateChecksum(section.content);
|
|
226
|
+
const marker = this.createSectionMarker(sectionName, section.version, checksum);
|
|
227
|
+
const endMarker = this.createEndMarker(sectionName);
|
|
228
|
+
|
|
229
|
+
return `${marker}
|
|
230
|
+
${section.title}
|
|
231
|
+
|
|
232
|
+
${section.content}
|
|
233
|
+
${endMarker}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Create new house rules with all managed sections
|
|
238
|
+
*/
|
|
239
|
+
createNewHouseRules() {
|
|
240
|
+
const sections = [];
|
|
241
|
+
|
|
242
|
+
// Header
|
|
243
|
+
sections.push(`# House Rules for AI Agents
|
|
244
|
+
|
|
245
|
+
**IMPORTANT: All AI agents (Claude, Cline, Copilot, etc.) must read and follow these rules at the start of each session.**
|
|
246
|
+
`);
|
|
247
|
+
|
|
248
|
+
// Add managed sections
|
|
249
|
+
sections.push(this.formatSection('file-coordination'));
|
|
250
|
+
sections.push('');
|
|
251
|
+
sections.push(this.formatSection('core-principles'));
|
|
252
|
+
sections.push('');
|
|
253
|
+
sections.push(this.formatSection('project-conventions'));
|
|
254
|
+
|
|
255
|
+
return sections.join('\n');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Update house rules intelligently
|
|
260
|
+
*/
|
|
261
|
+
async updateHouseRules(options = {}) {
|
|
262
|
+
const { createIfMissing = true, backupExisting = true } = options;
|
|
263
|
+
|
|
264
|
+
// Find or create house rules file
|
|
265
|
+
if (!this.houseRulesPath) {
|
|
266
|
+
if (!createIfMissing) {
|
|
267
|
+
return { updated: false, reason: 'No house rules file found' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.houseRulesPath = path.join(this.projectRoot, 'houserules.md');
|
|
271
|
+
const content = this.createNewHouseRules();
|
|
272
|
+
fs.writeFileSync(this.houseRulesPath, content);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
updated: true,
|
|
276
|
+
created: true,
|
|
277
|
+
path: this.houseRulesPath,
|
|
278
|
+
sections: Object.keys(MANAGED_SECTIONS)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Read existing content
|
|
283
|
+
const existingContent = fs.readFileSync(this.houseRulesPath, 'utf8');
|
|
284
|
+
const existingSections = this.extractManagedSections(existingContent);
|
|
285
|
+
|
|
286
|
+
// Check what needs updating
|
|
287
|
+
const updates = [];
|
|
288
|
+
const additions = [];
|
|
289
|
+
|
|
290
|
+
for (const [sectionName, sectionData] of Object.entries(MANAGED_SECTIONS)) {
|
|
291
|
+
if (existingSections[sectionName]) {
|
|
292
|
+
// Section exists - check if needs update
|
|
293
|
+
if (this.needsUpdate(sectionName, existingSections[sectionName].version, existingSections[sectionName].checksum)) {
|
|
294
|
+
updates.push(sectionName);
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
// Section doesn't exist - needs to be added
|
|
298
|
+
additions.push(sectionName);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// If no updates needed, return
|
|
303
|
+
if (updates.length === 0 && additions.length === 0) {
|
|
304
|
+
return { updated: false, reason: 'All sections are up to date' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Create backup if requested
|
|
308
|
+
if (backupExisting) {
|
|
309
|
+
const backupPath = `${this.houseRulesPath}.backup.${Date.now()}`;
|
|
310
|
+
fs.copyFileSync(this.houseRulesPath, backupPath);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Build new content
|
|
314
|
+
let newContent = existingContent;
|
|
315
|
+
|
|
316
|
+
// Update existing sections
|
|
317
|
+
for (const sectionName of updates) {
|
|
318
|
+
const existingSection = existingSections[sectionName];
|
|
319
|
+
const newSection = this.formatSection(sectionName);
|
|
320
|
+
|
|
321
|
+
// Replace the old section with the new one
|
|
322
|
+
const beforeSection = newContent.substring(0, existingSection.startIndex);
|
|
323
|
+
const afterSection = newContent.substring(existingSection.endIndex);
|
|
324
|
+
newContent = beforeSection + newSection + afterSection;
|
|
325
|
+
|
|
326
|
+
// Recalculate positions for remaining sections
|
|
327
|
+
const diff = newSection.length - (existingSection.endIndex - existingSection.startIndex);
|
|
328
|
+
for (const section of Object.values(existingSections)) {
|
|
329
|
+
if (section.startIndex > existingSection.startIndex) {
|
|
330
|
+
section.startIndex += diff;
|
|
331
|
+
section.endIndex += diff;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Add new sections
|
|
337
|
+
if (additions.length > 0) {
|
|
338
|
+
// Find the best place to insert new sections
|
|
339
|
+
let insertPosition = 0;
|
|
340
|
+
|
|
341
|
+
// Try to insert after the main header
|
|
342
|
+
const headerMatch = /^# .+\n/m.exec(newContent);
|
|
343
|
+
if (headerMatch) {
|
|
344
|
+
insertPosition = headerMatch.index + headerMatch[0].length;
|
|
345
|
+
|
|
346
|
+
// Skip any immediate description
|
|
347
|
+
const descMatch = /\*\*IMPORTANT:.+\*\*\n/m.exec(newContent.substring(insertPosition));
|
|
348
|
+
if (descMatch && descMatch.index === 0) {
|
|
349
|
+
insertPosition += descMatch[0].length;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Add each new section
|
|
354
|
+
const sectionsToAdd = additions.map(name => '\n' + this.formatSection(name) + '\n').join('');
|
|
355
|
+
newContent = newContent.substring(0, insertPosition) + sectionsToAdd + newContent.substring(insertPosition);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Write updated content
|
|
359
|
+
fs.writeFileSync(this.houseRulesPath, newContent);
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
updated: true,
|
|
363
|
+
path: this.houseRulesPath,
|
|
364
|
+
updatedSections: updates,
|
|
365
|
+
addedSections: additions,
|
|
366
|
+
totalChanges: updates.length + additions.length
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get status of house rules
|
|
372
|
+
*/
|
|
373
|
+
getStatus() {
|
|
374
|
+
if (!this.houseRulesPath) {
|
|
375
|
+
return {
|
|
376
|
+
exists: false,
|
|
377
|
+
path: null,
|
|
378
|
+
managedSections: {},
|
|
379
|
+
needsUpdate: true
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const content = fs.readFileSync(this.houseRulesPath, 'utf8');
|
|
384
|
+
const existingSections = this.extractManagedSections(content);
|
|
385
|
+
const status = {
|
|
386
|
+
exists: true,
|
|
387
|
+
path: this.houseRulesPath,
|
|
388
|
+
managedSections: {},
|
|
389
|
+
needsUpdate: false
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Check each managed section
|
|
393
|
+
for (const [sectionName, sectionData] of Object.entries(MANAGED_SECTIONS)) {
|
|
394
|
+
if (existingSections[sectionName]) {
|
|
395
|
+
const existing = existingSections[sectionName];
|
|
396
|
+
const needsUpdate = this.needsUpdate(sectionName, existing.version, existing.checksum);
|
|
397
|
+
|
|
398
|
+
status.managedSections[sectionName] = {
|
|
399
|
+
installed: true,
|
|
400
|
+
installedVersion: existing.version,
|
|
401
|
+
currentVersion: sectionData.version,
|
|
402
|
+
needsUpdate
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
if (needsUpdate) {
|
|
406
|
+
status.needsUpdate = true;
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
status.managedSections[sectionName] = {
|
|
410
|
+
installed: false,
|
|
411
|
+
currentVersion: sectionData.version,
|
|
412
|
+
needsUpdate: true
|
|
413
|
+
};
|
|
414
|
+
status.needsUpdate = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return status;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export default HouseRulesManager;
|
|
423
|
+
|
|
424
|
+
// CLI interface when run directly
|
|
425
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
426
|
+
const manager = new HouseRulesManager();
|
|
427
|
+
const command = process.argv[2];
|
|
428
|
+
|
|
429
|
+
switch (command) {
|
|
430
|
+
case 'status':
|
|
431
|
+
console.log('House Rules Status:', JSON.stringify(manager.getStatus(), null, 2));
|
|
432
|
+
break;
|
|
433
|
+
|
|
434
|
+
case 'update':
|
|
435
|
+
manager.updateHouseRules().then(result => {
|
|
436
|
+
console.log('Update Result:', JSON.stringify(result, null, 2));
|
|
437
|
+
});
|
|
438
|
+
break;
|
|
439
|
+
|
|
440
|
+
default:
|
|
441
|
+
console.log('Usage: node house-rules-manager.js [status|update]');
|
|
442
|
+
}
|
|
443
|
+
}
|