@kernel.chat/kbot 3.33.1 → 3.34.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/dist/agent-create.d.ts +14 -0
- package/dist/agent-create.d.ts.map +1 -0
- package/dist/agent-create.js +256 -0
- package/dist/agent-create.js.map +1 -0
- package/dist/cli.js +36 -0
- package/dist/cli.js.map +1 -1
- package/dist/compete.d.ts +2 -0
- package/dist/compete.d.ts.map +1 -0
- package/dist/compete.js +233 -0
- package/dist/compete.js.map +1 -0
- package/dist/dashboard.d.ts +2 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +277 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/digest.d.ts +19 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +77 -0
- package/dist/digest.js.map +1 -0
- package/dist/forge-registry.d.ts +16 -0
- package/dist/forge-registry.d.ts.map +1 -0
- package/dist/forge-registry.js +103 -0
- package/dist/forge-registry.js.map +1 -0
- package/dist/init.d.ts +23 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +143 -4
- package/dist/init.js.map +1 -1
- package/dist/init.test.js +2 -0
- package/dist/init.test.js.map +1 -1
- package/dist/openclaw-connect.d.ts +2 -0
- package/dist/openclaw-connect.d.ts.map +1 -0
- package/dist/openclaw-connect.js +196 -0
- package/dist/openclaw-connect.js.map +1 -0
- package/dist/pair.d.ts +22 -0
- package/dist/pair.d.ts.map +1 -1
- package/dist/pair.js +275 -2
- package/dist/pair.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// kbot OpenClaw Connect — One-command OpenClaw Gateway setup
|
|
2
|
+
//
|
|
3
|
+
// Checks gateway availability, creates a SOUL.md agent definition,
|
|
4
|
+
// and verifies connectivity with a test message.
|
|
5
|
+
//
|
|
6
|
+
// Usage: kbot openclaw connect
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { KBOT_DIR } from './auth.js';
|
|
11
|
+
// ── Constants ──
|
|
12
|
+
const DEFAULT_GATEWAY_URL = 'http://127.0.0.1:18789';
|
|
13
|
+
const GATEWAY_URL = process.env.OPENCLAW_GATEWAY_URL || DEFAULT_GATEWAY_URL;
|
|
14
|
+
const SOUL_PATH = join(KBOT_DIR, 'openclaw-soul.md');
|
|
15
|
+
// ── Helpers ──
|
|
16
|
+
/** Check if the OpenClaw Gateway is reachable */
|
|
17
|
+
async function checkGateway() {
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(`${GATEWAY_URL}/health`, {
|
|
20
|
+
signal: AbortSignal.timeout(5000),
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
return null;
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Fetch full gateway status (models, platforms, etc.) */
|
|
32
|
+
async function fetchGatewayStatus() {
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(`${GATEWAY_URL}/status`, {
|
|
35
|
+
signal: AbortSignal.timeout(5000),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok)
|
|
38
|
+
return {};
|
|
39
|
+
return await res.json();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Send a test message to verify connectivity */
|
|
46
|
+
async function sendTestMessage() {
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(`${GATEWAY_URL}/v1/chat/completions`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
model: 'auto',
|
|
53
|
+
messages: [
|
|
54
|
+
{ role: 'system', content: 'You are kbot, confirming OpenClaw connectivity. Respond with exactly: connected' },
|
|
55
|
+
{ role: 'user', content: 'ping' },
|
|
56
|
+
],
|
|
57
|
+
max_tokens: 16,
|
|
58
|
+
}),
|
|
59
|
+
signal: AbortSignal.timeout(10000),
|
|
60
|
+
});
|
|
61
|
+
return res.ok;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ── SOUL.md Generation ──
|
|
68
|
+
const SPECIALIST_IDS = [
|
|
69
|
+
'kernel', 'researcher', 'coder', 'writer', 'analyst',
|
|
70
|
+
'aesthete', 'guardian', 'curator', 'strategist',
|
|
71
|
+
'infrastructure', 'quant', 'investigator', 'oracle',
|
|
72
|
+
'chronist', 'sage', 'communicator', 'adapter', 'immune',
|
|
73
|
+
];
|
|
74
|
+
function generateSoulMd() {
|
|
75
|
+
const specialistList = SPECIALIST_IDS
|
|
76
|
+
.map(id => `- **${id}** — specialist agent`)
|
|
77
|
+
.join('\n');
|
|
78
|
+
return `# SOUL.md — kbot OpenClaw Agent Definition
|
|
79
|
+
|
|
80
|
+
## Identity
|
|
81
|
+
|
|
82
|
+
- **Name**: kbot
|
|
83
|
+
- **Type**: Terminal AI agent
|
|
84
|
+
- **Version**: OpenClaw-connected
|
|
85
|
+
- **Source**: https://github.com/isaacsight/kernel
|
|
86
|
+
- **License**: MIT
|
|
87
|
+
|
|
88
|
+
## Purpose
|
|
89
|
+
|
|
90
|
+
kbot is an open-source terminal AI agent that connects to the OpenClaw Gateway
|
|
91
|
+
to access local and remote AI models. It brings ${SPECIALIST_IDS.length} specialist agents,
|
|
92
|
+
290+ tools, and 20 AI providers into a single CLI interface.
|
|
93
|
+
|
|
94
|
+
## Specialists
|
|
95
|
+
|
|
96
|
+
${specialistList}
|
|
97
|
+
|
|
98
|
+
## Capabilities
|
|
99
|
+
|
|
100
|
+
- Multi-provider AI (Anthropic, OpenAI, Google, + 17 more)
|
|
101
|
+
- 290+ built-in tools (file ops, git, GitHub, web search, browser, sandbox)
|
|
102
|
+
- Autonomous planning and execution
|
|
103
|
+
- Learning engine (patterns, solutions, user profile)
|
|
104
|
+
- Session persistence and memory synthesis
|
|
105
|
+
- MCP server/client support
|
|
106
|
+
- Local model support (Ollama, LM Studio, embedded llama.cpp)
|
|
107
|
+
|
|
108
|
+
## OpenClaw Integration
|
|
109
|
+
|
|
110
|
+
- **Gateway URL**: ${GATEWAY_URL}
|
|
111
|
+
- **Protocol**: OpenAI-compatible chat completions (v1/chat/completions)
|
|
112
|
+
- **Agent routing**: kbot routes to specialists based on intent classification
|
|
113
|
+
- **Tool execution**: Local-first, then API calls as needed
|
|
114
|
+
- **Memory**: Persistent across sessions at ~/.kbot/
|
|
115
|
+
|
|
116
|
+
## Interaction Style
|
|
117
|
+
|
|
118
|
+
- Act, don't advise — execute tasks directly
|
|
119
|
+
- Local-first — use local tools before making API calls
|
|
120
|
+
- Failures trigger fallbacks, not stops
|
|
121
|
+
- Route work to the right specialist
|
|
122
|
+
- Compound improvements — each session leaves things better
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
// ── Main ──
|
|
126
|
+
export async function runOpenClawConnect() {
|
|
127
|
+
const DIM = chalk.dim;
|
|
128
|
+
const ACCENT = chalk.hex('#A78BFA');
|
|
129
|
+
const GREEN = chalk.hex('#4ADE80');
|
|
130
|
+
const RED = chalk.hex('#F87171');
|
|
131
|
+
const CYAN = chalk.hex('#67E8F9');
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(` ${ACCENT('◉')} ${chalk.bold('kbot OpenClaw Connect')}`);
|
|
134
|
+
console.log(` ${DIM('─'.repeat(40))}`);
|
|
135
|
+
console.log();
|
|
136
|
+
// Step 1: Check if gateway is running
|
|
137
|
+
console.log(` ${DIM('Checking OpenClaw Gateway at')} ${CYAN(GATEWAY_URL)}${DIM('...')}`);
|
|
138
|
+
const health = await checkGateway();
|
|
139
|
+
if (!health) {
|
|
140
|
+
console.log();
|
|
141
|
+
console.log(` ${RED('✗')} OpenClaw Gateway is not running.`);
|
|
142
|
+
console.log();
|
|
143
|
+
console.log(` ${chalk.bold('To install and start OpenClaw:')}`);
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(` ${CYAN('1.')} Install the gateway:`);
|
|
146
|
+
console.log(` ${chalk.white('pip install openclaw')}`);
|
|
147
|
+
console.log(` ${DIM('or: brew install openclaw/tap/openclaw')}`);
|
|
148
|
+
console.log();
|
|
149
|
+
console.log(` ${CYAN('2.')} Start the gateway:`);
|
|
150
|
+
console.log(` ${chalk.white('openclaw serve')}`);
|
|
151
|
+
console.log(` ${DIM('Default: http://127.0.0.1:18789')}`);
|
|
152
|
+
console.log();
|
|
153
|
+
console.log(` ${CYAN('3.')} Set a custom URL (optional):`);
|
|
154
|
+
console.log(` ${chalk.white('export OPENCLAW_GATEWAY_URL=http://your-host:port')}`);
|
|
155
|
+
console.log();
|
|
156
|
+
console.log(` ${DIM('Then run')} ${chalk.white('kbot openclaw connect')} ${DIM('again.')}`);
|
|
157
|
+
console.log();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log(` ${GREEN('✓')} Gateway is running${health.version ? ` (v${health.version})` : ''}`);
|
|
161
|
+
// Step 2: Fetch full status
|
|
162
|
+
const status = await fetchGatewayStatus();
|
|
163
|
+
const platforms = status.platforms || ['local'];
|
|
164
|
+
const models = status.models || [];
|
|
165
|
+
if (models.length > 0) {
|
|
166
|
+
console.log(` ${GREEN('✓')} Models available: ${models.slice(0, 5).join(', ')}${models.length > 5 ? ` +${models.length - 5} more` : ''}`);
|
|
167
|
+
}
|
|
168
|
+
// Step 3: Create SOUL.md
|
|
169
|
+
if (!existsSync(KBOT_DIR)) {
|
|
170
|
+
mkdirSync(KBOT_DIR, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
const soulContent = generateSoulMd();
|
|
173
|
+
writeFileSync(SOUL_PATH, soulContent, 'utf-8');
|
|
174
|
+
console.log(` ${GREEN('✓')} Agent definition saved to ${DIM(SOUL_PATH)}`);
|
|
175
|
+
// Step 4: Send test message
|
|
176
|
+
console.log(` ${DIM('Sending test message...')}`);
|
|
177
|
+
const testOk = await sendTestMessage();
|
|
178
|
+
if (testOk) {
|
|
179
|
+
console.log(` ${GREEN('✓')} Connectivity verified`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(` ${chalk.hex('#FBBF24')('⚠')} Test message failed — gateway is up but may not have a model loaded.`);
|
|
183
|
+
console.log(` ${DIM('Try: ollama pull llama3.1:8b')}`);
|
|
184
|
+
}
|
|
185
|
+
// Step 5: Print success summary
|
|
186
|
+
console.log();
|
|
187
|
+
console.log(` ${GREEN('kbot is now connected to OpenClaw.')}`);
|
|
188
|
+
console.log(` ${DIM('Your agents are available on:')} ${platforms.map(p => chalk.white(p)).join(', ')}`);
|
|
189
|
+
console.log();
|
|
190
|
+
console.log(` ${DIM('Usage:')}`);
|
|
191
|
+
console.log(` ${chalk.white('kbot')} ${DIM('— start interactive session via OpenClaw')}`);
|
|
192
|
+
console.log(` ${chalk.white('kbot --agent coder')} ${DIM('— use a specific specialist')}`);
|
|
193
|
+
console.log(` ${chalk.white('kbot openclaw status')} ${DIM('— check gateway status')}`);
|
|
194
|
+
console.log();
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=openclaw-connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-connect.js","sourceRoot":"","sources":["../src/openclaw-connect.ts"],"names":[],"mappings":"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"}
|
package/dist/pair.d.ts
CHANGED
|
@@ -47,7 +47,17 @@ declare let sessionStats: {
|
|
|
47
47
|
suggestionsShown: number;
|
|
48
48
|
fixesApplied: number;
|
|
49
49
|
errorsFound: number;
|
|
50
|
+
testsRun: number;
|
|
51
|
+
testsFailed: number;
|
|
50
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Get files that frequently change together with the given file.
|
|
55
|
+
* Returns top co-changing files sorted by frequency.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getCoChangePatterns(targetFile?: string): Array<{
|
|
58
|
+
pair: [string, string];
|
|
59
|
+
count: number;
|
|
60
|
+
}>;
|
|
51
61
|
export declare function startPairMode(options?: PairOptions): Promise<void>;
|
|
52
62
|
export declare function stopPairMode(): void;
|
|
53
63
|
/**
|
|
@@ -69,6 +79,18 @@ export declare function analyzeWithAgent(filePath: string, agentOptions?: {
|
|
|
69
79
|
model?: string;
|
|
70
80
|
tier?: string;
|
|
71
81
|
}): Promise<string>;
|
|
82
|
+
/**
|
|
83
|
+
* runPair() — Start AI pair programming watch mode.
|
|
84
|
+
*
|
|
85
|
+
* 1. Watches cwd (or specified path) for file changes using fs.watch
|
|
86
|
+
* 2. When .ts/.js/.py/.rs/.go files change:
|
|
87
|
+
* - Runs the relevant test if one exists (detects vitest/jest/pytest/cargo/go)
|
|
88
|
+
* - If test fails, shows the failure with fix suggestions
|
|
89
|
+
* - Tracks which files change together (co-change pattern extraction)
|
|
90
|
+
* 3. Simple terminal output: file changed, action taken, result
|
|
91
|
+
* 4. Runs until Ctrl+C
|
|
92
|
+
*/
|
|
93
|
+
export declare function runPair(path?: string, options?: Omit<PairOptions, 'path'>): Promise<void>;
|
|
72
94
|
/**
|
|
73
95
|
* Register the `kbot pair` command with the CLI program.
|
|
74
96
|
*
|
package/dist/pair.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../src/pair.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAMxC,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,mCAAmC;IACnC,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,+CAA+C;IAC/C,YAAY,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACjE;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAA;IAC1C,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI,CAAA;IAC3E,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAA;IAClB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;CACf;AA4GD,QAAA,IAAI,YAAY
|
|
1
|
+
{"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../src/pair.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAMxC,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,mCAAmC;IACnC,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,+CAA+C;IAC/C,YAAY,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACjE;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAA;IAC1C,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI,CAAA;IAC3E,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAA;IAClB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;CACf;AA4GD,QAAA,IAAI,YAAY;;;;;;;CAA0G,CAAA;AAmB1H;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBzG;AAw8BD,wBAAsB,aAAa,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+G5E;AAED,wBAAgB,YAAY,IAAI,IAAI,CAmCnC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,YAAY,CAElD;AAMD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/F;AAMD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmE1D"}
|
package/dist/pair.js
CHANGED
|
@@ -103,7 +103,243 @@ const SECURITY_PATTERNS = [
|
|
|
103
103
|
let activeWatcher = null;
|
|
104
104
|
let debounceTimer = null;
|
|
105
105
|
const pendingChanges = new Set();
|
|
106
|
-
let sessionStats = { filesAnalyzed: 0, suggestionsShown: 0, fixesApplied: 0, errorsFound: 0 };
|
|
106
|
+
let sessionStats = { filesAnalyzed: 0, suggestionsShown: 0, fixesApplied: 0, errorsFound: 0, testsRun: 0, testsFailed: 0 };
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Co-change pattern tracking
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/** Tracks which files change together within a short time window. */
|
|
111
|
+
const coChangeHistory = [];
|
|
112
|
+
const CO_CHANGE_WINDOW_MS = 5000; // files changing within 5s are "together"
|
|
113
|
+
const MAX_CO_CHANGE_HISTORY = 200;
|
|
114
|
+
function recordCoChange(files) {
|
|
115
|
+
if (files.length < 2)
|
|
116
|
+
return;
|
|
117
|
+
coChangeHistory.push({ files: files.sort(), timestamp: Date.now() });
|
|
118
|
+
if (coChangeHistory.length > MAX_CO_CHANGE_HISTORY) {
|
|
119
|
+
coChangeHistory.splice(0, coChangeHistory.length - MAX_CO_CHANGE_HISTORY);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get files that frequently change together with the given file.
|
|
124
|
+
* Returns top co-changing files sorted by frequency.
|
|
125
|
+
*/
|
|
126
|
+
export function getCoChangePatterns(targetFile) {
|
|
127
|
+
const pairCounts = new Map();
|
|
128
|
+
for (const entry of coChangeHistory) {
|
|
129
|
+
for (let i = 0; i < entry.files.length; i++) {
|
|
130
|
+
for (let j = i + 1; j < entry.files.length; j++) {
|
|
131
|
+
if (targetFile && entry.files[i] !== targetFile && entry.files[j] !== targetFile)
|
|
132
|
+
continue;
|
|
133
|
+
const key = `${entry.files[i]}||${entry.files[j]}`;
|
|
134
|
+
pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Array.from(pairCounts.entries())
|
|
139
|
+
.map(([key, count]) => {
|
|
140
|
+
const [a, b] = key.split('||');
|
|
141
|
+
return { pair: [a, b], count };
|
|
142
|
+
})
|
|
143
|
+
.filter(e => e.count >= 2)
|
|
144
|
+
.sort((a, b) => b.count - a.count)
|
|
145
|
+
.slice(0, 10);
|
|
146
|
+
}
|
|
147
|
+
function detectTestRunner(projectRoot) {
|
|
148
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
149
|
+
if (existsSync(pkgPath)) {
|
|
150
|
+
try {
|
|
151
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
152
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
153
|
+
const scripts = pkg.scripts || {};
|
|
154
|
+
// Vitest
|
|
155
|
+
if (deps.vitest || (scripts.test && scripts.test.includes('vitest'))) {
|
|
156
|
+
return {
|
|
157
|
+
name: 'vitest',
|
|
158
|
+
command: 'npx vitest run',
|
|
159
|
+
singleFileCommand: (f) => `npx vitest run "${f}" --reporter=verbose`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Jest
|
|
163
|
+
if (deps.jest || deps['@jest/core'] || (scripts.test && scripts.test.includes('jest'))) {
|
|
164
|
+
return {
|
|
165
|
+
name: 'jest',
|
|
166
|
+
command: 'npx jest',
|
|
167
|
+
singleFileCommand: (f) => `npx jest "${f}" --verbose`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Mocha
|
|
171
|
+
if (deps.mocha) {
|
|
172
|
+
return {
|
|
173
|
+
name: 'mocha',
|
|
174
|
+
command: 'npx mocha',
|
|
175
|
+
singleFileCommand: (f) => `npx mocha "${f}"`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch { /* ignore */ }
|
|
180
|
+
}
|
|
181
|
+
// Python: pytest
|
|
182
|
+
if (existsSync(join(projectRoot, 'pytest.ini')) ||
|
|
183
|
+
existsSync(join(projectRoot, 'pyproject.toml')) ||
|
|
184
|
+
existsSync(join(projectRoot, 'setup.cfg'))) {
|
|
185
|
+
try {
|
|
186
|
+
const content = existsSync(join(projectRoot, 'pyproject.toml'))
|
|
187
|
+
? readFileSync(join(projectRoot, 'pyproject.toml'), 'utf-8')
|
|
188
|
+
: '';
|
|
189
|
+
if (content.includes('pytest') || existsSync(join(projectRoot, 'pytest.ini'))) {
|
|
190
|
+
return {
|
|
191
|
+
name: 'pytest',
|
|
192
|
+
command: 'python -m pytest',
|
|
193
|
+
singleFileCommand: (f) => `python -m pytest "${f}" -v`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch { /* ignore */ }
|
|
198
|
+
}
|
|
199
|
+
// Rust: cargo test
|
|
200
|
+
if (existsSync(join(projectRoot, 'Cargo.toml'))) {
|
|
201
|
+
return {
|
|
202
|
+
name: 'cargo test',
|
|
203
|
+
command: 'cargo test',
|
|
204
|
+
singleFileCommand: (_f) => 'cargo test',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// Go: go test
|
|
208
|
+
if (existsSync(join(projectRoot, 'go.mod'))) {
|
|
209
|
+
return {
|
|
210
|
+
name: 'go test',
|
|
211
|
+
command: 'go test ./...',
|
|
212
|
+
singleFileCommand: (f) => `go test -v -run . "${dirname(f)}"`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Find the test file corresponding to a source file, if it exists.
|
|
219
|
+
*/
|
|
220
|
+
function findTestFile(sourcePath) {
|
|
221
|
+
const ext = extname(sourcePath);
|
|
222
|
+
const dir = dirname(sourcePath);
|
|
223
|
+
const nameWithoutExt = basename(sourcePath, ext);
|
|
224
|
+
// Skip test/spec files themselves
|
|
225
|
+
if (nameWithoutExt.endsWith('.test') || nameWithoutExt.endsWith('.spec'))
|
|
226
|
+
return null;
|
|
227
|
+
if (nameWithoutExt.endsWith('_test'))
|
|
228
|
+
return null;
|
|
229
|
+
// JS/TS patterns
|
|
230
|
+
const jsTsPatterns = [
|
|
231
|
+
join(dir, `${nameWithoutExt}.test${ext}`),
|
|
232
|
+
join(dir, `${nameWithoutExt}.spec${ext}`),
|
|
233
|
+
join(dir, `${nameWithoutExt}.test.ts`),
|
|
234
|
+
join(dir, `${nameWithoutExt}.spec.ts`),
|
|
235
|
+
join(dir, '__tests__', `${nameWithoutExt}.test${ext}`),
|
|
236
|
+
join(dir, '__tests__', `${nameWithoutExt}.spec${ext}`),
|
|
237
|
+
];
|
|
238
|
+
// Python patterns
|
|
239
|
+
const pyPatterns = [
|
|
240
|
+
join(dir, `test_${nameWithoutExt}.py`),
|
|
241
|
+
join(dir, `${nameWithoutExt}_test.py`),
|
|
242
|
+
join(dir, 'tests', `test_${nameWithoutExt}.py`),
|
|
243
|
+
];
|
|
244
|
+
// Go patterns
|
|
245
|
+
const goPatterns = [
|
|
246
|
+
join(dir, `${nameWithoutExt}_test.go`),
|
|
247
|
+
];
|
|
248
|
+
// Rust tests are inline (in the same file), so just return the source
|
|
249
|
+
if (ext === '.rs') {
|
|
250
|
+
try {
|
|
251
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
252
|
+
if (content.includes('#[cfg(test)]') || content.includes('#[test]')) {
|
|
253
|
+
return sourcePath;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch { /* ignore */ }
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const allPatterns = [...jsTsPatterns, ...pyPatterns, ...goPatterns];
|
|
260
|
+
for (const p of allPatterns) {
|
|
261
|
+
if (existsSync(p))
|
|
262
|
+
return p;
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Run the test file for a changed source file.
|
|
268
|
+
* Returns suggestions about the test result.
|
|
269
|
+
*/
|
|
270
|
+
function runTestForFile(change) {
|
|
271
|
+
const projectRoot = findProjectRoot(change.fullPath);
|
|
272
|
+
if (!projectRoot)
|
|
273
|
+
return [];
|
|
274
|
+
const testRunner = detectTestRunner(projectRoot);
|
|
275
|
+
if (!testRunner)
|
|
276
|
+
return [];
|
|
277
|
+
const testFile = findTestFile(change.fullPath);
|
|
278
|
+
if (!testFile)
|
|
279
|
+
return [];
|
|
280
|
+
const suggestions = [];
|
|
281
|
+
const relativeTestFile = testFile.replace(projectRoot + '/', '');
|
|
282
|
+
try {
|
|
283
|
+
execSync(testRunner.singleFileCommand(relativeTestFile), {
|
|
284
|
+
encoding: 'utf-8',
|
|
285
|
+
timeout: 30000,
|
|
286
|
+
cwd: projectRoot,
|
|
287
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
288
|
+
});
|
|
289
|
+
sessionStats.testsRun++;
|
|
290
|
+
suggestions.push({
|
|
291
|
+
type: 'info',
|
|
292
|
+
category: 'test',
|
|
293
|
+
file: change.file,
|
|
294
|
+
message: `Tests passed (${testRunner.name}: ${relativeTestFile})`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
sessionStats.testsRun++;
|
|
299
|
+
sessionStats.testsFailed++;
|
|
300
|
+
const stdout = err instanceof Error && 'stdout' in err
|
|
301
|
+
? String(err.stdout)
|
|
302
|
+
: '';
|
|
303
|
+
const stderr = err instanceof Error && 'stderr' in err
|
|
304
|
+
? String(err.stderr)
|
|
305
|
+
: '';
|
|
306
|
+
const output = (stdout + '\n' + stderr).trim();
|
|
307
|
+
// Extract the most relevant failure line
|
|
308
|
+
const failureLines = output.split('\n')
|
|
309
|
+
.filter(l => /fail|error|assert|expect/i.test(l) && !l.includes('node_modules'))
|
|
310
|
+
.slice(0, 3);
|
|
311
|
+
suggestions.push({
|
|
312
|
+
type: 'error',
|
|
313
|
+
category: 'test',
|
|
314
|
+
file: change.file,
|
|
315
|
+
message: `Test failed (${testRunner.name}: ${relativeTestFile})`,
|
|
316
|
+
});
|
|
317
|
+
for (const line of failureLines) {
|
|
318
|
+
const trimmed = line.trim().slice(0, 120);
|
|
319
|
+
if (trimmed) {
|
|
320
|
+
suggestions.push({
|
|
321
|
+
type: 'error',
|
|
322
|
+
category: 'test',
|
|
323
|
+
file: change.file,
|
|
324
|
+
message: ` ${trimmed}`,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (failureLines.length === 0 && output.length > 0) {
|
|
329
|
+
// Show last few lines as fallback
|
|
330
|
+
const lastLines = output.split('\n').filter(l => l.trim()).slice(-3);
|
|
331
|
+
for (const line of lastLines) {
|
|
332
|
+
suggestions.push({
|
|
333
|
+
type: 'error',
|
|
334
|
+
category: 'test',
|
|
335
|
+
file: change.file,
|
|
336
|
+
message: ` ${line.trim().slice(0, 120)}`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return suggestions;
|
|
342
|
+
}
|
|
107
343
|
// ---------------------------------------------------------------------------
|
|
108
344
|
// Config management
|
|
109
345
|
// ---------------------------------------------------------------------------
|
|
@@ -703,6 +939,10 @@ async function analyzeChanges(changes, config, options) {
|
|
|
703
939
|
if (isSource && config.checks.style) {
|
|
704
940
|
suggestions.push(...checkStyle(change));
|
|
705
941
|
}
|
|
942
|
+
// Run tests for changed source files
|
|
943
|
+
if (isSource && config.checks.missingTests) {
|
|
944
|
+
suggestions.push(...runTestForFile(change));
|
|
945
|
+
}
|
|
706
946
|
// Apply auto-fixes if enabled
|
|
707
947
|
let autoFixed = [];
|
|
708
948
|
if (config.autoFix || options.autoFix) {
|
|
@@ -779,7 +1019,7 @@ export async function startPairMode(options = {}) {
|
|
|
779
1019
|
return;
|
|
780
1020
|
}
|
|
781
1021
|
// Reset session stats
|
|
782
|
-
sessionStats = { filesAnalyzed: 0, suggestionsShown: 0, fixesApplied: 0, errorsFound: 0 };
|
|
1022
|
+
sessionStats = { filesAnalyzed: 0, suggestionsShown: 0, fixesApplied: 0, errorsFound: 0, testsRun: 0, testsFailed: 0 };
|
|
783
1023
|
// Print banner
|
|
784
1024
|
process.stderr.write('\n');
|
|
785
1025
|
process.stderr.write(` ${ACCENT('kbot')} ${chalk.bold('pair')} ${DIM('— watching for changes')}\n`);
|
|
@@ -825,6 +1065,8 @@ export async function startPairMode(options = {}) {
|
|
|
825
1065
|
const full = join(watchPath, file);
|
|
826
1066
|
return classifyChange(full, file);
|
|
827
1067
|
});
|
|
1068
|
+
// Track co-change patterns
|
|
1069
|
+
recordCoChange(batch);
|
|
828
1070
|
// Run analysis pipeline
|
|
829
1071
|
await analyzeChanges(changes, config, options);
|
|
830
1072
|
}, DEBOUNCE_MS);
|
|
@@ -862,9 +1104,20 @@ export function stopPairMode() {
|
|
|
862
1104
|
process.stderr.write(` ${DIM('Files analyzed:')} ${sessionStats.filesAnalyzed}\n`);
|
|
863
1105
|
process.stderr.write(` ${DIM('Suggestions:')} ${sessionStats.suggestionsShown}\n`);
|
|
864
1106
|
process.stderr.write(` ${DIM('Errors found:')} ${sessionStats.errorsFound}\n`);
|
|
1107
|
+
if (sessionStats.testsRun > 0) {
|
|
1108
|
+
process.stderr.write(` ${DIM('Tests run:')} ${sessionStats.testsRun}${sessionStats.testsFailed > 0 ? ` (${RED(String(sessionStats.testsFailed) + ' failed')})` : ` (${GREEN('all passed')})`}\n`);
|
|
1109
|
+
}
|
|
865
1110
|
if (sessionStats.fixesApplied > 0) {
|
|
866
1111
|
process.stderr.write(` ${DIM('Auto-fixed:')} ${GREEN(String(sessionStats.fixesApplied))}\n`);
|
|
867
1112
|
}
|
|
1113
|
+
// Print co-change patterns if any were detected
|
|
1114
|
+
const patterns = getCoChangePatterns();
|
|
1115
|
+
if (patterns.length > 0) {
|
|
1116
|
+
process.stderr.write(`\n ${DIM('Co-change patterns detected:')}\n`);
|
|
1117
|
+
for (const p of patterns.slice(0, 5)) {
|
|
1118
|
+
process.stderr.write(` ${CYAN(p.pair[0])} ${DIM('<->')} ${CYAN(p.pair[1])} ${DIM(`(${p.count}x)`)}\n`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
868
1121
|
process.stderr.write('\n');
|
|
869
1122
|
}
|
|
870
1123
|
}
|
|
@@ -927,6 +1180,26 @@ export async function analyzeWithAgent(filePath, agentOptions) {
|
|
|
927
1180
|
}
|
|
928
1181
|
}
|
|
929
1182
|
// ---------------------------------------------------------------------------
|
|
1183
|
+
// runPair() — convenience entry point
|
|
1184
|
+
// ---------------------------------------------------------------------------
|
|
1185
|
+
/**
|
|
1186
|
+
* runPair() — Start AI pair programming watch mode.
|
|
1187
|
+
*
|
|
1188
|
+
* 1. Watches cwd (or specified path) for file changes using fs.watch
|
|
1189
|
+
* 2. When .ts/.js/.py/.rs/.go files change:
|
|
1190
|
+
* - Runs the relevant test if one exists (detects vitest/jest/pytest/cargo/go)
|
|
1191
|
+
* - If test fails, shows the failure with fix suggestions
|
|
1192
|
+
* - Tracks which files change together (co-change pattern extraction)
|
|
1193
|
+
* 3. Simple terminal output: file changed, action taken, result
|
|
1194
|
+
* 4. Runs until Ctrl+C
|
|
1195
|
+
*/
|
|
1196
|
+
export async function runPair(path, options) {
|
|
1197
|
+
return startPairMode({
|
|
1198
|
+
path: path || process.cwd(),
|
|
1199
|
+
...options,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
// ---------------------------------------------------------------------------
|
|
930
1203
|
// CLI registration
|
|
931
1204
|
// ---------------------------------------------------------------------------
|
|
932
1205
|
/**
|