s9n-devops-agent 2.0.18-dev.3 β 2.0.18-dev.5
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 +1 -0
- package/package.json +1 -1
- package/src/agent-chat.js +17 -4
- package/src/cs-devops-agent-worker.js +120 -0
- package/src/session-coordinator.js +54 -2
package/README.md
CHANGED
|
@@ -81,6 +81,7 @@ s9n-devops-agent start
|
|
|
81
81
|
### π² Smart Branch Management
|
|
82
82
|
- **Hierarchy:** `session/task` β `daily/date` β `main`.
|
|
83
83
|
- **Auto-Merge:** Sessions automatically merge into daily branches, which roll over to main.
|
|
84
|
+
- **Base Branch Selection:** Choose any branch (main, develop, etc.) as the starting point for your session worktree.
|
|
84
85
|
|
|
85
86
|
### π House Rules System
|
|
86
87
|
- **Context Injection:** AI agents read `docs/houserules.md` to understand your coding conventions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s9n-devops-agent",
|
|
3
|
-
"version": "2.0.18-dev.
|
|
3
|
+
"version": "2.0.18-dev.5",
|
|
4
4
|
"description": "CS_DevOpsAgent - Intelligent Git Automation System with multi-agent support and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cs-devops-agent-worker.js",
|
package/src/agent-chat.js
CHANGED
|
@@ -51,9 +51,14 @@ const CONFIG = {
|
|
|
51
51
|
|
|
52
52
|
class SmartAssistant {
|
|
53
53
|
constructor() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
// Initialize Groq client lazily or with null if key is missing
|
|
55
|
+
const apiKey = process.env.GROQ_API_KEY || process.env.OPENAI_API_KEY;
|
|
56
|
+
|
|
57
|
+
if (apiKey) {
|
|
58
|
+
this.groq = new Groq({ apiKey });
|
|
59
|
+
} else {
|
|
60
|
+
this.groq = null; // Will be initialized in start()
|
|
61
|
+
}
|
|
57
62
|
|
|
58
63
|
this.history = [];
|
|
59
64
|
this.repoRoot = process.cwd();
|
|
@@ -125,8 +130,16 @@ When you want to perform an action, use the available tools.`;
|
|
|
125
130
|
* Initialize the chat session
|
|
126
131
|
*/
|
|
127
132
|
async start() {
|
|
133
|
+
// Ensure Groq client is initialized
|
|
134
|
+
if (!this.groq) {
|
|
135
|
+
const apiKey = credentialsManager.getGroqApiKey();
|
|
136
|
+
if (apiKey) {
|
|
137
|
+
this.groq = new Groq({ apiKey });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
// Check for Groq API Key
|
|
129
|
-
if (!credentialsManager.hasGroqApiKey()) {
|
|
142
|
+
if (!this.groq && !credentialsManager.hasGroqApiKey()) {
|
|
130
143
|
console.log('\n' + '='.repeat(60));
|
|
131
144
|
console.log(`${CONFIG.colors.yellow}β οΈ GROQ API KEY MISSING${CONFIG.colors.reset}`);
|
|
132
145
|
console.log('='.repeat(60));
|
|
@@ -165,6 +165,10 @@ const FORCE_ROLLOVER = (process.env.AC_FORCE_ROLLOVER || "false").toLowerCase(
|
|
|
165
165
|
const VERSION_PREFIX = process.env.AC_VERSION_PREFIX || "v0.";
|
|
166
166
|
const VERSION_START_MINOR = Number(process.env.AC_VERSION_START_MINOR || "20"); // Start at v0.20 for micro-revisions
|
|
167
167
|
const VERSION_BASE_REF = process.env.AC_VERSION_BASE_REF || "origin/main"; // where new version branches start
|
|
168
|
+
|
|
169
|
+
// Rebase configuration
|
|
170
|
+
const REBASE_INTERVAL_HOURS = Number(process.env.AC_REBASE_INTERVAL || 0);
|
|
171
|
+
const BASE_BRANCH = process.env.AC_BASE_BRANCH || 'HEAD';
|
|
168
172
|
// ------------------------------------------------
|
|
169
173
|
|
|
170
174
|
const log = (...a) => console.log("[cs-devops-agent]", ...a);
|
|
@@ -953,6 +957,109 @@ function detectInfrastructureChanges(changedFiles) {
|
|
|
953
957
|
return detected;
|
|
954
958
|
}
|
|
955
959
|
|
|
960
|
+
// ============================================================================
|
|
961
|
+
// AUTO-REBASE FUNCTIONALITY
|
|
962
|
+
// ============================================================================
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Check if it's time to rebase and perform the operation
|
|
966
|
+
* @param {string} repoRoot - Repository root
|
|
967
|
+
* @returns {Promise<boolean>} - True if rebase occurred
|
|
968
|
+
*/
|
|
969
|
+
async function checkAndPerformRebase(repoRoot) {
|
|
970
|
+
if (REBASE_INTERVAL_HOURS <= 0) return false;
|
|
971
|
+
if (BASE_BRANCH === 'HEAD') return false; // Can't rebase from HEAD (relative)
|
|
972
|
+
|
|
973
|
+
// Calculate interval in ms
|
|
974
|
+
const intervalMs = REBASE_INTERVAL_HOURS * 60 * 60 * 1000;
|
|
975
|
+
const now = Date.now();
|
|
976
|
+
|
|
977
|
+
// Check if enough time has passed
|
|
978
|
+
if (!global.lastRebaseTime) global.lastRebaseTime = now; // Initialize on first run
|
|
979
|
+
if (now - global.lastRebaseTime < intervalMs) return false;
|
|
980
|
+
|
|
981
|
+
// Don't rebase if we are busy
|
|
982
|
+
if (busy) return false;
|
|
983
|
+
|
|
984
|
+
try {
|
|
985
|
+
busy = true;
|
|
986
|
+
|
|
987
|
+
console.log('\n' + 'β'.repeat(60));
|
|
988
|
+
console.log(`\x1b[33mβ οΈ AUTO-REBASE IN PROGRESS - PAUSING AGENT...\x1b[0m`);
|
|
989
|
+
console.log(`Interval: ${REBASE_INTERVAL_HOURS} hours reached.`);
|
|
990
|
+
console.log(`Base: ${BASE_BRANCH}`);
|
|
991
|
+
console.log('β'.repeat(60) + '\n');
|
|
992
|
+
|
|
993
|
+
// Check for uncommitted changes
|
|
994
|
+
const dirty = await hasUncommittedChanges();
|
|
995
|
+
let stashed = false;
|
|
996
|
+
|
|
997
|
+
if (dirty) {
|
|
998
|
+
log('Stashing uncommitted changes before rebase...');
|
|
999
|
+
const stashRes = await run('git', ['stash', 'push', '-m', `Auto-stash before rebase ${new Date().toISOString()}`]);
|
|
1000
|
+
if (stashRes.ok) stashed = true;
|
|
1001
|
+
else {
|
|
1002
|
+
console.error('\x1b[31mβ Failed to stash changes. Aborting rebase.\x1b[0m');
|
|
1003
|
+
busy = false;
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Fetch latest
|
|
1009
|
+
log(`Fetching latest changes from origin...`);
|
|
1010
|
+
await run('git', ['fetch', 'origin', BASE_BRANCH]);
|
|
1011
|
+
|
|
1012
|
+
// Rebase
|
|
1013
|
+
log(`Rebasing onto origin/${BASE_BRANCH}...`);
|
|
1014
|
+
const rebaseRes = await run('git', ['pull', '--rebase', 'origin', BASE_BRANCH]);
|
|
1015
|
+
|
|
1016
|
+
if (rebaseRes.ok) {
|
|
1017
|
+
console.log(`\x1b[32mβ Rebase successful!\x1b[0m`);
|
|
1018
|
+
|
|
1019
|
+
// Pop stash if needed
|
|
1020
|
+
if (stashed) {
|
|
1021
|
+
log('Restoring stashed changes...');
|
|
1022
|
+
const popRes = await run('git', ['stash', 'pop']);
|
|
1023
|
+
if (!popRes.ok) {
|
|
1024
|
+
console.error('\x1b[31mβ οΈ Conflict during stash pop. Manual intervention required.\x1b[0m');
|
|
1025
|
+
console.log('\x1b[33mPlease resolve conflicts and continue.\x1b[0m');
|
|
1026
|
+
} else {
|
|
1027
|
+
console.log('\x1b[32mβ Stash restored.\x1b[0m');
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
global.lastRebaseTime = Date.now();
|
|
1032
|
+
} else {
|
|
1033
|
+
console.error(`\x1b[31mβ REBASE CONFLICT DETECTED\x1b[0m`);
|
|
1034
|
+
console.error(rebaseRes.stdout);
|
|
1035
|
+
|
|
1036
|
+
// Abort rebase
|
|
1037
|
+
log('Aborting rebase...');
|
|
1038
|
+
await run('git', ['rebase', '--abort']);
|
|
1039
|
+
|
|
1040
|
+
// Restore stash if we stashed
|
|
1041
|
+
if (stashed) {
|
|
1042
|
+
await run('git', ['stash', 'pop']);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
console.log('\x1b[33mRebase aborted. Please manually rebase your branch.\x1b[0m');
|
|
1046
|
+
// Disable auto-rebase for this session to avoid loop?
|
|
1047
|
+
// Or just wait for next interval.
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
console.log(`\x1b[32mβ RESUMING AGENT OPERATION\x1b[0m\n`);
|
|
1051
|
+
return true;
|
|
1052
|
+
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
console.error(`Rebase error: ${err.message}`);
|
|
1055
|
+
// Try to recover state
|
|
1056
|
+
try { await run('git', ['rebase', '--abort']); } catch (e) {}
|
|
1057
|
+
return false;
|
|
1058
|
+
} finally {
|
|
1059
|
+
busy = false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
956
1063
|
/**
|
|
957
1064
|
* Update infrastructure documentation
|
|
958
1065
|
* @param {object} infraChanges - Infrastructure change details
|
|
@@ -1500,6 +1607,19 @@ console.log();
|
|
|
1500
1607
|
log("watchingβ¦");
|
|
1501
1608
|
}
|
|
1502
1609
|
|
|
1610
|
+
// Schedule auto-rebase if configured
|
|
1611
|
+
if (REBASE_INTERVAL_HOURS > 0) {
|
|
1612
|
+
log(`Auto-rebase scheduled every ${REBASE_INTERVAL_HOURS} hours (checking every 5m)`);
|
|
1613
|
+
// Initial check (in case we started overdue)
|
|
1614
|
+
// Don't await this so we don't block startup
|
|
1615
|
+
checkAndPerformRebase(repoRoot).catch(err => console.error(err));
|
|
1616
|
+
|
|
1617
|
+
// Periodic check
|
|
1618
|
+
setInterval(async () => {
|
|
1619
|
+
await checkAndPerformRebase(repoRoot);
|
|
1620
|
+
}, 5 * 60 * 1000); // Check every 5 minutes
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1503
1623
|
// ============================================================================
|
|
1504
1624
|
// FILE WATCHER SETUP - Monitor for changes and trigger commits
|
|
1505
1625
|
// ============================================================================
|
|
@@ -830,7 +830,7 @@ export class SessionCoordinator {
|
|
|
830
830
|
*/
|
|
831
831
|
async promptForBaseBranch() {
|
|
832
832
|
console.log(`\n${CONFIG.colors.yellow}βββ Base Branch Selection βββ${CONFIG.colors.reset}`);
|
|
833
|
-
console.log(`${CONFIG.colors.dim}
|
|
833
|
+
console.log(`${CONFIG.colors.dim}Which branch should I use as the starting point for your work?${CONFIG.colors.reset}`);
|
|
834
834
|
|
|
835
835
|
// Get available branches
|
|
836
836
|
const branches = this.getAvailableBranches();
|
|
@@ -1270,6 +1270,9 @@ export class SessionCoordinator {
|
|
|
1270
1270
|
// Ask for base branch (where to start work from)
|
|
1271
1271
|
const baseBranch = await this.promptForBaseBranch();
|
|
1272
1272
|
|
|
1273
|
+
// Ask for auto-rebase interval
|
|
1274
|
+
const rebaseInterval = await this.promptForRebaseInterval();
|
|
1275
|
+
|
|
1273
1276
|
// Check for Docker configuration and ask about restart preference
|
|
1274
1277
|
let dockerConfig = null;
|
|
1275
1278
|
|
|
@@ -1425,6 +1428,9 @@ export class SessionCoordinator {
|
|
|
1425
1428
|
execSync(`git worktree add -b ${branchName} "${worktreePath}" ${baseRef}`, { stdio: 'pipe' });
|
|
1426
1429
|
console.log(`${CONFIG.colors.green}β${CONFIG.colors.reset} Worktree created at: ${worktreePath}`);
|
|
1427
1430
|
|
|
1431
|
+
// Store base branch in session data for rebase logic
|
|
1432
|
+
const sessionBaseBranch = baseRef === 'HEAD' ? await this.resolveHeadBranch() : baseRef;
|
|
1433
|
+
|
|
1428
1434
|
// If we're in a submodule, set up the correct remote for the worktree
|
|
1429
1435
|
if (isSubmodule && parentRemote) {
|
|
1430
1436
|
console.log(`${CONFIG.colors.yellow}Configuring worktree to use parent repository remote...${CONFIG.colors.reset}`);
|
|
@@ -1446,11 +1452,13 @@ export class SessionCoordinator {
|
|
|
1446
1452
|
task,
|
|
1447
1453
|
worktreePath,
|
|
1448
1454
|
branchName,
|
|
1455
|
+
baseBranch: sessionBaseBranch,
|
|
1449
1456
|
created: new Date().toISOString(),
|
|
1450
1457
|
status: 'active',
|
|
1451
1458
|
pid: process.pid,
|
|
1452
1459
|
developerInitials: devInitials,
|
|
1453
1460
|
mergeConfig: mergeConfig,
|
|
1461
|
+
rebaseInterval: rebaseInterval,
|
|
1454
1462
|
dockerConfig: dockerConfig
|
|
1455
1463
|
};
|
|
1456
1464
|
|
|
@@ -1488,6 +1496,46 @@ export class SessionCoordinator {
|
|
|
1488
1496
|
}
|
|
1489
1497
|
}
|
|
1490
1498
|
|
|
1499
|
+
/**
|
|
1500
|
+
* Prompt for auto-rebase interval
|
|
1501
|
+
*/
|
|
1502
|
+
async promptForRebaseInterval() {
|
|
1503
|
+
console.log(`\n${CONFIG.colors.yellow}βββ Auto-Rebase Configuration βββ${CONFIG.colors.reset}`);
|
|
1504
|
+
console.log(`${CONFIG.colors.dim}I can automatically pull updates from the base branch to keep you up-to-date.${CONFIG.colors.reset}`);
|
|
1505
|
+
console.log(`${CONFIG.colors.dim}This helps prevent conflicts later by rebasing your work periodically.${CONFIG.colors.reset}`);
|
|
1506
|
+
|
|
1507
|
+
const rl = readline.createInterface({
|
|
1508
|
+
input: process.stdin,
|
|
1509
|
+
output: process.stdout
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
return new Promise((resolve) => {
|
|
1513
|
+
rl.question(`\nHow often should I rebase? (in hours, 0 to disable) [0]: `, (answer) => {
|
|
1514
|
+
rl.close();
|
|
1515
|
+
const hours = parseFloat(answer.trim());
|
|
1516
|
+
if (isNaN(hours) || hours <= 0) {
|
|
1517
|
+
console.log(`${CONFIG.colors.dim}Auto-rebase disabled. I'll let you manage updates manually.${CONFIG.colors.reset}`);
|
|
1518
|
+
resolve(0);
|
|
1519
|
+
} else {
|
|
1520
|
+
console.log(`${CONFIG.colors.green}β I'll check for updates and rebase every ${hours} hour(s).${CONFIG.colors.reset}`);
|
|
1521
|
+
resolve(hours);
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* Resolve HEAD to actual branch name
|
|
1529
|
+
*/
|
|
1530
|
+
async resolveHeadBranch() {
|
|
1531
|
+
try {
|
|
1532
|
+
const head = execSync('git rev-parse --abbrev-ref HEAD', { cwd: this.repoRoot, encoding: 'utf8' }).trim();
|
|
1533
|
+
return head;
|
|
1534
|
+
} catch (e) {
|
|
1535
|
+
return 'main';
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1491
1539
|
/**
|
|
1492
1540
|
* Generate instructions for the coding agent
|
|
1493
1541
|
*/
|
|
@@ -1895,7 +1943,11 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1895
1943
|
AC_DATE_STYLE: process.env.AC_DATE_STYLE || 'dash', // Preserve date style
|
|
1896
1944
|
// Apply version configuration if set
|
|
1897
1945
|
...(projectSettings.versioningStrategy?.prefix && { AC_VERSION_PREFIX: projectSettings.versioningStrategy.prefix }),
|
|
1898
|
-
...(projectSettings.versioningStrategy?.startMinor && { AC_VERSION_START_MINOR: projectSettings.versioningStrategy.startMinor.toString() })
|
|
1946
|
+
...(projectSettings.versioningStrategy?.startMinor && { AC_VERSION_START_MINOR: projectSettings.versioningStrategy.startMinor.toString() }),
|
|
1947
|
+
|
|
1948
|
+
// Rebase configuration
|
|
1949
|
+
AC_REBASE_INTERVAL: (sessionData.rebaseInterval || 0).toString(),
|
|
1950
|
+
AC_BASE_BRANCH: sessionData.baseBranch || 'HEAD' // We need to pass the base branch for rebasing
|
|
1899
1951
|
};
|
|
1900
1952
|
|
|
1901
1953
|
const agentScript = path.join(__dirname, 'cs-devops-agent-worker.js');
|