s9n-devops-agent 1.3.4 → 1.4.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/docs/houserules.md +66 -3
- package/package.json +1 -1
- package/src/cs-devops-agent-worker.js +25 -1
- package/src/docker-utils.js +24 -8
- package/src/house-rules-manager.js +222 -0
- package/src/session-coordinator.js +150 -12
package/docs/houserules.md
CHANGED
|
@@ -53,21 +53,43 @@ To prevent conflicts with other agents editing the same files, you MUST follow t
|
|
|
53
53
|
|
|
54
54
|
## Testing Policy (Required)
|
|
55
55
|
|
|
56
|
+
### Test-Driven Development (TDD) Requirements
|
|
57
|
+
|
|
58
|
+
**CRITICAL: For ALL new features and copied code (except infrastructure):**
|
|
59
|
+
1. **WRITE THE TEST FIRST** - Before implementing any new functionality
|
|
60
|
+
2. **RED-GREEN-REFACTOR** cycle:
|
|
61
|
+
- RED: Write a failing test that defines expected behavior
|
|
62
|
+
- GREEN: Write minimal code to make the test pass
|
|
63
|
+
- REFACTOR: Improve the code while keeping tests green
|
|
64
|
+
|
|
65
|
+
### When Tests Are MANDATORY:
|
|
66
|
+
- **New Features**: Test MUST be written BEFORE implementation code
|
|
67
|
+
- **Copied/Adapted Code**: Test MUST verify the adaptation works correctly
|
|
68
|
+
- **Bug Fixes**: Test MUST reproduce the bug first (fail), then pass after fix
|
|
69
|
+
- **API Changes**: Test MUST cover new endpoints/methods
|
|
70
|
+
- **Business Logic**: Test MUST validate all logic paths
|
|
71
|
+
|
|
72
|
+
### When Tests Are OPTIONAL:
|
|
73
|
+
- **Pure Infrastructure Code**: Build scripts, deployment configs
|
|
74
|
+
- **Configuration Files**: Unless they contain logic
|
|
75
|
+
- **Documentation**: README files, comments
|
|
76
|
+
- **Generated Code**: Auto-generated files (but test the generator!)
|
|
77
|
+
|
|
56
78
|
Goal: Every bug fix and feature ships with an executable test.
|
|
57
79
|
|
|
58
80
|
Location: test_cases/<area>/<component>/.
|
|
59
81
|
|
|
60
|
-
DoD: (1) failing test
|
|
82
|
+
DoD: (1) failing test defines behavior (red), (2) implementation makes it pass (green), (3) tests wired into CI and local runner.
|
|
61
83
|
|
|
62
84
|
Naming: YYYYMMDD_<short-slug>_spec.(py|ts|js|go|rb|java)
|
|
63
85
|
|
|
64
|
-
Non-duplication: Extend existing tests for the same
|
|
86
|
+
Non-duplication: Extend existing tests for the same functionality.
|
|
65
87
|
|
|
66
88
|
Determinism: No flaky/random tests; use seeds and fake timers.
|
|
67
89
|
|
|
68
90
|
## Test Generation Rules (for Claude / code assistants)
|
|
69
91
|
|
|
70
|
-
|
|
92
|
+
**ALWAYS write the test BEFORE the implementation code.**
|
|
71
93
|
|
|
72
94
|
Infer <area>/<component> from changed paths.
|
|
73
95
|
|
|
@@ -77,6 +99,47 @@ Use native runners per language (pytest, vitest/jest, go test, rspec, JUnit).
|
|
|
77
99
|
|
|
78
100
|
Add short "why it failed before the fix" note in the test.
|
|
79
101
|
|
|
102
|
+
## TDD Workflow for New Features
|
|
103
|
+
|
|
104
|
+
### Step 1: Write the Test First
|
|
105
|
+
Before writing ANY implementation code:
|
|
106
|
+
1. Create test file in `test_cases/<area>/<component>/`
|
|
107
|
+
2. Define expected behavior through test assertions
|
|
108
|
+
3. Run test to ensure it FAILS (Red phase)
|
|
109
|
+
4. Commit the failing test with message: `test: add failing test for <feature>`
|
|
110
|
+
|
|
111
|
+
### Step 2: Implement Minimal Code
|
|
112
|
+
1. Write ONLY enough code to make the test pass
|
|
113
|
+
2. Don't add extra features or optimizations yet
|
|
114
|
+
3. Run test to ensure it PASSES (Green phase)
|
|
115
|
+
4. Commit implementation with message: `feat: implement <feature> to pass test`
|
|
116
|
+
|
|
117
|
+
### Step 3: Refactor (if needed)
|
|
118
|
+
1. Improve code quality, performance, or structure
|
|
119
|
+
2. Ensure tests still pass after each change
|
|
120
|
+
3. Commit refactoring with message: `refactor: improve <aspect> of <feature>`
|
|
121
|
+
|
|
122
|
+
### Example TDD Flow for Agents:
|
|
123
|
+
```bash
|
|
124
|
+
# 1. First, declare your intent to edit test and implementation files
|
|
125
|
+
echo '{"files": ["test_cases/session/coordinator/test_new_feature.js", "src/new_feature.js"]}' > .file-coordination/active-edits/claude-session.json
|
|
126
|
+
|
|
127
|
+
# 2. Write the test FIRST
|
|
128
|
+
# Create: test_cases/session/coordinator/20241003_new_feature_spec.js
|
|
129
|
+
|
|
130
|
+
# 3. Run test to verify it fails
|
|
131
|
+
npm test test_cases/session/coordinator/20241003_new_feature_spec.js
|
|
132
|
+
|
|
133
|
+
# 4. NOW write the implementation
|
|
134
|
+
# Create/modify: src/new_feature.js
|
|
135
|
+
|
|
136
|
+
# 5. Run test to verify it passes
|
|
137
|
+
npm test test_cases/session/coordinator/20241003_new_feature_spec.js
|
|
138
|
+
|
|
139
|
+
# 6. Release your file locks
|
|
140
|
+
rm .file-coordination/active-edits/claude-session.json
|
|
141
|
+
```
|
|
142
|
+
|
|
80
143
|
## Test Case Template
|
|
81
144
|
```
|
|
82
145
|
# Test Case: <human title>
|
package/package.json
CHANGED
|
@@ -1454,7 +1454,31 @@ console.log();
|
|
|
1454
1454
|
ignoreInitial: true,
|
|
1455
1455
|
usePolling: USE_POLLING,
|
|
1456
1456
|
interval: 500,
|
|
1457
|
-
ignored: [
|
|
1457
|
+
ignored: [
|
|
1458
|
+
"**/__pycache__/**",
|
|
1459
|
+
"**/*.pyc",
|
|
1460
|
+
"**/.DS_Store",
|
|
1461
|
+
"**/logs/**",
|
|
1462
|
+
"**/node_modules/**",
|
|
1463
|
+
"**/.git/**",
|
|
1464
|
+
"**/local_deploy/**",
|
|
1465
|
+
"**/dist/**",
|
|
1466
|
+
"**/build/**",
|
|
1467
|
+
"**/coverage/**",
|
|
1468
|
+
"**/.next/**",
|
|
1469
|
+
"**/.nuxt/**",
|
|
1470
|
+
"**/tmp/**",
|
|
1471
|
+
"**/temp/**",
|
|
1472
|
+
"**/.cache/**",
|
|
1473
|
+
"**/vendor/**",
|
|
1474
|
+
"**/.vscode/**",
|
|
1475
|
+
"**/.idea/**",
|
|
1476
|
+
"**/*.log",
|
|
1477
|
+
"**/*.lock",
|
|
1478
|
+
"**/package-lock.json",
|
|
1479
|
+
"**/yarn.lock",
|
|
1480
|
+
"**/pnpm-lock.yaml"
|
|
1481
|
+
],
|
|
1458
1482
|
})
|
|
1459
1483
|
.on("all", async (evt, p) => {
|
|
1460
1484
|
const now = Date.now();
|
package/src/docker-utils.js
CHANGED
|
@@ -46,7 +46,7 @@ export async function isDockerComposeAvailable() {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Find docker-compose files in the project
|
|
49
|
+
* Find docker-compose files in the project, parent directory, and parent/Infrastructure
|
|
50
50
|
*/
|
|
51
51
|
export function findDockerComposeFiles(projectPath) {
|
|
52
52
|
const possibleFiles = [
|
|
@@ -60,14 +60,30 @@ export function findDockerComposeFiles(projectPath) {
|
|
|
60
60
|
'docker-compose.local.yaml'
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
const searchLocations = [
|
|
64
|
+
{ path: projectPath, label: 'project directory' },
|
|
65
|
+
{ path: path.dirname(projectPath), label: 'parent directory' },
|
|
66
|
+
{ path: path.join(path.dirname(projectPath), 'Infrastructure'), label: 'parent/Infrastructure' },
|
|
67
|
+
{ path: path.join(path.dirname(projectPath), 'infrastructure'), label: 'parent/infrastructure' }
|
|
68
|
+
];
|
|
69
|
+
|
|
63
70
|
const found = [];
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
|
|
72
|
+
for (const location of searchLocations) {
|
|
73
|
+
// Check if location exists
|
|
74
|
+
if (!fs.existsSync(location.path)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const file of possibleFiles) {
|
|
79
|
+
const filePath = path.join(location.path, file);
|
|
80
|
+
if (fs.existsSync(filePath)) {
|
|
81
|
+
found.push({
|
|
82
|
+
name: file,
|
|
83
|
+
path: filePath,
|
|
84
|
+
location: location.label
|
|
85
|
+
});
|
|
86
|
+
}
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
|
|
@@ -529,6 +529,228 @@ ${endMarker}`;
|
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Initial setup - prompt for folder structure preference and copy appropriate files
|
|
534
|
+
*/
|
|
535
|
+
async initialSetup() {
|
|
536
|
+
// Check if house rules already exist
|
|
537
|
+
if (this.houseRulesPath && fs.existsSync(this.houseRulesPath)) {
|
|
538
|
+
return {
|
|
539
|
+
alreadyExists: true,
|
|
540
|
+
path: this.houseRulesPath,
|
|
541
|
+
message: 'House rules already exist'
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Import readline for interactive prompts
|
|
546
|
+
const readline = await import('readline');
|
|
547
|
+
const rl = readline.createInterface({
|
|
548
|
+
input: process.stdin,
|
|
549
|
+
output: process.stdout
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
return new Promise((resolve) => {
|
|
553
|
+
console.log('\n📋 House Rules Setup');
|
|
554
|
+
console.log('━'.repeat(60));
|
|
555
|
+
console.log('\nWould you like to enforce a structured folder organization?');
|
|
556
|
+
console.log('\n ✅ YES - Get comprehensive folder structure with guidelines');
|
|
557
|
+
console.log(' • Modular organization (/ModuleName/src/featurename/)');
|
|
558
|
+
console.log(' • Best for: New projects, large applications');
|
|
559
|
+
console.log(' • Includes: houserules + folders.md guide');
|
|
560
|
+
console.log('\n ❌ NO - Use flexible structure (organize your own way)');
|
|
561
|
+
console.log(' • No enforced folder structure');
|
|
562
|
+
console.log(' • Best for: Existing projects, quick setup');
|
|
563
|
+
console.log(' • Includes: Core house rules only');
|
|
564
|
+
console.log();
|
|
565
|
+
|
|
566
|
+
rl.question('Do you want structured folder organization? (Y/N) [N]: ', (answer) => {
|
|
567
|
+
const wantStructure = answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
|
|
568
|
+
rl.close();
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
const result = this.setupHouseRules(wantStructure);
|
|
572
|
+
console.log();
|
|
573
|
+
console.log(`${wantStructure ? '📁' : '📄'} House rules setup complete!`);
|
|
574
|
+
console.log(` Location: ${result.houseRulesPath}`);
|
|
575
|
+
|
|
576
|
+
if (wantStructure) {
|
|
577
|
+
console.log(` Folder guide: ${result.foldersPath}`);
|
|
578
|
+
console.log();
|
|
579
|
+
console.log('💡 TIP: Open folders.md to customize your project structure');
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (result.infrastructurePath) {
|
|
583
|
+
console.log(` Infrastructure docs: ${result.infrastructurePath}`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
console.log();
|
|
587
|
+
resolve(result);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error('❌ Failed to setup house rules:', error.message);
|
|
590
|
+
resolve({ success: false, error: error.message });
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Setup house rules by copying appropriate template
|
|
598
|
+
*/
|
|
599
|
+
setupHouseRules(withStructure = false) {
|
|
600
|
+
// Determine source and target paths
|
|
601
|
+
const agentRoot = path.join(__dirname, '..');
|
|
602
|
+
const sourceFileName = withStructure ? 'houserules_structured.md' : 'houserules_core.md';
|
|
603
|
+
const sourcePath = path.join(agentRoot, sourceFileName);
|
|
604
|
+
const targetPath = path.join(this.projectRoot, 'houserules.md');
|
|
605
|
+
|
|
606
|
+
// Verify source file exists
|
|
607
|
+
if (!fs.existsSync(sourcePath)) {
|
|
608
|
+
throw new Error(`Source house rules template not found: ${sourcePath}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Copy house rules
|
|
612
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
613
|
+
this.houseRulesPath = targetPath;
|
|
614
|
+
|
|
615
|
+
const result = {
|
|
616
|
+
success: true,
|
|
617
|
+
withStructure,
|
|
618
|
+
houseRulesPath: targetPath
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// If structured, also copy folders.md
|
|
622
|
+
if (withStructure) {
|
|
623
|
+
const foldersSrc = path.join(agentRoot, 'folders.md');
|
|
624
|
+
const foldersDest = path.join(this.projectRoot, 'folders.md');
|
|
625
|
+
|
|
626
|
+
if (fs.existsSync(foldersSrc)) {
|
|
627
|
+
fs.copyFileSync(foldersSrc, foldersDest);
|
|
628
|
+
result.foldersPath = foldersDest;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Always create infrastructure directory and template
|
|
633
|
+
const infraDir = path.join(this.projectRoot, 'infrastructure');
|
|
634
|
+
if (!fs.existsSync(infraDir)) {
|
|
635
|
+
fs.mkdirSync(infraDir, { recursive: true });
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const infraDocPath = path.join(infraDir, 'infrastructure.md');
|
|
639
|
+
if (!fs.existsSync(infraDocPath)) {
|
|
640
|
+
const infraTemplate = this.getInfrastructureTemplate();
|
|
641
|
+
fs.writeFileSync(infraDocPath, infraTemplate);
|
|
642
|
+
result.infrastructurePath = infraDocPath;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Get infrastructure.md template content
|
|
650
|
+
*/
|
|
651
|
+
getInfrastructureTemplate() {
|
|
652
|
+
return `# Infrastructure Documentation
|
|
653
|
+
|
|
654
|
+
**IMPORTANT**: This file documents all infrastructure resources for this project.
|
|
655
|
+
Always read this file before creating new servers, instances, or Docker containers.
|
|
656
|
+
|
|
657
|
+
## Overview
|
|
658
|
+
Last Updated: ${new Date().toISOString()}
|
|
659
|
+
|
|
660
|
+
## Docker Containers
|
|
661
|
+
|
|
662
|
+
### Example Container
|
|
663
|
+
- **Name**: example-service
|
|
664
|
+
- **Image**: nginx:latest
|
|
665
|
+
- **Ports**: 8080:80
|
|
666
|
+
- **Purpose**: Example web server
|
|
667
|
+
- **Dependencies**: None
|
|
668
|
+
- **Configuration**:
|
|
669
|
+
- Environment: \`ENV=production\`
|
|
670
|
+
- Volumes: \`./data:/usr/share/nginx/html\`
|
|
671
|
+
|
|
672
|
+
## Servers / Instances
|
|
673
|
+
|
|
674
|
+
### Example Server
|
|
675
|
+
- **Name**: api-server
|
|
676
|
+
- **Type**: AWS EC2 t2.micro
|
|
677
|
+
- **IP/Domain**: api.example.com
|
|
678
|
+
- **Purpose**: REST API backend
|
|
679
|
+
- **Access**: SSH key required
|
|
680
|
+
- **Dependencies**: PostgreSQL database
|
|
681
|
+
|
|
682
|
+
## Databases
|
|
683
|
+
|
|
684
|
+
### Example Database
|
|
685
|
+
- **Name**: main-db
|
|
686
|
+
- **Type**: PostgreSQL 14
|
|
687
|
+
- **Host**: localhost:5432
|
|
688
|
+
- **Purpose**: Application data storage
|
|
689
|
+
- **Backup Schedule**: Daily at 2 AM UTC
|
|
690
|
+
|
|
691
|
+
## Services / APIs
|
|
692
|
+
|
|
693
|
+
### Example Service
|
|
694
|
+
- **Name**: auth-service
|
|
695
|
+
- **Type**: OAuth2 Provider
|
|
696
|
+
- **Endpoint**: https://auth.example.com
|
|
697
|
+
- **Purpose**: User authentication
|
|
698
|
+
- **API Keys**: Stored in environment variables
|
|
699
|
+
|
|
700
|
+
## Network Configuration
|
|
701
|
+
|
|
702
|
+
- **VPC/Network**: default
|
|
703
|
+
- **Subnets**:
|
|
704
|
+
- Public: 10.0.1.0/24
|
|
705
|
+
- Private: 10.0.2.0/24
|
|
706
|
+
- **Security Groups**:
|
|
707
|
+
- web-sg: Allows 80, 443
|
|
708
|
+
- api-sg: Allows 8080
|
|
709
|
+
|
|
710
|
+
## Monitoring & Logging
|
|
711
|
+
|
|
712
|
+
- **Monitoring**: CloudWatch / Prometheus
|
|
713
|
+
- **Logging**: Centralized logging to /var/log/
|
|
714
|
+
- **Alerts**: Email notifications enabled
|
|
715
|
+
|
|
716
|
+
## Backup & Recovery
|
|
717
|
+
|
|
718
|
+
- **Backup Location**: S3 bucket or local path
|
|
719
|
+
- **Recovery Time Objective (RTO)**: < 1 hour
|
|
720
|
+
- **Recovery Point Objective (RPO)**: < 15 minutes
|
|
721
|
+
|
|
722
|
+
## Access & Credentials
|
|
723
|
+
|
|
724
|
+
**NOTE**: Never commit credentials to this file.
|
|
725
|
+
Document where credentials are stored (e.g., password manager, secrets vault).
|
|
726
|
+
|
|
727
|
+
- **SSH Keys**: Stored in team password manager
|
|
728
|
+
- **API Keys**: Environment variables only
|
|
729
|
+
- **Database Passwords**: AWS Secrets Manager
|
|
730
|
+
|
|
731
|
+
## Rollback Procedures
|
|
732
|
+
|
|
733
|
+
### How to Remove/Rollback Infrastructure
|
|
734
|
+
|
|
735
|
+
1. **Docker Containers**:
|
|
736
|
+
\`\`\`bash
|
|
737
|
+
docker-compose down
|
|
738
|
+
\`\`\`
|
|
739
|
+
|
|
740
|
+
2. **Servers**:
|
|
741
|
+
- Stop the instance via cloud console
|
|
742
|
+
- Take snapshot before termination
|
|
743
|
+
- Document instance ID and configuration
|
|
744
|
+
|
|
745
|
+
## Change History
|
|
746
|
+
|
|
747
|
+
### YYYY-MM-DD - Initial Setup
|
|
748
|
+
- Created base infrastructure
|
|
749
|
+
- Deployed example services
|
|
750
|
+
- Configured networking and security
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
|
|
532
754
|
/**
|
|
533
755
|
* Get status of house rules
|
|
534
756
|
*/
|
|
@@ -25,6 +25,7 @@ import { execSync, spawn, fork } from 'child_process';
|
|
|
25
25
|
import crypto from 'crypto';
|
|
26
26
|
import readline from 'readline';
|
|
27
27
|
import { hasDockerConfiguration } from './docker-utils.js';
|
|
28
|
+
import HouseRulesManager from './house-rules-manager.js';
|
|
28
29
|
|
|
29
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
30
31
|
const __dirname = dirname(__filename);
|
|
@@ -150,6 +151,19 @@ class SessionCoordinator {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Ensure house rules are set up for the project
|
|
156
|
+
*/
|
|
157
|
+
async ensureHouseRulesSetup() {
|
|
158
|
+
const houseRulesManager = new HouseRulesManager(this.repoRoot);
|
|
159
|
+
|
|
160
|
+
// Check if house rules exist
|
|
161
|
+
if (!houseRulesManager.houseRulesPath || !fs.existsSync(houseRulesManager.houseRulesPath)) {
|
|
162
|
+
console.log(`\n${CONFIG.colors.yellow}House rules not found - setting up now...${CONFIG.colors.reset}`);
|
|
163
|
+
await houseRulesManager.initialSetup();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
153
167
|
/**
|
|
154
168
|
* Ensure project-specific version settings are configured
|
|
155
169
|
*/
|
|
@@ -463,6 +477,16 @@ class SessionCoordinator {
|
|
|
463
477
|
* Prompt for auto-merge configuration
|
|
464
478
|
*/
|
|
465
479
|
async promptForMergeConfig() {
|
|
480
|
+
// Check if auto-merge setting is already configured
|
|
481
|
+
const projectSettings = this.loadProjectSettings();
|
|
482
|
+
if (projectSettings.autoMergeConfig && projectSettings.autoMergeConfig.alwaysEnabled !== undefined) {
|
|
483
|
+
// Already configured with 'Always', use saved settings
|
|
484
|
+
if (projectSettings.autoMergeConfig.alwaysEnabled) {
|
|
485
|
+
console.log(`\n${CONFIG.colors.dim}Using saved auto-merge configuration${CONFIG.colors.reset}`);
|
|
486
|
+
return projectSettings.autoMergeConfig;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
466
490
|
const rl = readline.createInterface({
|
|
467
491
|
input: process.stdin,
|
|
468
492
|
output: process.stdout
|
|
@@ -476,18 +500,26 @@ class SessionCoordinator {
|
|
|
476
500
|
console.log(` • At the end of each day, your work is automatically merged`);
|
|
477
501
|
console.log(` • This keeps your target branch (main/develop) up to date`);
|
|
478
502
|
console.log(` • Prevents accumulation of stale feature branches`);
|
|
503
|
+
console.log();
|
|
504
|
+
console.log(`${CONFIG.colors.bright}Options:${CONFIG.colors.reset}`);
|
|
505
|
+
console.log(` ${CONFIG.colors.green}Y${CONFIG.colors.reset}) Yes - Enable for this session only`);
|
|
506
|
+
console.log(` ${CONFIG.colors.red}N${CONFIG.colors.reset}) No - Disable for this session`);
|
|
507
|
+
console.log(` ${CONFIG.colors.blue}A${CONFIG.colors.reset}) Always - Enable and remember for all sessions (24x7 operation)`);
|
|
479
508
|
|
|
480
509
|
// Ask if they want auto-merge
|
|
481
|
-
const
|
|
482
|
-
rl.question('\nEnable auto-merge
|
|
483
|
-
resolve(
|
|
510
|
+
const answer = await new Promise((resolve) => {
|
|
511
|
+
rl.question('\nEnable auto-merge? (Y/N/A) [N]: ', (ans) => {
|
|
512
|
+
resolve(ans.trim().toLowerCase());
|
|
484
513
|
});
|
|
485
514
|
});
|
|
486
515
|
|
|
516
|
+
const autoMerge = answer === 'y' || answer === 'yes' || answer === 'a' || answer === 'always';
|
|
517
|
+
const alwaysAutoMerge = answer === 'a' || answer === 'always';
|
|
518
|
+
|
|
487
519
|
if (!autoMerge) {
|
|
488
520
|
rl.close();
|
|
489
521
|
console.log(`${CONFIG.colors.dim}Auto-merge disabled. You'll need to manually merge your work.${CONFIG.colors.reset}`);
|
|
490
|
-
return { autoMerge: false };
|
|
522
|
+
return { autoMerge: false, alwaysEnabled: false };
|
|
491
523
|
}
|
|
492
524
|
|
|
493
525
|
// Get available branches
|
|
@@ -549,12 +581,23 @@ class SessionCoordinator {
|
|
|
549
581
|
autoMerge: true,
|
|
550
582
|
targetBranch,
|
|
551
583
|
strategy,
|
|
552
|
-
requireTests: strategy !== 'pull-request'
|
|
584
|
+
requireTests: strategy !== 'pull-request',
|
|
585
|
+
alwaysEnabled: alwaysAutoMerge
|
|
553
586
|
};
|
|
554
587
|
|
|
555
588
|
console.log(`\n${CONFIG.colors.green}✓${CONFIG.colors.reset} Auto-merge configuration saved:`);
|
|
556
589
|
console.log(` ${CONFIG.colors.bright}Today's work${CONFIG.colors.reset} → ${CONFIG.colors.bright}${targetBranch}${CONFIG.colors.reset}`);
|
|
557
590
|
console.log(` Strategy: ${CONFIG.colors.bright}${strategy}${CONFIG.colors.reset}`);
|
|
591
|
+
|
|
592
|
+
if (alwaysAutoMerge) {
|
|
593
|
+
console.log(` ${CONFIG.colors.blue}Mode: Always enabled${CONFIG.colors.reset} (24x7 operation - auto rollover)`);
|
|
594
|
+
// Save to project settings
|
|
595
|
+
projectSettings.autoMergeConfig = config;
|
|
596
|
+
this.saveProjectSettings(projectSettings);
|
|
597
|
+
} else {
|
|
598
|
+
console.log(` ${CONFIG.colors.dim}Mode: This session only${CONFIG.colors.reset}`);
|
|
599
|
+
}
|
|
600
|
+
|
|
558
601
|
console.log(`${CONFIG.colors.dim} (Daily branches will be merged into ${targetBranch} at end of day)${CONFIG.colors.reset}`);
|
|
559
602
|
|
|
560
603
|
return config;
|
|
@@ -677,6 +720,7 @@ class SessionCoordinator {
|
|
|
677
720
|
// Ensure both global and project setup are complete
|
|
678
721
|
await this.ensureGlobalSetup(); // Developer initials (once per user)
|
|
679
722
|
await this.ensureProjectSetup(); // Version strategy (once per project)
|
|
723
|
+
await this.ensureHouseRulesSetup(); // House rules setup (once per project)
|
|
680
724
|
|
|
681
725
|
const sessionId = this.generateSessionId();
|
|
682
726
|
const task = options.task || 'development';
|
|
@@ -702,7 +746,7 @@ class SessionCoordinator {
|
|
|
702
746
|
if (dockerInfo.hasCompose) {
|
|
703
747
|
console.log(`${CONFIG.colors.dim}Found docker-compose files:${CONFIG.colors.reset}`);
|
|
704
748
|
dockerInfo.composeFiles.forEach(file => {
|
|
705
|
-
console.log(` • ${file.name}`);
|
|
749
|
+
console.log(` • ${file.name} ${CONFIG.colors.dim}(in ${file.location})${CONFIG.colors.reset}`);
|
|
706
750
|
});
|
|
707
751
|
}
|
|
708
752
|
|
|
@@ -711,12 +755,78 @@ class SessionCoordinator {
|
|
|
711
755
|
}
|
|
712
756
|
|
|
713
757
|
dockerConfig = await this.promptForDockerConfig();
|
|
758
|
+
} else {
|
|
759
|
+
// No Docker configuration found - prompt user
|
|
760
|
+
const rl = readline.createInterface({
|
|
761
|
+
input: process.stdin,
|
|
762
|
+
output: process.stdout
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
console.log(`\n${CONFIG.colors.yellow}No Docker Configuration Found${CONFIG.colors.reset}`);
|
|
766
|
+
console.log(`${CONFIG.colors.dim}I couldn't find any docker-compose files in:${CONFIG.colors.reset}`);
|
|
767
|
+
console.log(`${CONFIG.colors.dim} • Project directory${CONFIG.colors.reset}`);
|
|
768
|
+
console.log(`${CONFIG.colors.dim} • Parent directory${CONFIG.colors.reset}`);
|
|
769
|
+
console.log(`${CONFIG.colors.dim} • Parent/Infrastructure or parent/infrastructure${CONFIG.colors.reset}`);
|
|
770
|
+
|
|
771
|
+
const hasDocker = await new Promise((resolve) => {
|
|
772
|
+
rl.question(`\nDo you have a Docker setup you'd like to configure? (y/N): `, (answer) => {
|
|
773
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
if (hasDocker) {
|
|
778
|
+
const dockerPath = await new Promise((resolve) => {
|
|
779
|
+
rl.question(`\nEnter the full path to your docker-compose file: `, (answer) => {
|
|
780
|
+
resolve(answer.trim());
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
if (dockerPath && fs.existsSync(dockerPath)) {
|
|
785
|
+
console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Found docker-compose file at: ${dockerPath}`);
|
|
786
|
+
|
|
787
|
+
// Ask about rebuild and service preferences
|
|
788
|
+
const rebuild = await new Promise((resolve) => {
|
|
789
|
+
rl.question('\nRebuild containers on restart? (y/N): ', (answer) => {
|
|
790
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const specificService = await new Promise((resolve) => {
|
|
795
|
+
rl.question('\nSpecific service to restart (leave empty for all): ', (answer) => {
|
|
796
|
+
resolve(answer.trim() || null);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
dockerConfig = {
|
|
801
|
+
enabled: true,
|
|
802
|
+
composeFile: dockerPath,
|
|
803
|
+
rebuild: rebuild,
|
|
804
|
+
service: specificService,
|
|
805
|
+
forceRecreate: false
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
console.log(`\n${CONFIG.colors.green}✓${CONFIG.colors.reset} Docker restart configuration:`);
|
|
809
|
+
console.log(` ${CONFIG.colors.bright}Auto-restart:${CONFIG.colors.reset} Enabled`);
|
|
810
|
+
console.log(` ${CONFIG.colors.bright}Compose file:${CONFIG.colors.reset} ${path.basename(dockerPath)}`);
|
|
811
|
+
console.log(` ${CONFIG.colors.bright}Rebuild:${CONFIG.colors.reset} ${rebuild ? 'Yes' : 'No'}`);
|
|
812
|
+
if (specificService) {
|
|
813
|
+
console.log(` ${CONFIG.colors.bright}Service:${CONFIG.colors.reset} ${specificService}`);
|
|
814
|
+
}
|
|
815
|
+
} else if (dockerPath) {
|
|
816
|
+
console.log(`${CONFIG.colors.red}✗${CONFIG.colors.reset} File not found: ${dockerPath}`);
|
|
817
|
+
console.log(`${CONFIG.colors.dim}Skipping Docker configuration${CONFIG.colors.reset}`);
|
|
818
|
+
}
|
|
819
|
+
} else {
|
|
820
|
+
console.log(`${CONFIG.colors.dim}Skipping Docker configuration${CONFIG.colors.reset}`);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
rl.close();
|
|
714
824
|
}
|
|
715
825
|
|
|
716
|
-
// Create worktree with developer initials in the name
|
|
717
|
-
const worktreeName = `${
|
|
826
|
+
// Create worktree with developer initials first in the name
|
|
827
|
+
const worktreeName = `${devInitials}-${agentType}-${sessionId}-${task.replace(/\s+/g, '-')}`;
|
|
718
828
|
const worktreePath = path.join(this.worktreesPath, worktreeName);
|
|
719
|
-
const branchName = `${
|
|
829
|
+
const branchName = `${devInitials}/${agentType}/${sessionId}/${task.replace(/\s+/g, '-')}`;
|
|
720
830
|
|
|
721
831
|
try {
|
|
722
832
|
// Detect if we're in a submodule and get the parent repository
|
|
@@ -970,6 +1080,34 @@ The DevOps agent will automatically:
|
|
|
970
1080
|
* Create configuration in the worktree
|
|
971
1081
|
*/
|
|
972
1082
|
createWorktreeConfig(worktreePath, sessionData) {
|
|
1083
|
+
// Create file coordination directory structure
|
|
1084
|
+
const fileCoordDir = path.join(worktreePath, '.file-coordination');
|
|
1085
|
+
const activeEditsDir = path.join(fileCoordDir, 'active-edits');
|
|
1086
|
+
const historyDir = path.join(fileCoordDir, 'history');
|
|
1087
|
+
|
|
1088
|
+
// Create directories if they don't exist
|
|
1089
|
+
[fileCoordDir, activeEditsDir, historyDir].forEach(dir => {
|
|
1090
|
+
if (!fs.existsSync(dir)) {
|
|
1091
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
// Create README for file coordination
|
|
1096
|
+
const coordReadme = `# File Coordination System
|
|
1097
|
+
|
|
1098
|
+
This directory manages multi-agent file access coordination.
|
|
1099
|
+
|
|
1100
|
+
## Structure
|
|
1101
|
+
- \`active-edits/\` - Current file locks by agents
|
|
1102
|
+
- \`history/\` - Completed edits log
|
|
1103
|
+
|
|
1104
|
+
## Usage
|
|
1105
|
+
Agents must declare files before editing by creating a JSON file in active-edits/.
|
|
1106
|
+
See house rules for details.
|
|
1107
|
+
`;
|
|
1108
|
+
fs.writeFileSync(path.join(fileCoordDir, 'README.md'), coordReadme);
|
|
1109
|
+
console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Created file coordination directory structure`);
|
|
1110
|
+
|
|
973
1111
|
// Session config file
|
|
974
1112
|
const configPath = path.join(worktreePath, '.devops-session.json');
|
|
975
1113
|
fs.writeFileSync(configPath, JSON.stringify(sessionData, null, 2));
|
|
@@ -991,7 +1129,7 @@ The DevOps agent will automatically:
|
|
|
991
1129
|
'DEVOPS_WORKTREE': path.basename(worktreePath),
|
|
992
1130
|
'DEVOPS_BRANCH': sessionData.branchName,
|
|
993
1131
|
'AC_MSG_FILE': `.devops-commit-${sessionData.sessionId}.msg`,
|
|
994
|
-
'AC_BRANCH_PREFIX': `${sessionData.agentType}_${sessionData.sessionId}_`
|
|
1132
|
+
'AC_BRANCH_PREFIX': `${sessionData.developerInitials || 'dev'}_${sessionData.agentType}_${sessionData.sessionId}_`
|
|
995
1133
|
}
|
|
996
1134
|
};
|
|
997
1135
|
|
|
@@ -1165,12 +1303,12 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1165
1303
|
...process.env,
|
|
1166
1304
|
DEVOPS_SESSION_ID: sessionId,
|
|
1167
1305
|
AC_MSG_FILE: `.devops-commit-${sessionId}.msg`,
|
|
1168
|
-
AC_BRANCH_PREFIX: `${
|
|
1306
|
+
AC_BRANCH_PREFIX: `${devInitials}_${sessionData.agentType}_${sessionId}_`,
|
|
1169
1307
|
AC_WORKING_DIR: sessionData.worktreePath,
|
|
1170
1308
|
// Don't set AC_BRANCH - let the agent create daily branches within the worktree
|
|
1171
1309
|
// AC_BRANCH would force a static branch, preventing daily/weekly rollover
|
|
1172
1310
|
AC_PUSH: 'true', // Enable auto-push for session branches
|
|
1173
|
-
AC_DAILY_PREFIX: `${
|
|
1311
|
+
AC_DAILY_PREFIX: `${devInitials}_${sessionData.agentType}_${sessionId}_`, // Daily branches with dev initials first
|
|
1174
1312
|
AC_TZ: process.env.AC_TZ || 'Asia/Dubai', // Preserve timezone for daily branches
|
|
1175
1313
|
AC_DATE_STYLE: process.env.AC_DATE_STYLE || 'dash', // Preserve date style
|
|
1176
1314
|
// Apply version configuration if set
|