s9n-devops-agent 1.2.1 → 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.
@@ -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
+ }
@@ -461,7 +461,13 @@ class SessionCoordinator {
461
461
  });
462
462
 
463
463
  console.log(`\n${CONFIG.colors.yellow}═══ Auto-merge Configuration ═══${CONFIG.colors.reset}`);
464
- console.log(`${CONFIG.colors.dim}(Automatically merge today's work into a target branch)${CONFIG.colors.reset}`);
464
+ console.log(`${CONFIG.colors.dim}Automatically merge your daily work branches into a target branch.${CONFIG.colors.reset}`);
465
+ console.log();
466
+ console.log(`${CONFIG.colors.bright}How it works:${CONFIG.colors.reset}`);
467
+ console.log(` • The agent creates dated branches (e.g., ${CONFIG.colors.blue}agent_dev_2025-10-01${CONFIG.colors.reset})`);
468
+ console.log(` • At the end of each day, your work is automatically merged`);
469
+ console.log(` • This keeps your target branch (main/develop) up to date`);
470
+ console.log(` • Prevents accumulation of stale feature branches`);
465
471
 
466
472
  // Ask if they want auto-merge
467
473
  const autoMerge = await new Promise((resolve) => {
@@ -784,6 +790,28 @@ INSTRUCTIONS:
784
790
  - **Worktree Path:** \`${worktreePath}\`
785
791
  - **Branch:** \`${branchName}\`
786
792
 
793
+ ## 🚨 CRITICAL: File Coordination Protocol
794
+
795
+ **BEFORE editing any files, you MUST:**
796
+
797
+ 1. **Declare your intent** by creating:
798
+ \`\`\`json
799
+ // File: .file-coordination/active-edits/<agent>-${sessionId}.json
800
+ {
801
+ "agent": "<your-name>",
802
+ "session": "${sessionId}",
803
+ "files": ["list", "files", "to", "edit"],
804
+ "operation": "edit",
805
+ "reason": "${task}",
806
+ "declaredAt": "<ISO-8601-timestamp>",
807
+ "estimatedDuration": 300
808
+ }
809
+ \`\`\`
810
+
811
+ 2. **Check for conflicts** - read all files in \`.file-coordination/active-edits/\`
812
+ 3. **Only proceed if no conflicts** - wait or choose different files if blocked
813
+ 4. **Release files when done** - delete your declaration after edits
814
+
787
815
  ## Instructions for Claude/Cline
788
816
 
789
817
  ### Step 1: Navigate to Your Worktree
@@ -797,18 +825,25 @@ git branch --show-current
797
825
  # Should output: ${branchName}
798
826
  \`\`\`
799
827
 
800
- ### Step 3: Work on Your Task
828
+ ### Step 3: Declare Files Before Editing
829
+ Create your declaration in \`.file-coordination/active-edits/\`
830
+
831
+ ### Step 4: Work on Your Task
801
832
  Make changes for: **${task}**
802
833
 
803
- ### Step 4: Commit Your Changes
834
+ ### Step 5: Commit Your Changes
804
835
  Write your commit message to the session-specific file:
805
836
  \`\`\`bash
806
837
  echo "feat: your commit message here" > .devops-commit-${sessionId}.msg
807
838
  \`\`\`
808
839
 
809
- ### Step 5: Automatic Processing
840
+ ### Step 6: Release Your File Locks
841
+ Delete your declaration from \`.file-coordination/active-edits/\`
842
+
843
+ ### Step 7: Automatic Processing
810
844
  The DevOps agent will automatically:
811
845
  - Detect your changes
846
+ - Check for coordination conflicts
812
847
  - Read your commit message
813
848
  - Commit and push to the remote repository
814
849
  - Clear the message file
@@ -853,11 +888,21 @@ The DevOps agent will automatically:
853
888
  console.log(`- Working Directory: ${instructions.worktreePath}`);
854
889
  console.log(`- Task: ${task || 'development'}`);
855
890
  console.log(``);
856
- console.log(`Please switch to this directory before making any changes:`);
857
- console.log(`cd "${instructions.worktreePath}"`);
891
+ console.log(`CRITICAL FIRST STEP:`);
892
+ console.log(`1. Read and follow the house rules: cat "${instructions.worktreePath}/houserules.md"`);
893
+ console.log(`2. Switch to the working directory: cd "${instructions.worktreePath}"`);
894
+ console.log(``);
895
+ console.log(`FILE COORDINATION PROTOCOL (from house rules at ${instructions.worktreePath}/houserules.md):`);
896
+ console.log(`Before editing ANY files, you MUST:`);
897
+ console.log(`- Declare your intent in .file-coordination/active-edits/<agent>-${sessionId}.json`);
898
+ console.log(`- Check for conflicts with other agents`);
899
+ console.log(`- Only edit files you've declared`);
900
+ console.log(`- Release files when done`);
858
901
  console.log(``);
859
902
  console.log(`Write commit messages to: .devops-commit-${sessionId}.msg`);
860
903
  console.log(`The DevOps agent will automatically commit and push changes.`);
904
+ console.log(``);
905
+ console.log(`Remember: ALWAYS check house rules first for the latest protocols!`);
861
906
  console.log();
862
907
 
863
908
  console.log(`${CONFIG.colors.yellow}══════════════════════════════════════════════════════════════${CONFIG.colors.reset}`);
@@ -735,7 +735,8 @@ export AC_MSG_MIN_BYTES="20"
735
735
  export AC_DEBOUNCE_MS="1500"
736
736
  export AC_MSG_DEBOUNCE_MS="3000"
737
737
  export AC_CLEAR_MSG_WHEN="push"
738
- export AC_ROLLOVER_PROMPT="true"
738
+ # Daily rollover is automatic - no prompting needed
739
+ export AC_ROLLOVER_PROMPT="false"
739
740
  export AC_DEBUG="false"
740
741
 
741
742
  # Check for debug flag
@@ -784,7 +785,8 @@ AC_MSG_DEBOUNCE_MS=3000
784
785
 
785
786
  # Behavior
786
787
  AC_CLEAR_MSG_WHEN=push
787
- AC_ROLLOVER_PROMPT=true
788
+ # Daily rollover is automatic
789
+ AC_ROLLOVER_PROMPT=false
788
790
  AC_DEBUG=false
789
791
  `;
790
792
 
@@ -826,7 +828,7 @@ function printInstructions(initials) {
826
828
  log.title('🎯 Daily Workflow:');
827
829
  console.log('');
828
830
  console.log(`• Your daily branches will be: ${colors.bright}dev_${initials}_YYYY-MM-DD${colors.reset}`);
829
- console.log('• The worker handles day rollover automatically');
831
+ console.log('• The worker automatically creates new daily branches at midnight');
830
832
  console.log('• Commits require valid conventional format (feat/fix/docs/etc)');
831
833
  console.log('• Message file is cleared after successful push');
832
834
  console.log('');