s9n-devops-agent 2.0.18-dev.1 → 2.0.18-dev.12
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/bin/cs-devops-agent +16 -20
- package/docs/RELEASE_NOTES.md +15 -0
- package/package.json +1 -1
- package/scripts/deploy-local.sh +100 -0
- package/src/agent-chat.js +299 -36
- package/src/credentials-manager.js +28 -6
- package/src/cs-devops-agent-worker.js +456 -87
- package/src/kora-skills.json +47 -0
- package/src/session-coordinator.js +499 -70
- package/src/setup-cs-devops-agent.js +298 -42
- package/src/ui-utils.js +1 -1
- package/start-devops-session.sh +4 -27
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/bin/cs-devops-agent
CHANGED
|
@@ -54,21 +54,11 @@ function runShellScript(scriptPath, scriptArgs = []) {
|
|
|
54
54
|
switch(command) {
|
|
55
55
|
case 'start':
|
|
56
56
|
case 'session':
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} else {
|
|
63
|
-
// Unix/Mac: use bash script
|
|
64
|
-
const sessionScript = join(rootDir, 'start-devops-session.sh');
|
|
65
|
-
if (fs.existsSync(sessionScript)) {
|
|
66
|
-
runShellScript(sessionScript, args.slice(1));
|
|
67
|
-
} else {
|
|
68
|
-
console.error('Session script not found. Please ensure the package is properly installed.');
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
57
|
+
case 'chat':
|
|
58
|
+
// Unified entry point: Run Kora (Smart Assistant)
|
|
59
|
+
// Kora handles session creation and management conversationally
|
|
60
|
+
console.log('Starting Kora (Smart DevOps Assistant)...');
|
|
61
|
+
runScript(join(rootDir, 'src', 'agent-chat.js'), args.slice(1));
|
|
72
62
|
break;
|
|
73
63
|
|
|
74
64
|
case 'worker':
|
|
@@ -101,6 +91,11 @@ switch(command) {
|
|
|
101
91
|
runScript(join(rootDir, 'src', 'session-coordinator.js'), ['create', ...args.slice(1)]);
|
|
102
92
|
break;
|
|
103
93
|
|
|
94
|
+
case 'resume':
|
|
95
|
+
// Resume existing session
|
|
96
|
+
runScript(join(rootDir, 'src', 'session-coordinator.js'), ['resume', ...args.slice(1)]);
|
|
97
|
+
break;
|
|
98
|
+
|
|
104
99
|
case 'close':
|
|
105
100
|
// Close session
|
|
106
101
|
runScript(join(rootDir, 'src', 'close-session.js'), args.slice(1));
|
|
@@ -118,7 +113,7 @@ switch(command) {
|
|
|
118
113
|
break;
|
|
119
114
|
|
|
120
115
|
case 'chat':
|
|
121
|
-
// Run the chat agent
|
|
116
|
+
// Run the chat agent (kept for backward compatibility, now same as start)
|
|
122
117
|
runScript(join(rootDir, 'src', 'agent-chat.js'), args.slice(1));
|
|
123
118
|
break;
|
|
124
119
|
|
|
@@ -207,11 +202,12 @@ For more information, visit: https://github.com/SecondBrainAICo/CS_DevOpsAgent
|
|
|
207
202
|
break;
|
|
208
203
|
|
|
209
204
|
default:
|
|
210
|
-
// Default to
|
|
211
|
-
// This
|
|
205
|
+
// Default to Kora (Smart Assistant) if no command provided
|
|
206
|
+
// This provides the unified experience
|
|
212
207
|
if (!command || !command.startsWith('-')) {
|
|
213
|
-
console.log('Starting Kora Smart Assistant...');
|
|
214
|
-
|
|
208
|
+
console.log('Starting Kora (Smart DevOps Assistant)...');
|
|
209
|
+
// Pass all args to agent-chat
|
|
210
|
+
runScript(join(rootDir, 'src', 'agent-chat.js'), args);
|
|
215
211
|
} else {
|
|
216
212
|
// Show help for flags like -h, --help, or unknown flags
|
|
217
213
|
console.log(`\nCS_DevOpsAgent - Intelligent Git Automation System`);
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Release Notes - s9n-devops-agent v2.0.18-dev.3
|
|
2
|
+
|
|
3
|
+
## 🚀 Enhancements
|
|
4
|
+
- **Base Branch Selection**: You can now select a base branch (e.g., main, develop) when starting a session, allowing for cleaner feature branching from stable points.
|
|
5
|
+
- **Enhanced Setup Wizard**:
|
|
6
|
+
- Finds and merges contract files from subdirectories.
|
|
7
|
+
- Ensures versioning strategy is configured.
|
|
8
|
+
- Persists credentials in user home directory to survive package updates.
|
|
9
|
+
|
|
10
|
+
## 🐛 Fixes
|
|
11
|
+
- **Update Logic**: Fixed update checker to respect dev versions.
|
|
12
|
+
- **Credentials**: Fixed issue where API keys were lost during updates.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
1
16
|
# Release Notes - s9n-devops-agent v2.0.11-dev.0
|
|
2
17
|
|
|
3
18
|
## 🐛 Fixed
|
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.12",
|
|
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",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Deploy DevOpsAgent locally to a target directory
|
|
4
|
+
# Usage: ./scripts/deploy-local.sh <target_directory>
|
|
5
|
+
|
|
6
|
+
TARGET_DIR="$1"
|
|
7
|
+
|
|
8
|
+
if [ -z "$TARGET_DIR" ]; then
|
|
9
|
+
echo "Usage: $0 <target_directory>"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [ ! -d "$TARGET_DIR" ]; then
|
|
14
|
+
echo "Error: Target directory $TARGET_DIR does not exist"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
SOURCE_DIR="$(pwd)"
|
|
19
|
+
DEST_DIR="$TARGET_DIR/DevOpsAgent"
|
|
20
|
+
|
|
21
|
+
echo "Deploying DevOpsAgent to $DEST_DIR..."
|
|
22
|
+
|
|
23
|
+
# Create destination directory
|
|
24
|
+
mkdir -p "$DEST_DIR"
|
|
25
|
+
|
|
26
|
+
# Copy files
|
|
27
|
+
echo "Copying files..."
|
|
28
|
+
rsync -av --exclude 'node_modules' \
|
|
29
|
+
--exclude '.git' \
|
|
30
|
+
--exclude '.DS_Store' \
|
|
31
|
+
--exclude 'local_deploy' \
|
|
32
|
+
--exclude 'coverage' \
|
|
33
|
+
--exclude 'test_cases' \
|
|
34
|
+
"$SOURCE_DIR/" "$DEST_DIR/"
|
|
35
|
+
|
|
36
|
+
# Install dependencies in the target
|
|
37
|
+
echo "Installing dependencies in $DEST_DIR..."
|
|
38
|
+
cd "$DEST_DIR"
|
|
39
|
+
npm install --production --silent
|
|
40
|
+
cd "$SOURCE_DIR"
|
|
41
|
+
|
|
42
|
+
# Create universal runner script
|
|
43
|
+
RUNNER="$TARGET_DIR/devops"
|
|
44
|
+
LOG_FILE="$TARGET_DIR/devops-agent.log"
|
|
45
|
+
|
|
46
|
+
echo "Creating 'devops' CLI wrapper..."
|
|
47
|
+
|
|
48
|
+
cat > "$RUNNER" << EOF
|
|
49
|
+
#!/bin/bash
|
|
50
|
+
|
|
51
|
+
# DevOps Agent CLI Wrapper
|
|
52
|
+
# Supports all standard commands: start, chat, setup, worker, etc.
|
|
53
|
+
|
|
54
|
+
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
|
|
55
|
+
AGENT_DIR="\$DIR/DevOpsAgent"
|
|
56
|
+
LOG_FILE="\$DIR/devops-agent.log"
|
|
57
|
+
|
|
58
|
+
# Ensure executable permissions
|
|
59
|
+
chmod +x "\$AGENT_DIR/bin/cs-devops-agent"
|
|
60
|
+
|
|
61
|
+
# If first arg is 'worker' or 'background', run in background
|
|
62
|
+
if [ "\$1" == "background" ]; then
|
|
63
|
+
echo "Starting worker in background..."
|
|
64
|
+
echo "--- Worker Start: \$(date) ---" >> "\$LOG_FILE"
|
|
65
|
+
export AC_DEBUG="true"
|
|
66
|
+
node "\$AGENT_DIR/src/cs-devops-agent-worker.js" >> "\$LOG_FILE" 2>&1 &
|
|
67
|
+
echo "Worker PID: \$!"
|
|
68
|
+
echo "Logs: \$LOG_FILE"
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Otherwise run interactive CLI
|
|
73
|
+
node "\$AGENT_DIR/bin/cs-devops-agent" "\$@"
|
|
74
|
+
EOF
|
|
75
|
+
|
|
76
|
+
chmod +x "$RUNNER"
|
|
77
|
+
|
|
78
|
+
# Add to gitignore in target
|
|
79
|
+
GITIGNORE="$TARGET_DIR/.gitignore"
|
|
80
|
+
if [ -f "$GITIGNORE" ]; then
|
|
81
|
+
if ! grep -q "DevOpsAgent/" "$GITIGNORE"; then
|
|
82
|
+
echo "" >> "$GITIGNORE"
|
|
83
|
+
echo "# DevOps Agent" >> "$GITIGNORE"
|
|
84
|
+
echo "DevOpsAgent/" >> "$GITIGNORE"
|
|
85
|
+
echo "devops-agent.log" >> "$GITIGNORE"
|
|
86
|
+
echo "devops" >> "$GITIGNORE"
|
|
87
|
+
echo "Added DevOpsAgent files to .gitignore"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo "Deployment complete!"
|
|
92
|
+
echo "Use the './devops' script to interact with the agent:"
|
|
93
|
+
echo ""
|
|
94
|
+
echo " ./devops setup # Run first-time setup"
|
|
95
|
+
echo " ./devops chat # Chat with Kora"
|
|
96
|
+
echo " ./devops start # Start session manager (Interactive)"
|
|
97
|
+
echo " ./devops background # Run worker silently"
|
|
98
|
+
echo " ./devops --help # See all commands"
|
|
99
|
+
echo ""
|
|
100
|
+
echo "Logs location: '$LOG_FILE'"
|
package/src/agent-chat.js
CHANGED
|
@@ -23,7 +23,7 @@ import readline from 'readline';
|
|
|
23
23
|
import Groq from 'groq-sdk';
|
|
24
24
|
import { fileURLToPath } from 'url';
|
|
25
25
|
import { dirname } from 'path';
|
|
26
|
-
import { spawn } from 'child_process';
|
|
26
|
+
import { spawn, execSync } from 'child_process';
|
|
27
27
|
import { credentialsManager } from './credentials-manager.js';
|
|
28
28
|
import HouseRulesManager from './house-rules-manager.js';
|
|
29
29
|
// We'll import SessionCoordinator dynamically to avoid circular deps if any
|
|
@@ -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();
|
|
@@ -99,10 +104,50 @@ class SmartAssistant {
|
|
|
99
104
|
description: "Check the status of active sessions and locks",
|
|
100
105
|
parameters: { type: "object", properties: {} }
|
|
101
106
|
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: "function",
|
|
110
|
+
function: {
|
|
111
|
+
name: "resume_session",
|
|
112
|
+
description: "Resume an existing or orphaned session",
|
|
113
|
+
parameters: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
sessionId: { type: "string", description: "The ID of the session to resume" },
|
|
117
|
+
taskName: { type: "string", description: "The task name to search for (optional)" }
|
|
118
|
+
},
|
|
119
|
+
required: ["sessionId"]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "function",
|
|
125
|
+
function: {
|
|
126
|
+
name: "recover_sessions",
|
|
127
|
+
description: "Scan for and recover orphaned sessions from existing worktrees",
|
|
128
|
+
parameters: { type: "object", properties: {} }
|
|
129
|
+
}
|
|
102
130
|
}
|
|
103
131
|
];
|
|
104
132
|
|
|
105
|
-
|
|
133
|
+
// Load skills definition
|
|
134
|
+
let skillsDef = null;
|
|
135
|
+
try {
|
|
136
|
+
const skillsPath = path.join(__dirname, 'kora-skills.json');
|
|
137
|
+
if (fs.existsSync(skillsPath)) {
|
|
138
|
+
skillsDef = JSON.parse(fs.readFileSync(skillsPath, 'utf8'));
|
|
139
|
+
}
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Fallback if file missing or invalid
|
|
142
|
+
console.error('Warning: Could not load kora-skills.json, using defaults.');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (skillsDef) {
|
|
146
|
+
// Build dynamic system prompt from skills definition
|
|
147
|
+
const allowedTopics = skillsDef.guardrails.allowed_topics.map(t => `- ${t}`).join('\n');
|
|
148
|
+
const disallowedTopics = skillsDef.guardrails.disallowed_topics.map(t => `- ${t}`).join('\n');
|
|
149
|
+
|
|
150
|
+
this.systemPrompt = `You are ${skillsDef.assistant_name}, the ${skillsDef.role}.
|
|
106
151
|
Your goal is to help developers follow the House Rules and Contract System while being helpful and efficient.
|
|
107
152
|
|
|
108
153
|
CONTEXT:
|
|
@@ -111,22 +156,81 @@ CONTEXT:
|
|
|
111
156
|
- Users need to create "Sessions" to do work.
|
|
112
157
|
- You can execute tools to help the user.
|
|
113
158
|
|
|
114
|
-
|
|
159
|
+
CAPABILITIES (ALLOWED):
|
|
160
|
+
${allowedTopics}
|
|
161
|
+
|
|
162
|
+
GUARDRAILS (STRICTLY PROHIBITED):
|
|
163
|
+
${disallowedTopics}
|
|
164
|
+
|
|
165
|
+
AVAILABLE TOOLS:
|
|
166
|
+
1. get_house_rules_summary - Read the project's rules.
|
|
167
|
+
2. list_contracts - Check what contract files exist.
|
|
168
|
+
3. check_session_status - See active work sessions.
|
|
169
|
+
4. start_session - Begin a new task.
|
|
170
|
+
5. resume_session - Resume an existing or orphaned session.
|
|
171
|
+
6. recover_sessions - Scan and restore lost session locks.
|
|
172
|
+
|
|
173
|
+
IMPORTANT INSTRUCTIONS:
|
|
174
|
+
- ONLY use the tools listed above. Do NOT invent new tools like "check_compliance" or "run_tests".
|
|
175
|
+
- If the user asks for something OUTSIDE your capabilities, you MUST reply with exactly this message:
|
|
176
|
+
"${skillsDef.guardrails.fallback_response}"
|
|
177
|
+
- Be concise but helpful.
|
|
178
|
+
- Identify yourself as "${skillsDef.assistant_name}".
|
|
179
|
+
- If the user asks about starting a task, ask for a clear task name if not provided.
|
|
180
|
+
- If the user asks about rules, summarize them from the actual files.
|
|
181
|
+
- If the user wants to resume work, use check_session_status first.
|
|
182
|
+
- If a session seems missing but worktree exists, use recover_sessions.
|
|
183
|
+
- Always prefer "Structured" organization for new code.
|
|
184
|
+
|
|
185
|
+
When you want to perform an action, use the available tools.`;
|
|
186
|
+
} else {
|
|
187
|
+
// Fallback static prompt
|
|
188
|
+
this.systemPrompt = `You are Kora, the Smart DevOps Assistant.
|
|
189
|
+
Your goal is to help developers follow the House Rules and Contract System while being helpful and efficient.
|
|
190
|
+
|
|
191
|
+
CONTEXT:
|
|
192
|
+
- You are running inside a "DevOps Agent" environment.
|
|
193
|
+
- The project follows a strict "Contract System" (API, DB, Features, etc.).
|
|
194
|
+
- Users need to create "Sessions" to do work.
|
|
195
|
+
- You can execute tools to help the user.
|
|
196
|
+
|
|
197
|
+
AVAILABLE TOOLS:
|
|
198
|
+
1. get_house_rules_summary - Read the project's rules.
|
|
199
|
+
2. list_contracts - Check what contract files exist.
|
|
200
|
+
3. check_session_status - See active work sessions.
|
|
201
|
+
4. start_session - Begin a new task.
|
|
202
|
+
5. resume_session - Resume an existing or orphaned session.
|
|
203
|
+
6. recover_sessions - Scan and restore lost session locks.
|
|
204
|
+
|
|
205
|
+
IMPORTANT INSTRUCTIONS:
|
|
206
|
+
- ONLY use the tools listed above. Do NOT invent new tools like "check_compliance" or "run_tests".
|
|
207
|
+
- If a user asks for something you can't do with a tool (like running tests), tell them you can't do it yet but they can run "npm test" themselves.
|
|
115
208
|
- Be concise but helpful.
|
|
116
209
|
- Identify yourself as "Kora".
|
|
117
210
|
- If the user asks about starting a task, ask for a clear task name if not provided.
|
|
118
211
|
- If the user asks about rules, summarize them from the actual files.
|
|
212
|
+
- If the user wants to resume work, use check_session_status first.
|
|
213
|
+
- If a session seems missing but worktree exists, use recover_sessions.
|
|
119
214
|
- Always prefer "Structured" organization for new code.
|
|
120
215
|
|
|
121
216
|
When you want to perform an action, use the available tools.`;
|
|
217
|
+
}
|
|
122
218
|
}
|
|
123
219
|
|
|
124
220
|
/**
|
|
125
221
|
* Initialize the chat session
|
|
126
222
|
*/
|
|
127
223
|
async start() {
|
|
224
|
+
// Ensure Groq client is initialized
|
|
225
|
+
if (!this.groq) {
|
|
226
|
+
const apiKey = credentialsManager.getGroqApiKey();
|
|
227
|
+
if (apiKey) {
|
|
228
|
+
this.groq = new Groq({ apiKey });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
128
232
|
// Check for Groq API Key
|
|
129
|
-
if (!credentialsManager.hasGroqApiKey()) {
|
|
233
|
+
if (!this.groq && !credentialsManager.hasGroqApiKey()) {
|
|
130
234
|
console.log('\n' + '='.repeat(60));
|
|
131
235
|
console.log(`${CONFIG.colors.yellow}⚠️ GROQ API KEY MISSING${CONFIG.colors.reset}`);
|
|
132
236
|
console.log('='.repeat(60));
|
|
@@ -171,6 +275,41 @@ When you want to perform an action, use the available tools.`;
|
|
|
171
275
|
console.log(`\n${CONFIG.colors.cyan}Hi! I'm Kora. How can I help you today?${CONFIG.colors.reset}`);
|
|
172
276
|
console.log(`${CONFIG.colors.dim}(Try: "Start a new task for login", "Explain house rules", "Check contracts")${CONFIG.colors.reset}\n`);
|
|
173
277
|
|
|
278
|
+
// Check for command line arguments
|
|
279
|
+
const args = process.argv.slice(2);
|
|
280
|
+
const taskIndex = args.indexOf('--task') !== -1 ? args.indexOf('--task') : args.indexOf('-t');
|
|
281
|
+
|
|
282
|
+
if (taskIndex !== -1 && args[taskIndex + 1]) {
|
|
283
|
+
const taskName = args[taskIndex + 1];
|
|
284
|
+
console.log(`\n${CONFIG.colors.cyan}Auto-starting session for task: ${taskName}${CONFIG.colors.reset}\n`);
|
|
285
|
+
await this.startSession({ taskName });
|
|
286
|
+
return; // Exit after session? Or continue chat? Usually session start spawns child and returns.
|
|
287
|
+
// startSession re-initializes readline after child exit, so we can continue chat.
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check for auto-resume
|
|
291
|
+
const resumeIndex = args.indexOf('resume');
|
|
292
|
+
const sessionIdIndex = args.indexOf('--session-id');
|
|
293
|
+
|
|
294
|
+
if (resumeIndex !== -1 || sessionIdIndex !== -1) {
|
|
295
|
+
let sessionId = null;
|
|
296
|
+
let taskName = null;
|
|
297
|
+
|
|
298
|
+
if (sessionIdIndex !== -1 && args[sessionIdIndex + 1]) {
|
|
299
|
+
sessionId = args[sessionIdIndex + 1];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (taskIndex !== -1 && args[taskIndex + 1]) {
|
|
303
|
+
taskName = args[taskIndex + 1];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (sessionId || taskName) {
|
|
307
|
+
console.log(`\n${CONFIG.colors.cyan}Auto-resuming session...${CONFIG.colors.reset}\n`);
|
|
308
|
+
await this.resumeSession({ sessionId, taskName });
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
174
313
|
this.startReadline();
|
|
175
314
|
}
|
|
176
315
|
|
|
@@ -292,6 +431,12 @@ When you want to perform an action, use the available tools.`;
|
|
|
292
431
|
case 'start_session':
|
|
293
432
|
result = await this.startSession(args);
|
|
294
433
|
break;
|
|
434
|
+
case 'resume_session':
|
|
435
|
+
result = await this.resumeSession(args);
|
|
436
|
+
break;
|
|
437
|
+
case 'recover_sessions':
|
|
438
|
+
result = await this.recoverSessions();
|
|
439
|
+
break;
|
|
295
440
|
default:
|
|
296
441
|
result = JSON.stringify({ error: "Unknown tool" });
|
|
297
442
|
}
|
|
@@ -351,15 +496,50 @@ When you want to perform an action, use the available tools.`;
|
|
|
351
496
|
|
|
352
497
|
async listContracts() {
|
|
353
498
|
const contractsDir = path.join(this.repoRoot, 'House_Rules_Contracts');
|
|
354
|
-
|
|
355
|
-
|
|
499
|
+
const centralExists = fs.existsSync(contractsDir);
|
|
500
|
+
|
|
501
|
+
// Recursive search for contracts (similar to setup script)
|
|
502
|
+
const findCommand = `find "${this.repoRoot}" -type f \\( -iname "*CONTRACT*.md" -o -iname "*CONTRACT*.json" \\) -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/local_deploy/*"`;
|
|
503
|
+
|
|
504
|
+
let allFiles = [];
|
|
505
|
+
try {
|
|
506
|
+
const output = execSync(findCommand, { encoding: 'utf8' }).trim();
|
|
507
|
+
allFiles = output.split('\n').filter(Boolean);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
// Find failed or no files
|
|
356
510
|
}
|
|
357
511
|
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
512
|
+
const requiredTypes = [
|
|
513
|
+
'FEATURES_CONTRACT.md', 'API_CONTRACT.md', 'DATABASE_SCHEMA_CONTRACT.md',
|
|
514
|
+
'SQL_CONTRACT.json', 'THIRD_PARTY_INTEGRATIONS.md', 'INFRA_CONTRACT.md'
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
const status = {};
|
|
518
|
+
let scatteredCount = 0;
|
|
519
|
+
|
|
520
|
+
requiredTypes.forEach(type => {
|
|
521
|
+
// Check if in central folder
|
|
522
|
+
const isCentral = fs.existsSync(path.join(contractsDir, type));
|
|
523
|
+
|
|
524
|
+
// Check if anywhere in repo
|
|
525
|
+
const found = allFiles.filter(f => path.basename(f).toUpperCase() === type || path.basename(f).toUpperCase().includes(type.split('.')[0]));
|
|
526
|
+
|
|
527
|
+
status[type] = {
|
|
528
|
+
central: isCentral,
|
|
529
|
+
foundCount: found.length,
|
|
530
|
+
locations: found.map(f => path.relative(this.repoRoot, f))
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
if (!isCentral && found.length > 0) scatteredCount++;
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
return JSON.stringify({
|
|
537
|
+
centralFolderExists: centralExists,
|
|
538
|
+
scatteredContractsCount: scatteredCount,
|
|
539
|
+
details: status,
|
|
540
|
+
message: scatteredCount > 0
|
|
541
|
+
? "Contracts found scattered in repository. Recommend running 'npm run setup' to consolidate."
|
|
542
|
+
: (centralExists ? "Contracts found in central folder." : "No contracts found.")
|
|
363
543
|
});
|
|
364
544
|
}
|
|
365
545
|
|
|
@@ -391,55 +571,138 @@ When you want to perform an action, use the available tools.`;
|
|
|
391
571
|
}
|
|
392
572
|
|
|
393
573
|
async startSession(args) {
|
|
394
|
-
const taskName = args
|
|
574
|
+
const { taskName, description } = args;
|
|
395
575
|
|
|
396
|
-
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}Starting
|
|
576
|
+
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}Starting session for task: ${taskName}...`);
|
|
397
577
|
|
|
398
|
-
// Close readline interface
|
|
578
|
+
// Close readline interface
|
|
399
579
|
if (this.rl) {
|
|
400
580
|
this.rl.close();
|
|
401
581
|
this.rl = null;
|
|
402
582
|
}
|
|
403
583
|
|
|
404
|
-
// We need to run the session coordinator interactively
|
|
405
|
-
// We'll use the 'create-and-start' command to jump straight to the task
|
|
406
584
|
const scriptPath = path.join(__dirname, 'session-coordinator.js');
|
|
407
585
|
|
|
408
586
|
return new Promise((resolve, reject) => {
|
|
409
|
-
// Use 'inherit' for stdio to allow interactive input/output
|
|
410
587
|
const child = spawn('node', [scriptPath, 'create-and-start', '--task', taskName], {
|
|
411
588
|
stdio: 'inherit',
|
|
412
589
|
cwd: this.repoRoot
|
|
413
590
|
});
|
|
414
591
|
|
|
415
592
|
child.on('close', (code) => {
|
|
416
|
-
// Re-initialize readline interface after child process exits
|
|
417
593
|
this.startReadline();
|
|
594
|
+
if (code === 0) {
|
|
595
|
+
resolve(JSON.stringify({ success: true, message: "Session started successfully." }));
|
|
596
|
+
} else {
|
|
597
|
+
resolve(JSON.stringify({ success: false, message: `Session process exited with code ${code}.` }));
|
|
598
|
+
}
|
|
599
|
+
console.log(`\n${CONFIG.colors.cyan}Welcome back to Kora!${CONFIG.colors.reset}`);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
child.on('error', (err) => {
|
|
603
|
+
this.startReadline();
|
|
604
|
+
resolve(JSON.stringify({ success: false, error: err.message }));
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
}
|
|
418
608
|
|
|
609
|
+
async recoverSessions() {
|
|
610
|
+
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}Scanning for orphaned sessions to recover...`);
|
|
611
|
+
|
|
612
|
+
// Close readline interface
|
|
613
|
+
if (this.rl) {
|
|
614
|
+
this.rl.close();
|
|
615
|
+
this.rl = null;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const scriptPath = path.join(__dirname, 'session-coordinator.js');
|
|
619
|
+
|
|
620
|
+
return new Promise((resolve, reject) => {
|
|
621
|
+
const child = spawn('node', [scriptPath, 'recover'], {
|
|
622
|
+
stdio: 'inherit',
|
|
623
|
+
cwd: this.repoRoot
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
child.on('close', (code) => {
|
|
627
|
+
this.startReadline();
|
|
419
628
|
if (code === 0) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
629
|
+
// Instead of generic message, suggest checking status
|
|
630
|
+
resolve(JSON.stringify({
|
|
631
|
+
success: true,
|
|
632
|
+
message: "Recovery scan complete. Please run 'check_session_status' to see recovered sessions."
|
|
423
633
|
}));
|
|
424
634
|
} else {
|
|
425
|
-
resolve(JSON.stringify({
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
635
|
+
resolve(JSON.stringify({ success: false, message: `Recovery process exited with code ${code}.` }));
|
|
636
|
+
}
|
|
637
|
+
// Don't print "Welcome back" here to keep flow cleaner
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
child.on('error', (err) => {
|
|
641
|
+
this.startReadline();
|
|
642
|
+
resolve(JSON.stringify({ success: false, error: err.message }));
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
async resumeSession(args) {
|
|
647
|
+
const { sessionId, taskName } = args;
|
|
648
|
+
|
|
649
|
+
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}Resuming session: ${sessionId || taskName}...`);
|
|
650
|
+
|
|
651
|
+
// Close readline interface
|
|
652
|
+
if (this.rl) {
|
|
653
|
+
this.rl.close();
|
|
654
|
+
this.rl = null;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const scriptPath = path.join(__dirname, 'session-coordinator.js');
|
|
658
|
+
|
|
659
|
+
// Construct arguments for session coordinator
|
|
660
|
+
// We use the 'resume' command if we have an ID, or create-and-start with task if we're fuzzy matching
|
|
661
|
+
// But actually session-coordinator doesn't have a direct 'resume' command exposed easily via CLI args
|
|
662
|
+
// that jumps straight to monitoring without prompts, EXCEPT via the way createSession handles existing sessions.
|
|
663
|
+
// However, createSession with --task will prompt.
|
|
664
|
+
// Let's use a new approach: pass --resume-session-id if we have it.
|
|
665
|
+
|
|
666
|
+
// Wait, session-coordinator.js CLI handling (which I can't fully see but I saw 'create' and 'list')
|
|
667
|
+
// I need to check how to invoke resume.
|
|
668
|
+
// Looking at session-coordinator.js (which I read), it has 'requestSession' and 'createSession'.
|
|
669
|
+
// It doesn't seem to have a direct CLI command for 'resume' that takes an ID.
|
|
670
|
+
// However, 'create-and-start' (implied by startSession usage) might support it?
|
|
671
|
+
// In startSession: [scriptPath, 'create-and-start', '--task', taskName, '--skip-setup', '--skip-update']
|
|
672
|
+
|
|
673
|
+
// If I use 'create-and-start' with the SAME task name, it triggers the "Found existing session" logic
|
|
674
|
+
// but that logic is interactive (prompts Y/n).
|
|
675
|
+
|
|
676
|
+
// I should probably add a CLI command to session-coordinator.js to resume by ID non-interactively,
|
|
677
|
+
// OR just use the 'worker' command directly if I know the worktree?
|
|
678
|
+
// But the coordinator handles setting up the environment.
|
|
679
|
+
|
|
680
|
+
// Let's assume for now we can use a new 'resume' command in session-coordinator.js
|
|
681
|
+
// I will need to implement that in session-coordinator.js as well.
|
|
682
|
+
// But first let's implement the caller here.
|
|
683
|
+
|
|
684
|
+
const cmdArgs = ['resume', '--session-id', sessionId];
|
|
685
|
+
if (taskName) cmdArgs.push('--task', taskName);
|
|
686
|
+
|
|
687
|
+
return new Promise((resolve, reject) => {
|
|
688
|
+
const child = spawn('node', [scriptPath, ...cmdArgs], {
|
|
689
|
+
stdio: 'inherit',
|
|
690
|
+
cwd: this.repoRoot
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
child.on('close', (code) => {
|
|
694
|
+
this.startReadline();
|
|
695
|
+
if (code === 0) {
|
|
696
|
+
resolve(JSON.stringify({ success: true, message: "Session resumed successfully." }));
|
|
697
|
+
} else {
|
|
698
|
+
resolve(JSON.stringify({ success: false, message: `Session process exited with code ${code}.` }));
|
|
429
699
|
}
|
|
430
|
-
|
|
431
|
-
// Resume the chat interface after the child process exits
|
|
432
700
|
console.log(`\n${CONFIG.colors.cyan}Welcome back to Kora!${CONFIG.colors.reset}`);
|
|
433
701
|
});
|
|
434
702
|
|
|
435
703
|
child.on('error', (err) => {
|
|
436
|
-
// Re-initialize readline interface on error
|
|
437
704
|
this.startReadline();
|
|
438
|
-
|
|
439
|
-
resolve(JSON.stringify({
|
|
440
|
-
success: false,
|
|
441
|
-
error: err.message
|
|
442
|
-
}));
|
|
705
|
+
resolve(JSON.stringify({ success: false, error: err.message }));
|
|
443
706
|
});
|
|
444
707
|
});
|
|
445
708
|
}
|