s9n-devops-agent 2.0.14 → 2.0.18-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/House_Rules_Contracts/API_CONTRACT.md +612 -0
- package/House_Rules_Contracts/DATABASE_SCHEMA_CONTRACT.md +373 -0
- package/House_Rules_Contracts/DEVOPS_AGENT_INSTRUCTIONS.md +743 -0
- package/House_Rules_Contracts/FEATURES_CONTRACT.md +655 -0
- package/House_Rules_Contracts/INFRA_CONTRACT.md +638 -0
- package/House_Rules_Contracts/README.md +671 -0
- package/House_Rules_Contracts/SQL_CONTRACT.json +346 -0
- package/House_Rules_Contracts/THIRD_PARTY_INTEGRATIONS.md +604 -0
- package/README.md +93 -143
- package/bin/cs-devops-agent +20 -2
- package/houserules.md +1412 -0
- package/houserules_structured.md +1442 -0
- package/package.json +6 -2
- package/scripts/generate-ai-commit.js +135 -0
- package/scripts/local-install.sh +42 -0
- package/src/agent-chat.js +457 -0
- package/src/credentials-manager.js +1 -1
- package/src/cs-devops-agent-worker.js +84 -4
- package/src/house-rules-manager.js +4 -14
- package/src/session-coordinator.js +165 -15
- package/src/setup-cs-devops-agent.js +218 -242
- package/src/worktree-manager.js +2 -1
- package/start-devops-session.sh +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s9n-devops-agent",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18-dev.1",
|
|
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",
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
"src/",
|
|
13
13
|
"scripts/",
|
|
14
14
|
"docs/",
|
|
15
|
+
"House_Rules_Contracts/",
|
|
15
16
|
"start-devops-session.sh",
|
|
16
17
|
"cleanup-sessions.sh",
|
|
18
|
+
"houserules.md",
|
|
19
|
+
"houserules_structured.md",
|
|
17
20
|
"LICENSE",
|
|
18
21
|
"README.md"
|
|
19
22
|
],
|
|
@@ -25,6 +28,7 @@
|
|
|
25
28
|
"devops:start": "node src/session-coordinator.js start",
|
|
26
29
|
"devops:close": "node src/close-session.js",
|
|
27
30
|
"devops:cleanup": "./cleanup-sessions.sh",
|
|
31
|
+
"chat": "node src/agent-chat.js",
|
|
28
32
|
"setup": "node src/setup-cs-devops-agent.js",
|
|
29
33
|
"house-rules:status": "node src/house-rules-manager.js status",
|
|
30
34
|
"house-rules:update": "node src/house-rules-manager.js update",
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
"test:watch": "jest --watch",
|
|
34
38
|
"test:coverage": "jest --coverage",
|
|
35
39
|
"test:worktree": "jest test_cases/worktree/",
|
|
36
|
-
"test:e2e": "./test_scripts/test-multi-
|
|
40
|
+
"test:e2e": "./test_scripts/test-e2e-multi-session.sh",
|
|
37
41
|
"test:contracts": "jest tests/integration/contract-workflow.test.js",
|
|
38
42
|
"test:push-behind": "jest test_scripts/push_behind_spec.js",
|
|
39
43
|
"test:debug": "./debug-failing-tests.sh"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* AI COMMIT MESSAGE GENERATOR (via Groq/Llama 3)
|
|
6
|
+
* ============================================================================
|
|
7
|
+
*
|
|
8
|
+
* Generates conventional commit messages by analyzing staged changes.
|
|
9
|
+
* Enforces project House Rules and commit conventions.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/generate-ai-commit.js
|
|
13
|
+
* node scripts/generate-ai-commit.js --dry-run
|
|
14
|
+
*
|
|
15
|
+
* Configuration:
|
|
16
|
+
* Requires GROQ_API_KEY in .env or environment
|
|
17
|
+
* ============================================================================
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import fs from 'fs';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import { execSync } from 'child_process';
|
|
23
|
+
import Groq from 'groq-sdk';
|
|
24
|
+
import { credentialsManager } from '../src/credentials-manager.js';
|
|
25
|
+
|
|
26
|
+
// Inject credentials
|
|
27
|
+
credentialsManager.injectEnv();
|
|
28
|
+
|
|
29
|
+
const CONFIG = {
|
|
30
|
+
model: 'llama-3.1-70b-versatile',
|
|
31
|
+
maxDiffLength: 12000, // Characters
|
|
32
|
+
houseRulesPath: 'houserules.md'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
try {
|
|
37
|
+
// 1. Check for API Key
|
|
38
|
+
if (!process.env.GROQ_API_KEY) {
|
|
39
|
+
console.error('❌ GROQ_API_KEY not found. Cannot generate AI commit message.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
|
|
44
|
+
|
|
45
|
+
// 2. Get Staged Diff
|
|
46
|
+
let diff = '';
|
|
47
|
+
try {
|
|
48
|
+
diff = execSync('git diff --cached', { encoding: 'utf8', maxBuffer: 1024 * 1024 });
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error('❌ Failed to get git diff. Are there staged changes?');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!diff.trim()) {
|
|
55
|
+
console.log('ℹ️ No staged changes to analyze.');
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Truncate diff if too large
|
|
60
|
+
if (diff.length > CONFIG.maxDiffLength) {
|
|
61
|
+
diff = diff.substring(0, CONFIG.maxDiffLength) + '\n... (truncated)';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 3. Read House Rules (if available)
|
|
65
|
+
let houseRules = '';
|
|
66
|
+
if (fs.existsSync(CONFIG.houseRulesPath)) {
|
|
67
|
+
houseRules = fs.readFileSync(CONFIG.houseRulesPath, 'utf8');
|
|
68
|
+
// Truncate house rules to essential sections if needed to save context
|
|
69
|
+
houseRules = houseRules.substring(0, 5000);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 4. Construct Prompt
|
|
73
|
+
const prompt = `
|
|
74
|
+
You are a senior DevOps engineer and code reviewer.
|
|
75
|
+
Generate a conventional commit message for the following git diff.
|
|
76
|
+
|
|
77
|
+
CONTEXT:
|
|
78
|
+
- Project uses Semantic Versioning.
|
|
79
|
+
- Strict House Rules are in effect.
|
|
80
|
+
|
|
81
|
+
HOUSE RULES SUMMARY:
|
|
82
|
+
${houseRules}
|
|
83
|
+
|
|
84
|
+
INSTRUCTIONS:
|
|
85
|
+
1. Format: <type>(<scope>): <subject>
|
|
86
|
+
2. Followed by a blank line and a bulleted list of changes.
|
|
87
|
+
3. Type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
|
|
88
|
+
4. Scope is the affected module/component (optional).
|
|
89
|
+
5. Subject must be imperative, lowercase, no period.
|
|
90
|
+
6. Check the diff for any violations of House Rules (e.g. console.logs, hardcoded secrets) and mention them in the body if critical.
|
|
91
|
+
7. Output ONLY the commit message. No markdown blocks, no conversational text.
|
|
92
|
+
|
|
93
|
+
GIT DIFF:
|
|
94
|
+
${diff}
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
// 5. Call AI
|
|
98
|
+
console.log('🤖 Analyzing changes with Llama 3...');
|
|
99
|
+
const completion = await groq.chat.completions.create({
|
|
100
|
+
messages: [
|
|
101
|
+
{ role: 'system', content: 'You are a helpful coding assistant.' },
|
|
102
|
+
{ role: 'user', content: prompt }
|
|
103
|
+
],
|
|
104
|
+
model: CONFIG.model,
|
|
105
|
+
temperature: 0.2, // Low temperature for deterministic output
|
|
106
|
+
max_tokens: 500,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const commitMsg = completion.choices[0]?.message?.content?.trim();
|
|
110
|
+
|
|
111
|
+
if (!commitMsg) {
|
|
112
|
+
throw new Error('Received empty response from AI.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 6. Output or Save
|
|
116
|
+
// If --dry-run, just print
|
|
117
|
+
if (process.argv.includes('--dry-run')) {
|
|
118
|
+
console.log('\n--- Generated Message ---\n');
|
|
119
|
+
console.log(commitMsg);
|
|
120
|
+
console.log('\n-------------------------\n');
|
|
121
|
+
} else {
|
|
122
|
+
// Determine output file
|
|
123
|
+
const agentName = process.env.AGENT_NAME || 'claude';
|
|
124
|
+
const outputFile = `.${agentName.toLowerCase()}-commit-msg`;
|
|
125
|
+
fs.writeFileSync(outputFile, commitMsg);
|
|
126
|
+
console.log(`✅ Commit message written to ${outputFile}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`❌ Error generating commit message: ${error.message}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
main();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Local Installation Helper
|
|
5
|
+
# ============================================================================
|
|
6
|
+
# Installs the current version of DevOps Agent into a target repository
|
|
7
|
+
# for testing purposes without publishing to npm.
|
|
8
|
+
# ============================================================================
|
|
9
|
+
|
|
10
|
+
if [ -z "$1" ]; then
|
|
11
|
+
echo "Usage: $0 /path/to/target/repo"
|
|
12
|
+
echo "Example: $0 ../my-other-project"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
TARGET_REPO=$(realpath "$1")
|
|
17
|
+
CURRENT_DIR=$(pwd)
|
|
18
|
+
|
|
19
|
+
echo "📦 Packaging DevOps Agent..."
|
|
20
|
+
npm pack
|
|
21
|
+
|
|
22
|
+
PACKAGE_FILE=$(ls s9n-devops-agent-*.tgz)
|
|
23
|
+
|
|
24
|
+
if [ -z "$PACKAGE_FILE" ]; then
|
|
25
|
+
echo "❌ Error: Package file not found"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo "🚀 Installing into $TARGET_REPO..."
|
|
30
|
+
|
|
31
|
+
cd "$TARGET_REPO" || exit 1
|
|
32
|
+
|
|
33
|
+
# Install the tarball
|
|
34
|
+
npm install -g "$CURRENT_DIR/$PACKAGE_FILE"
|
|
35
|
+
|
|
36
|
+
# Clean up
|
|
37
|
+
cd "$CURRENT_DIR"
|
|
38
|
+
rm "$PACKAGE_FILE"
|
|
39
|
+
|
|
40
|
+
echo "✅ Installation complete!"
|
|
41
|
+
echo "You can now run 's9n-devops-agent' or 'devops' in the target repository."
|
|
42
|
+
echo "To test Kora, run: s9n-devops-agent chat"
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* SMART ASSISTANT - Conversational UX for DevOps Agent
|
|
6
|
+
* ============================================================================
|
|
7
|
+
*
|
|
8
|
+
* This module provides a conversational interface (Chat UX) for the DevOps Agent.
|
|
9
|
+
* It uses Groq LLM to understand user intent and execute agent commands.
|
|
10
|
+
*
|
|
11
|
+
* CAPABILITIES:
|
|
12
|
+
* - Answer questions about House Rules and Contracts
|
|
13
|
+
* - Help start sessions with proper naming and context
|
|
14
|
+
* - Analyze current project status
|
|
15
|
+
* - Guide users through the development workflow
|
|
16
|
+
*
|
|
17
|
+
* ============================================================================
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import fs from 'fs';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import readline from 'readline';
|
|
23
|
+
import Groq from 'groq-sdk';
|
|
24
|
+
import { fileURLToPath } from 'url';
|
|
25
|
+
import { dirname } from 'path';
|
|
26
|
+
import { spawn } from 'child_process';
|
|
27
|
+
import { credentialsManager } from './credentials-manager.js';
|
|
28
|
+
import HouseRulesManager from './house-rules-manager.js';
|
|
29
|
+
// We'll import SessionCoordinator dynamically to avoid circular deps if any
|
|
30
|
+
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
const __dirname = dirname(__filename);
|
|
33
|
+
|
|
34
|
+
// Initialize credentials
|
|
35
|
+
credentialsManager.injectEnv();
|
|
36
|
+
|
|
37
|
+
const CONFIG = {
|
|
38
|
+
model: 'llama-3.3-70b-versatile',
|
|
39
|
+
colors: {
|
|
40
|
+
reset: '\x1b[0m',
|
|
41
|
+
bright: '\x1b[1m',
|
|
42
|
+
dim: '\x1b[2m',
|
|
43
|
+
green: '\x1b[32m',
|
|
44
|
+
yellow: '\x1b[33m',
|
|
45
|
+
blue: '\x1b[36m',
|
|
46
|
+
magenta: '\x1b[35m',
|
|
47
|
+
cyan: '\x1b[36m',
|
|
48
|
+
red: '\x1b[31m'
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
class SmartAssistant {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.groq = new Groq({
|
|
55
|
+
apiKey: process.env.GROQ_API_KEY || process.env.OPENAI_API_KEY
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.history = [];
|
|
59
|
+
this.repoRoot = process.cwd();
|
|
60
|
+
this.houseRulesManager = new HouseRulesManager(this.repoRoot);
|
|
61
|
+
|
|
62
|
+
// Tools definition for the LLM
|
|
63
|
+
this.tools = [
|
|
64
|
+
{
|
|
65
|
+
type: "function",
|
|
66
|
+
function: {
|
|
67
|
+
name: "get_house_rules_summary",
|
|
68
|
+
description: "Get a summary of the current project's House Rules and folder structure",
|
|
69
|
+
parameters: { type: "object", properties: {} }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "function",
|
|
74
|
+
function: {
|
|
75
|
+
name: "list_contracts",
|
|
76
|
+
description: "List all available contract files and their completion status",
|
|
77
|
+
parameters: { type: "object", properties: {} }
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "function",
|
|
82
|
+
function: {
|
|
83
|
+
name: "start_session",
|
|
84
|
+
description: "Start a new development session with a specific task name",
|
|
85
|
+
parameters: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
taskName: { type: "string", description: "The name of the task (kebab-case preferred)" },
|
|
89
|
+
description: { type: "string", description: "Brief description of the task" }
|
|
90
|
+
},
|
|
91
|
+
required: ["taskName"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: "function",
|
|
97
|
+
function: {
|
|
98
|
+
name: "check_session_status",
|
|
99
|
+
description: "Check the status of active sessions and locks",
|
|
100
|
+
parameters: { type: "object", properties: {} }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
this.systemPrompt = `You are Kora, the Smart DevOps Assistant.
|
|
106
|
+
Your goal is to help developers follow the House Rules and Contract System while being helpful and efficient.
|
|
107
|
+
|
|
108
|
+
CONTEXT:
|
|
109
|
+
- You are running inside a "DevOps Agent" environment.
|
|
110
|
+
- The project follows a strict "Contract System" (API, DB, Features, etc.).
|
|
111
|
+
- Users need to create "Sessions" to do work.
|
|
112
|
+
- You can execute tools to help the user.
|
|
113
|
+
|
|
114
|
+
STYLE:
|
|
115
|
+
- Be concise but helpful.
|
|
116
|
+
- Identify yourself as "Kora".
|
|
117
|
+
- If the user asks about starting a task, ask for a clear task name if not provided.
|
|
118
|
+
- If the user asks about rules, summarize them from the actual files.
|
|
119
|
+
- Always prefer "Structured" organization for new code.
|
|
120
|
+
|
|
121
|
+
When you want to perform an action, use the available tools.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Initialize the chat session
|
|
126
|
+
*/
|
|
127
|
+
async start() {
|
|
128
|
+
// Check for Groq API Key
|
|
129
|
+
if (!credentialsManager.hasGroqApiKey()) {
|
|
130
|
+
console.log('\n' + '='.repeat(60));
|
|
131
|
+
console.log(`${CONFIG.colors.yellow}⚠️ GROQ API KEY MISSING${CONFIG.colors.reset}`);
|
|
132
|
+
console.log('='.repeat(60));
|
|
133
|
+
console.log('\nTo use Kora (Smart DevOps Assistant), you need a Groq API key.');
|
|
134
|
+
console.log('It allows Kora to understand your requests and help you manage sessions.\n');
|
|
135
|
+
|
|
136
|
+
const rl = readline.createInterface({
|
|
137
|
+
input: process.stdin,
|
|
138
|
+
output: process.stdout
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log(`${CONFIG.colors.bright}How to get a key:${CONFIG.colors.reset}`);
|
|
142
|
+
console.log(`1. Go to: ${CONFIG.colors.cyan}https://console.groq.com/keys${CONFIG.colors.reset}`);
|
|
143
|
+
console.log('2. Log in or sign up');
|
|
144
|
+
console.log('3. Click "Create API Key"');
|
|
145
|
+
console.log('4. Copy the key and paste it below\n');
|
|
146
|
+
|
|
147
|
+
const apiKey = await new Promise((resolve) => {
|
|
148
|
+
rl.question(`${CONFIG.colors.green}Enter your Groq API Key: ${CONFIG.colors.reset}`, (answer) => {
|
|
149
|
+
resolve(answer.trim());
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (apiKey) {
|
|
154
|
+
credentialsManager.setGroqApiKey(apiKey);
|
|
155
|
+
// Re-initialize Groq client with new key
|
|
156
|
+
this.groq = new Groq({
|
|
157
|
+
apiKey: apiKey
|
|
158
|
+
});
|
|
159
|
+
console.log(`\n${CONFIG.colors.green}✅ API Key saved successfully!${CONFIG.colors.reset}\n`);
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`\n${CONFIG.colors.red}❌ No key provided. Exiting.${CONFIG.colors.reset}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
rl.close();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('\n' + '='.repeat(60));
|
|
168
|
+
console.log(`${CONFIG.colors.magenta}🤖 Kora - Smart DevOps Assistant${CONFIG.colors.reset}`);
|
|
169
|
+
console.log(`${CONFIG.colors.dim}Powered by Groq (${CONFIG.model})${CONFIG.colors.reset}`);
|
|
170
|
+
console.log('='.repeat(60));
|
|
171
|
+
console.log(`\n${CONFIG.colors.cyan}Hi! I'm Kora. How can I help you today?${CONFIG.colors.reset}`);
|
|
172
|
+
console.log(`${CONFIG.colors.dim}(Try: "Start a new task for login", "Explain house rules", "Check contracts")${CONFIG.colors.reset}\n`);
|
|
173
|
+
|
|
174
|
+
this.startReadline();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
startReadline() {
|
|
178
|
+
if (this.rl) {
|
|
179
|
+
this.rl.close();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.rl = readline.createInterface({
|
|
183
|
+
input: process.stdin,
|
|
184
|
+
output: process.stdout,
|
|
185
|
+
prompt: `${CONFIG.colors.green}You > ${CONFIG.colors.reset}`
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
this.rl.prompt();
|
|
189
|
+
|
|
190
|
+
this.rl.on('line', async (line) => {
|
|
191
|
+
const input = line.trim();
|
|
192
|
+
if (!input) {
|
|
193
|
+
this.rl.prompt();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
198
|
+
console.log(`${CONFIG.colors.magenta}Goodbye!${CONFIG.colors.reset}`);
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await this.handleUserMessage(input);
|
|
203
|
+
// Only prompt if rl is still active (it might be closed if starting a session)
|
|
204
|
+
if (this.rl && !this.rl.closed) {
|
|
205
|
+
this.rl.prompt();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Handle a user message through the LLM
|
|
212
|
+
*/
|
|
213
|
+
async handleUserMessage(content) {
|
|
214
|
+
// Add user message to history
|
|
215
|
+
this.history.push({ role: 'user', content });
|
|
216
|
+
|
|
217
|
+
// Pause readline while thinking/executing
|
|
218
|
+
if (this.rl) {
|
|
219
|
+
this.rl.pause();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
process.stdout.write(`${CONFIG.colors.dim}Thinking...${CONFIG.colors.reset}`);
|
|
224
|
+
|
|
225
|
+
const response = await this.groq.chat.completions.create({
|
|
226
|
+
model: CONFIG.model,
|
|
227
|
+
messages: [
|
|
228
|
+
{ role: 'system', content: this.systemPrompt },
|
|
229
|
+
...this.history
|
|
230
|
+
],
|
|
231
|
+
tools: this.tools,
|
|
232
|
+
tool_choice: "auto",
|
|
233
|
+
temperature: 0.5,
|
|
234
|
+
max_tokens: 1024
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const message = response.choices[0].message;
|
|
238
|
+
|
|
239
|
+
// Clear "Thinking..."
|
|
240
|
+
readline.cursorTo(process.stdout, 0);
|
|
241
|
+
readline.clearLine(process.stdout, 0);
|
|
242
|
+
|
|
243
|
+
if (message.tool_calls) {
|
|
244
|
+
// Handle tool calls
|
|
245
|
+
await this.handleToolCalls(message.tool_calls);
|
|
246
|
+
} else {
|
|
247
|
+
// Just a text response
|
|
248
|
+
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}${message.content}\n`);
|
|
249
|
+
this.history.push(message);
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
readline.cursorTo(process.stdout, 0);
|
|
253
|
+
readline.clearLine(process.stdout, 0);
|
|
254
|
+
console.error(`${CONFIG.colors.red}Error: ${error.message}${CONFIG.colors.reset}\n`);
|
|
255
|
+
} finally {
|
|
256
|
+
// Resume readline if it exists and we're not starting a session (which handles its own RL)
|
|
257
|
+
if (this.rl && !this.rl.closed) {
|
|
258
|
+
this.rl.resume();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Execute tool calls from the LLM
|
|
265
|
+
*/
|
|
266
|
+
async handleToolCalls(toolCalls) {
|
|
267
|
+
// Add the assistant's message with tool calls to history
|
|
268
|
+
this.history.push({
|
|
269
|
+
role: 'assistant',
|
|
270
|
+
content: null,
|
|
271
|
+
tool_calls: toolCalls
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
for (const toolCall of toolCalls) {
|
|
275
|
+
const functionName = toolCall.function.name;
|
|
276
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
277
|
+
|
|
278
|
+
console.log(`${CONFIG.colors.dim}Executing: ${functionName}...${CONFIG.colors.reset}`);
|
|
279
|
+
|
|
280
|
+
let result;
|
|
281
|
+
try {
|
|
282
|
+
switch (functionName) {
|
|
283
|
+
case 'get_house_rules_summary':
|
|
284
|
+
result = await this.getHouseRulesSummary();
|
|
285
|
+
break;
|
|
286
|
+
case 'list_contracts':
|
|
287
|
+
result = await this.listContracts();
|
|
288
|
+
break;
|
|
289
|
+
case 'check_session_status':
|
|
290
|
+
result = await this.checkSessionStatus();
|
|
291
|
+
break;
|
|
292
|
+
case 'start_session':
|
|
293
|
+
result = await this.startSession(args);
|
|
294
|
+
break;
|
|
295
|
+
default:
|
|
296
|
+
result = JSON.stringify({ error: "Unknown tool" });
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
result = JSON.stringify({ error: err.message });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Add tool result to history
|
|
303
|
+
this.history.push({
|
|
304
|
+
tool_call_id: toolCall.id,
|
|
305
|
+
role: "tool",
|
|
306
|
+
name: functionName,
|
|
307
|
+
content: result
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Get final response from LLM after tool execution
|
|
312
|
+
try {
|
|
313
|
+
const response = await this.groq.chat.completions.create({
|
|
314
|
+
model: CONFIG.model,
|
|
315
|
+
messages: [
|
|
316
|
+
{ role: 'system', content: this.systemPrompt },
|
|
317
|
+
...this.history
|
|
318
|
+
]
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const finalMessage = response.choices[0].message;
|
|
322
|
+
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}${finalMessage.content}\n`);
|
|
323
|
+
this.history.push(finalMessage);
|
|
324
|
+
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error(`${CONFIG.colors.red}Error getting final response: ${error.message}${CONFIG.colors.reset}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ==========================================================================
|
|
331
|
+
// TOOL IMPLEMENTATIONS
|
|
332
|
+
// ==========================================================================
|
|
333
|
+
|
|
334
|
+
async getHouseRulesSummary() {
|
|
335
|
+
const status = this.houseRulesManager.getStatus();
|
|
336
|
+
const rulesPath = this.houseRulesManager.houseRulesPath;
|
|
337
|
+
|
|
338
|
+
if (fs.existsSync(rulesPath)) {
|
|
339
|
+
const content = fs.readFileSync(rulesPath, 'utf8');
|
|
340
|
+
// Extract first 50 lines or so for context
|
|
341
|
+
const summary = content.split('\n').slice(0, 50).join('\n');
|
|
342
|
+
return JSON.stringify({
|
|
343
|
+
exists: true,
|
|
344
|
+
status: status,
|
|
345
|
+
preview: summary,
|
|
346
|
+
path: rulesPath
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return JSON.stringify({ exists: false, message: "House Rules file not found." });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async listContracts() {
|
|
353
|
+
const contractsDir = path.join(this.repoRoot, 'House_Rules_Contracts');
|
|
354
|
+
if (!fs.existsSync(contractsDir)) {
|
|
355
|
+
return JSON.stringify({ exists: false, message: "Contracts folder not found." });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const files = fs.readdirSync(contractsDir).filter(f => f.endsWith('.md') || f.endsWith('.json'));
|
|
359
|
+
return JSON.stringify({
|
|
360
|
+
exists: true,
|
|
361
|
+
files: files,
|
|
362
|
+
location: contractsDir
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async checkSessionStatus() {
|
|
367
|
+
// We need to import SessionCoordinator here to avoid top-level await/circular deps issues
|
|
368
|
+
// Note: In a real implementation we might want to refactor SessionCoordinator to be more modular
|
|
369
|
+
// For now, we'll check the file system directly which is safer/faster for this tool
|
|
370
|
+
|
|
371
|
+
const sessionsDir = path.join(this.repoRoot, 'local_deploy/sessions');
|
|
372
|
+
const locksDir = path.join(this.repoRoot, 'local_deploy/session-locks');
|
|
373
|
+
|
|
374
|
+
let activeSessions = [];
|
|
375
|
+
let activeLocks = [];
|
|
376
|
+
|
|
377
|
+
if (fs.existsSync(sessionsDir)) {
|
|
378
|
+
activeSessions = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (fs.existsSync(locksDir)) {
|
|
382
|
+
activeLocks = fs.readdirSync(locksDir);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return JSON.stringify({
|
|
386
|
+
activeSessionsCount: activeSessions.length,
|
|
387
|
+
activeLocksCount: activeLocks.length,
|
|
388
|
+
sessions: activeSessions,
|
|
389
|
+
locks: activeLocks
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async startSession(args) {
|
|
394
|
+
const taskName = args.taskName;
|
|
395
|
+
|
|
396
|
+
console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}Starting new session for: ${taskName}...`);
|
|
397
|
+
|
|
398
|
+
// Close readline interface to release stdin for the child process
|
|
399
|
+
if (this.rl) {
|
|
400
|
+
this.rl.close();
|
|
401
|
+
this.rl = null;
|
|
402
|
+
}
|
|
403
|
+
|
|
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
|
+
const scriptPath = path.join(__dirname, 'session-coordinator.js');
|
|
407
|
+
|
|
408
|
+
return new Promise((resolve, reject) => {
|
|
409
|
+
// Use 'inherit' for stdio to allow interactive input/output
|
|
410
|
+
const child = spawn('node', [scriptPath, 'create-and-start', '--task', taskName], {
|
|
411
|
+
stdio: 'inherit',
|
|
412
|
+
cwd: this.repoRoot
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
child.on('close', (code) => {
|
|
416
|
+
// Re-initialize readline interface after child process exits
|
|
417
|
+
this.startReadline();
|
|
418
|
+
|
|
419
|
+
if (code === 0) {
|
|
420
|
+
resolve(JSON.stringify({
|
|
421
|
+
success: true,
|
|
422
|
+
message: `Session for '${taskName}' completed successfully.`
|
|
423
|
+
}));
|
|
424
|
+
} else {
|
|
425
|
+
resolve(JSON.stringify({
|
|
426
|
+
success: false,
|
|
427
|
+
message: `Session process exited with code ${code}.`
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Resume the chat interface after the child process exits
|
|
432
|
+
console.log(`\n${CONFIG.colors.cyan}Welcome back to Kora!${CONFIG.colors.reset}`);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
child.on('error', (err) => {
|
|
436
|
+
// Re-initialize readline interface on error
|
|
437
|
+
this.startReadline();
|
|
438
|
+
|
|
439
|
+
resolve(JSON.stringify({
|
|
440
|
+
success: false,
|
|
441
|
+
error: err.message
|
|
442
|
+
}));
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export { SmartAssistant };
|
|
449
|
+
|
|
450
|
+
// Run the assistant only if executed directly
|
|
451
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
452
|
+
const assistant = new SmartAssistant();
|
|
453
|
+
assistant.start().catch(err => {
|
|
454
|
+
console.error('Fatal Error:', err);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
7
7
|
const __dirname = dirname(__filename);
|
|
8
8
|
const rootDir = path.join(__dirname, '..');
|
|
9
9
|
|
|
10
|
-
const CREDENTIALS_PATH = path.join(rootDir, 'local_deploy', 'credentials.json');
|
|
10
|
+
const CREDENTIALS_PATH = process.env.DEVOPS_CREDENTIALS_PATH || path.join(rootDir, 'local_deploy', 'credentials.json');
|
|
11
11
|
|
|
12
12
|
// Simple obfuscation to prevent casual shoulder surfing
|
|
13
13
|
// NOTE: This is NOT strong encryption. In a production environment with sensitive keys,
|