karajan-code 1.59.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/scripts/install.js +18 -15
- package/src/agents/aider-agent.js +0 -7
- package/src/agents/claude-agent.js +1 -4
- package/src/agents/codex-agent.js +2 -6
- package/src/agents/gemini-agent.js +0 -4
- package/src/agents/opencode-agent.js +0 -4
- package/src/{becaria → ci}/dispatch.js +13 -13
- package/src/{becaria → ci}/pr-diff.js +1 -1
- package/src/cli.js +2 -2
- package/src/commands/agents.js +0 -1
- package/src/commands/doctor.js +22 -40
- package/src/commands/init.js +8 -8
- package/src/commands/report.js +0 -13
- package/src/commands/review.js +17 -17
- package/src/commands/run.js +1 -2
- package/src/commands/status.js +1 -7
- package/src/config.js +10 -52
- package/src/git/automation.js +9 -9
- package/src/guards/output-guard.js +0 -17
- package/src/mcp/handlers/direct-handlers.js +1 -1
- package/src/mcp/handlers/run-handler.js +1 -2
- package/src/mcp/sovereignty-guard.js +1 -1
- package/src/mcp/tools.js +1 -1
- package/src/orchestrator/brain-coordinator.js +165 -0
- package/src/orchestrator/ci-integration.js +139 -0
- package/src/orchestrator/config-init.js +435 -0
- package/src/orchestrator/direct-actions.js +186 -0
- package/src/orchestrator/feedback-enrichment.js +140 -0
- package/src/orchestrator/feedback-queue.js +152 -0
- package/src/orchestrator/flow-control.js +159 -0
- package/src/orchestrator/post-loop-stages.js +20 -15
- package/src/orchestrator/preflight-checks.js +7 -3
- package/src/orchestrator/role-output-compressor.js +190 -0
- package/src/orchestrator/session-journal.js +344 -0
- package/src/orchestrator/solomon-escalation.js +38 -5
- package/src/orchestrator/solomon-rules.js +32 -33
- package/src/orchestrator/stages/reviewer-stage.js +16 -5
- package/src/orchestrator/stages/sonar-stage.js +24 -1
- package/src/orchestrator/verification-gate.js +146 -0
- package/src/orchestrator.js +428 -664
- package/src/planning-game/client.js +2 -2
- package/src/planning-game/pipeline-adapter.js +1 -1
- package/src/prompts/coder.js +17 -3
- package/src/prompts/reviewer.js +2 -2
- package/src/prompts/rtk-snippet.js +3 -5
- package/src/review/diff-generator.js +66 -17
- package/src/review/snapshot-diff.js +122 -0
- package/src/review/tdd-policy.js +4 -2
- package/src/roles/agent-role.js +135 -0
- package/src/roles/architect-role.js +61 -108
- package/src/roles/audit-role.js +25 -71
- package/src/roles/coder-role.js +32 -57
- package/src/roles/discover-role.js +30 -71
- package/src/roles/hu-reviewer-role.js +51 -99
- package/src/roles/impeccable-role.js +33 -88
- package/src/roles/index.js +2 -0
- package/src/roles/karajan-brain-role.js +134 -0
- package/src/roles/planner-role.js +40 -75
- package/src/roles/refactorer-role.js +22 -64
- package/src/roles/researcher-role.js +51 -115
- package/src/roles/reviewer-role.js +68 -125
- package/src/roles/security-role.js +38 -102
- package/src/roles/solomon-role.js +69 -11
- package/src/roles/tester-role.js +121 -95
- package/src/roles/triage-role.js +50 -111
- package/src/session-store.js +1 -1
- package/src/skills/skill-detector.js +11 -0
- package/src/sonar/config-resolver.js +20 -1
- package/src/sonar/credentials.js +17 -0
- package/src/sonar/project-key.js +1 -1
- package/src/sonar/scanner.js +1 -1
- package/src/utils/display.js +33 -14
- package/src/utils/injection-guard.js +23 -8
- package/src/utils/logger.js +6 -1
- package/src/utils/process.js +3 -4
- package/src/utils/role-assigner.js +131 -0
- package/src/utils/update-check.js +5 -1
- package/src/webperf/devtools-detect.js +70 -23
- package/templates/roles/coder.md +7 -0
- package/templates/roles/karajan-brain.md +95 -0
- package/templates/roles/solomon.md +67 -106
- package/templates/workflows/automerge.yml +4 -4
- package/templates/workflows/houston-override.yml +4 -4
- package/templates/workflows/{becaria-gateway.yml → kj-ci-gateway.yml} +12 -12
- package/src/proxy/adapters/anthropic.js +0 -131
- package/src/proxy/adapters/gemini.js +0 -106
- package/src/proxy/adapters/index.js +0 -51
- package/src/proxy/adapters/openai.js +0 -71
- package/src/proxy/compression/ai-compressor.js +0 -209
- package/src/proxy/compression/dedup-cache.js +0 -100
- package/src/proxy/compression/deterministic/bash-build.js +0 -60
- package/src/proxy/compression/deterministic/bash-git.js +0 -101
- package/src/proxy/compression/deterministic/bash-infra.js +0 -64
- package/src/proxy/compression/deterministic/bash-misc.js +0 -53
- package/src/proxy/compression/deterministic/bash-pkg.js +0 -49
- package/src/proxy/compression/deterministic/bash-test.js +0 -61
- package/src/proxy/compression/deterministic/glob.js +0 -26
- package/src/proxy/compression/deterministic/grep.js +0 -53
- package/src/proxy/compression/deterministic/index.js +0 -58
- package/src/proxy/compression/deterministic/read.js +0 -22
- package/src/proxy/compression/deterministic/utils.js +0 -33
- package/src/proxy/compression/pipeline.js +0 -270
- package/src/proxy/middleware/prompt-injector.js +0 -165
- package/src/proxy/middleware/response-interceptor.js +0 -223
- package/src/proxy/progress-monitor.js +0 -98
- package/src/proxy/proxy-lifecycle.js +0 -201
- package/src/proxy/proxy-server.js +0 -239
- package/src/proxy/security.js +0 -45
- /package/src/{becaria → ci}/index.js +0 -0
- /package/src/{becaria → ci}/repo.js +0 -0
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Optional AI-powered compression for content that remains large
|
|
3
|
-
* after deterministic compression.
|
|
4
|
-
* Calls a cheap LLM model to summarize old tool results while
|
|
5
|
-
* preserving key data (file paths, error messages, numbers, status codes).
|
|
6
|
-
*
|
|
7
|
-
* Disabled by default. Enable via config: { enabled: true, provider, apiKey }.
|
|
8
|
-
* Zero external dependencies — uses vanilla Node.js https module.
|
|
9
|
-
*/
|
|
10
|
-
import https from "node:https";
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Provider definitions
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
const PROVIDERS = {
|
|
16
|
-
anthropic: {
|
|
17
|
-
hostname: "api.anthropic.com",
|
|
18
|
-
path: "/v1/messages",
|
|
19
|
-
defaultModel: "claude-3-5-haiku-20241022",
|
|
20
|
-
costPerInputToken: 0.25 / 1_000_000,
|
|
21
|
-
costPerOutputToken: 1.25 / 1_000_000,
|
|
22
|
-
buildRequest(text, model, apiKey, maxOutputTokens) {
|
|
23
|
-
const body = JSON.stringify({
|
|
24
|
-
model,
|
|
25
|
-
max_tokens: maxOutputTokens,
|
|
26
|
-
messages: [{ role: "user", content: buildPrompt(text) }]
|
|
27
|
-
});
|
|
28
|
-
return {
|
|
29
|
-
hostname: this.hostname,
|
|
30
|
-
path: this.path,
|
|
31
|
-
method: "POST",
|
|
32
|
-
headers: {
|
|
33
|
-
"Content-Type": "application/json",
|
|
34
|
-
"x-api-key": apiKey,
|
|
35
|
-
"anthropic-version": "2023-06-01"
|
|
36
|
-
},
|
|
37
|
-
body
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
parseResponse(data) {
|
|
41
|
-
const content = data.content?.[0]?.text ?? "";
|
|
42
|
-
const input = data.usage?.input_tokens ?? 0;
|
|
43
|
-
const output = data.usage?.output_tokens ?? 0;
|
|
44
|
-
return { text: content, tokensUsed: { input, output } };
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
openai: {
|
|
49
|
-
hostname: "api.openai.com",
|
|
50
|
-
path: "/v1/chat/completions",
|
|
51
|
-
defaultModel: "gpt-4o-mini",
|
|
52
|
-
costPerInputToken: 0.15 / 1_000_000,
|
|
53
|
-
costPerOutputToken: 0.6 / 1_000_000,
|
|
54
|
-
buildRequest(text, model, apiKey, maxOutputTokens) {
|
|
55
|
-
const body = JSON.stringify({
|
|
56
|
-
model,
|
|
57
|
-
max_tokens: maxOutputTokens,
|
|
58
|
-
messages: [
|
|
59
|
-
{ role: "system", content: SYSTEM_PROMPT },
|
|
60
|
-
{ role: "user", content: text }
|
|
61
|
-
]
|
|
62
|
-
});
|
|
63
|
-
return {
|
|
64
|
-
hostname: this.hostname,
|
|
65
|
-
path: this.path,
|
|
66
|
-
method: "POST",
|
|
67
|
-
headers: {
|
|
68
|
-
"Content-Type": "application/json",
|
|
69
|
-
Authorization: `Bearer ${apiKey}`
|
|
70
|
-
},
|
|
71
|
-
body
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
parseResponse(data) {
|
|
75
|
-
const content = data.choices?.[0]?.message?.content ?? "";
|
|
76
|
-
const input = data.usage?.prompt_tokens ?? 0;
|
|
77
|
-
const output = data.usage?.completion_tokens ?? 0;
|
|
78
|
-
return { text: content, tokensUsed: { input, output } };
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
gemini: {
|
|
83
|
-
hostname: "generativelanguage.googleapis.com",
|
|
84
|
-
defaultModel: "gemini-2.0-flash-lite",
|
|
85
|
-
costPerInputToken: 0.075 / 1_000_000,
|
|
86
|
-
costPerOutputToken: 0.3 / 1_000_000,
|
|
87
|
-
buildRequest(text, model, apiKey, maxOutputTokens) {
|
|
88
|
-
const path = `/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
|
89
|
-
const body = JSON.stringify({
|
|
90
|
-
contents: [{ parts: [{ text: buildPrompt(text) }] }],
|
|
91
|
-
generationConfig: { maxOutputTokens }
|
|
92
|
-
});
|
|
93
|
-
return {
|
|
94
|
-
hostname: this.hostname,
|
|
95
|
-
path,
|
|
96
|
-
method: "POST",
|
|
97
|
-
headers: { "Content-Type": "application/json" },
|
|
98
|
-
body
|
|
99
|
-
};
|
|
100
|
-
},
|
|
101
|
-
parseResponse(data) {
|
|
102
|
-
const content = data.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
|
|
103
|
-
const input = data.usageMetadata?.promptTokenCount ?? 0;
|
|
104
|
-
const output = data.usageMetadata?.candidatesTokenCount ?? 0;
|
|
105
|
-
return { text: content, tokensUsed: { input, output } };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// Prompt
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
const SYSTEM_PROMPT =
|
|
114
|
-
"You are a compression assistant. Summarize the following tool output concisely " +
|
|
115
|
-
"while preserving ALL: file paths, error messages, numbers, status codes, " +
|
|
116
|
-
"exit codes, and key technical details. Remove boilerplate, repeated lines, " +
|
|
117
|
-
"and verbose formatting. Output only the summary, no preamble.";
|
|
118
|
-
|
|
119
|
-
function buildPrompt(text) {
|
|
120
|
-
return `${SYSTEM_PROMPT}\n\n---\n${text}`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// HTTPS helper
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
function httpsRequest({ hostname, path, method, headers, body }) {
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
const req = https.request({ hostname, path, method, headers }, (res) => {
|
|
129
|
-
const chunks = [];
|
|
130
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
131
|
-
res.on("end", () => {
|
|
132
|
-
const raw = Buffer.concat(chunks).toString();
|
|
133
|
-
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
134
|
-
reject(new Error(`HTTP ${res.statusCode}: ${raw}`));
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
resolve(JSON.parse(raw));
|
|
139
|
-
} catch (err) {
|
|
140
|
-
reject(new Error(`Invalid JSON response: ${raw.slice(0, 200)}`));
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
res.on("error", reject);
|
|
144
|
-
});
|
|
145
|
-
req.on("error", reject);
|
|
146
|
-
if (body) req.write(body);
|
|
147
|
-
req.end();
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// Public API
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Check whether AI compression can be used with the given config.
|
|
157
|
-
* @param {{ enabled?: boolean, provider?: string, apiKey?: string }} config
|
|
158
|
-
* @returns {boolean}
|
|
159
|
-
*/
|
|
160
|
-
export function isAICompressionAvailable(config) {
|
|
161
|
-
if (!config || !config.enabled) return false;
|
|
162
|
-
const provider = config.provider ?? "anthropic";
|
|
163
|
-
if (!PROVIDERS[provider]) return false;
|
|
164
|
-
return Boolean(config.apiKey);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Compress text using a cheap LLM model.
|
|
169
|
-
* @param {string} text - text to compress
|
|
170
|
-
* @param {{ model?: string, apiKey?: string, provider?: string, maxOutputTokens?: number, enabled?: boolean }} options
|
|
171
|
-
* @returns {Promise<{ compressed: string, tokensUsed: { input: number, output: number }, cost: number }>}
|
|
172
|
-
*/
|
|
173
|
-
export async function compressWithAI(text, options = {}) {
|
|
174
|
-
const { enabled = false, provider: providerName = "anthropic", apiKey, maxOutputTokens = 1024 } = options;
|
|
175
|
-
|
|
176
|
-
// Disabled mode — return original text unchanged
|
|
177
|
-
if (!enabled || !apiKey) {
|
|
178
|
-
return { compressed: text, tokensUsed: { input: 0, output: 0 }, cost: 0 };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const provider = PROVIDERS[providerName];
|
|
182
|
-
if (!provider) {
|
|
183
|
-
return { compressed: text, tokensUsed: { input: 0, output: 0 }, cost: 0 };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const model = options.model ?? provider.defaultModel;
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const reqOpts = provider.buildRequest(text, model, apiKey, maxOutputTokens);
|
|
190
|
-
const data = await httpsRequest(reqOpts);
|
|
191
|
-
const { text: compressed, tokensUsed } = provider.parseResponse(data);
|
|
192
|
-
|
|
193
|
-
if (!compressed) {
|
|
194
|
-
return { compressed: text, tokensUsed: { input: 0, output: 0 }, cost: 0 };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const cost =
|
|
198
|
-
tokensUsed.input * provider.costPerInputToken +
|
|
199
|
-
tokensUsed.output * provider.costPerOutputToken;
|
|
200
|
-
|
|
201
|
-
return { compressed, tokensUsed, cost };
|
|
202
|
-
} catch {
|
|
203
|
-
// Graceful degradation — return original text on any failure
|
|
204
|
-
return { compressed: text, tokensUsed: { input: 0, output: 0 }, cost: 0 };
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Exported for testing */
|
|
209
|
-
export { PROVIDERS };
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-turn content deduplication using an LRU hash cache.
|
|
5
|
-
* Detects repeated content across conversation turns and returns
|
|
6
|
-
* compact references instead of duplicated text.
|
|
7
|
-
*/
|
|
8
|
-
export class DedupCache {
|
|
9
|
-
#maxEntries;
|
|
10
|
-
#cache; // Map preserves insertion order for LRU
|
|
11
|
-
#totalChecks;
|
|
12
|
-
#hits;
|
|
13
|
-
#misses;
|
|
14
|
-
|
|
15
|
-
constructor({ maxEntries = 500 } = {}) {
|
|
16
|
-
this.#maxEntries = maxEntries;
|
|
17
|
-
this.#cache = new Map();
|
|
18
|
-
this.#totalChecks = 0;
|
|
19
|
-
this.#hits = 0;
|
|
20
|
-
this.#misses = 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check if text has been seen in a prior turn.
|
|
25
|
-
* @param {string} text - Content to check
|
|
26
|
-
* @param {number} turnIndex - Current turn number
|
|
27
|
-
* @returns {{isDuplicate: boolean, reference?: string}}
|
|
28
|
-
*/
|
|
29
|
-
check(text, turnIndex) {
|
|
30
|
-
this.#totalChecks++;
|
|
31
|
-
|
|
32
|
-
const tokenCount = Math.floor(text.length / 4);
|
|
33
|
-
const hash = createHash("sha256").update(text).digest("hex");
|
|
34
|
-
const shortHash = hash.slice(0, 6);
|
|
35
|
-
|
|
36
|
-
// Small content is never deduped
|
|
37
|
-
if (tokenCount <= 200) {
|
|
38
|
-
this.#misses++;
|
|
39
|
-
this.#upsert(hash, { hash, turnIndex, tokenCount, insertedAt: Date.now() });
|
|
40
|
-
return { isDuplicate: false };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const existing = this.#cache.get(hash);
|
|
44
|
-
|
|
45
|
-
if (existing && existing.turnIndex < turnIndex) {
|
|
46
|
-
// Seen in a prior turn — duplicate
|
|
47
|
-
this.#hits++;
|
|
48
|
-
// Refresh LRU position
|
|
49
|
-
this.#cache.delete(hash);
|
|
50
|
-
this.#cache.set(hash, { ...existing, insertedAt: Date.now() });
|
|
51
|
-
return {
|
|
52
|
-
isDuplicate: true,
|
|
53
|
-
reference: `[Content from turn ${existing.turnIndex}, hash ${shortHash}, ${tokenCount} tokens - unchanged]`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Not seen or same turn — store and return not duplicate
|
|
58
|
-
this.#misses++;
|
|
59
|
-
this.#upsert(hash, { hash, turnIndex, tokenCount, insertedAt: Date.now() });
|
|
60
|
-
return { isDuplicate: false };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Insert or update an entry, evicting the oldest if at capacity.
|
|
65
|
-
*/
|
|
66
|
-
#upsert(key, entry) {
|
|
67
|
-
// If key already exists, delete first to refresh insertion order
|
|
68
|
-
if (this.#cache.has(key)) {
|
|
69
|
-
this.#cache.delete(key);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Evict oldest (first inserted) if at capacity
|
|
73
|
-
if (this.#cache.size >= this.#maxEntries) {
|
|
74
|
-
const oldestKey = this.#cache.keys().next().value;
|
|
75
|
-
this.#cache.delete(oldestKey);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
this.#cache.set(key, entry);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @returns {{totalChecks: number, hits: number, misses: number, entries: number}}
|
|
83
|
-
*/
|
|
84
|
-
getStats() {
|
|
85
|
-
return {
|
|
86
|
-
totalChecks: this.#totalChecks,
|
|
87
|
-
hits: this.#hits,
|
|
88
|
-
misses: this.#misses,
|
|
89
|
-
entries: this.#cache.size,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Clear all state (call between sessions). */
|
|
94
|
-
reset() {
|
|
95
|
-
this.#cache.clear();
|
|
96
|
-
this.#totalChecks = 0;
|
|
97
|
-
this.#hits = 0;
|
|
98
|
-
this.#misses = 0;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for build tool output.
|
|
3
|
-
* Handles: tsc, eslint, prettier, next build, webpack, vite build.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, collapseWhitespace, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const BUILD_PATTERNS = [
|
|
8
|
-
/error TS\d+:/,
|
|
9
|
-
/\d+ errors?/i,
|
|
10
|
-
/\d+ warnings?/i,
|
|
11
|
-
/eslint/i,
|
|
12
|
-
/prettier/i,
|
|
13
|
-
/webpack/i,
|
|
14
|
-
/✔|Built in|compiled/i,
|
|
15
|
-
/Module Error|SyntaxError|TypeError/,
|
|
16
|
-
/next build|vite build/i
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
export function looksLike(text) {
|
|
20
|
-
const clean = stripAnsi(text);
|
|
21
|
-
return BUILD_PATTERNS.some((p) => p.test(clean));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function compact(text) {
|
|
25
|
-
const clean = stripAnsi(text);
|
|
26
|
-
const lines = clean.split("\n");
|
|
27
|
-
|
|
28
|
-
// Collect error/warning lines and summary lines
|
|
29
|
-
const errors = [];
|
|
30
|
-
const warnings = [];
|
|
31
|
-
const summaries = [];
|
|
32
|
-
|
|
33
|
-
for (const line of lines) {
|
|
34
|
-
const trimmed = line.trim();
|
|
35
|
-
if (!trimmed) continue;
|
|
36
|
-
|
|
37
|
-
if (/error TS\d+:|Error:|SyntaxError:|TypeError:|Module Error/i.test(trimmed)) {
|
|
38
|
-
errors.push(trimmed);
|
|
39
|
-
} else if (/warning|warn\b/i.test(trimmed) && !/node_modules/.test(trimmed)) {
|
|
40
|
-
warnings.push(trimmed);
|
|
41
|
-
} else if (/\d+ errors?|\d+ warnings?|compiled|Built in|✔|Found \d+/i.test(trimmed)) {
|
|
42
|
-
summaries.push(trimmed);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const parts = [];
|
|
47
|
-
if (errors.length) {
|
|
48
|
-
parts.push(`Errors (${errors.length}):\n${truncateLines(errors.join("\n"), 20)}`);
|
|
49
|
-
}
|
|
50
|
-
if (warnings.length) {
|
|
51
|
-
parts.push(`Warnings (${warnings.length}):\n${truncateLines(warnings.join("\n"), 10)}`);
|
|
52
|
-
}
|
|
53
|
-
if (summaries.length) {
|
|
54
|
-
parts.push(summaries.join("\n"));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (parts.length) return parts.join("\n\n");
|
|
58
|
-
|
|
59
|
-
return truncateLines(collapseWhitespace(clean), 40);
|
|
60
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for git CLI output.
|
|
3
|
-
* Handles: git status, git diff, git log, git branch.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, collapseWhitespace, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const GIT_PATTERNS = [
|
|
8
|
-
/^On branch /m,
|
|
9
|
-
/^(modified|deleted|new file|renamed):/m,
|
|
10
|
-
/^Untracked files:/m,
|
|
11
|
-
/^diff --git/m,
|
|
12
|
-
/^commit [0-9a-f]{7,40}/m,
|
|
13
|
-
/^(Changes to be committed|Changes not staged)/m,
|
|
14
|
-
/^\* /m // git branch current branch marker
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export function looksLike(text) {
|
|
18
|
-
const clean = stripAnsi(text);
|
|
19
|
-
return GIT_PATTERNS.some((p) => p.test(clean));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function compact(text) {
|
|
23
|
-
const clean = stripAnsi(text);
|
|
24
|
-
|
|
25
|
-
// git status
|
|
26
|
-
if (/^On branch /m.test(clean) || /Changes (to be committed|not staged)/m.test(clean)) {
|
|
27
|
-
return compactStatus(clean);
|
|
28
|
-
}
|
|
29
|
-
// git diff
|
|
30
|
-
if (/^diff --git/m.test(clean)) {
|
|
31
|
-
return compactDiff(clean);
|
|
32
|
-
}
|
|
33
|
-
// git log
|
|
34
|
-
if (/^commit [0-9a-f]{7,40}/m.test(clean)) {
|
|
35
|
-
return compactLog(clean);
|
|
36
|
-
}
|
|
37
|
-
// git branch
|
|
38
|
-
if (/^\* /m.test(clean) && clean.split("\n").every((l) => /^[\s*]/.test(l) || !l.trim())) {
|
|
39
|
-
return compactBranch(clean);
|
|
40
|
-
}
|
|
41
|
-
return collapseWhitespace(clean);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function compactStatus(text) {
|
|
45
|
-
const lines = text.split("\n");
|
|
46
|
-
const kept = [];
|
|
47
|
-
const branchLine = lines.find((l) => l.startsWith("On branch "));
|
|
48
|
-
if (branchLine) kept.push(branchLine);
|
|
49
|
-
|
|
50
|
-
for (const line of lines) {
|
|
51
|
-
const trimmed = line.trim();
|
|
52
|
-
if (
|
|
53
|
-
/^(modified|deleted|new file|renamed|both modified):/.test(trimmed) ||
|
|
54
|
-
/^(Untracked files|Changes to be committed|Changes not staged)/.test(trimmed)
|
|
55
|
-
) {
|
|
56
|
-
kept.push(trimmed);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return kept.join("\n") || text;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function compactDiff(text) {
|
|
63
|
-
const files = text.split(/(?=^diff --git)/m);
|
|
64
|
-
const summaries = [];
|
|
65
|
-
for (const file of files) {
|
|
66
|
-
if (!file.trim()) continue;
|
|
67
|
-
const header = file.match(/^diff --git a\/(.+?) b\/(.+)/m);
|
|
68
|
-
const fname = header ? header[2] : "unknown";
|
|
69
|
-
const additions = (file.match(/^\+(?!\+\+)/gm) || []).length;
|
|
70
|
-
const deletions = (file.match(/^-(?!--)/gm) || []).length;
|
|
71
|
-
const hunkHeaders = file.match(/^@@.*@@.*$/gm) || [];
|
|
72
|
-
summaries.push(`${fname}: +${additions}/-${deletions} (${hunkHeaders.length} hunks)`);
|
|
73
|
-
|
|
74
|
-
// Keep first few hunk headers for context
|
|
75
|
-
for (const h of hunkHeaders.slice(0, 3)) {
|
|
76
|
-
summaries.push(` ${h.trim()}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return `git diff summary (${files.filter((f) => f.trim()).length} files):\n${summaries.join("\n")}`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function compactLog(text) {
|
|
83
|
-
const entries = text.split(/(?=^commit )/m).filter((e) => e.trim());
|
|
84
|
-
const lines = [];
|
|
85
|
-
for (const entry of entries) {
|
|
86
|
-
const hashMatch = entry.match(/^commit ([0-9a-f]{7})/);
|
|
87
|
-
const msgMatch = entry.match(/^\s{4}(.+)/m);
|
|
88
|
-
const hash = hashMatch ? hashMatch[1] : "???????";
|
|
89
|
-
const msg = msgMatch ? msgMatch[1].trim() : "(no message)";
|
|
90
|
-
lines.push(`${hash} ${msg}`);
|
|
91
|
-
}
|
|
92
|
-
return truncateLines(lines.join("\n"), 30);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function compactBranch(text) {
|
|
96
|
-
const lines = text.split("\n").filter((l) => l.trim());
|
|
97
|
-
if (lines.length <= 20) return lines.map((l) => l.trim()).join("\n");
|
|
98
|
-
const current = lines.find((l) => l.startsWith("*"));
|
|
99
|
-
const count = lines.length;
|
|
100
|
-
return `${current ? current.trim() : "(detached)"}\n... (${count} branches total)`;
|
|
101
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for infrastructure CLI output.
|
|
3
|
-
* Handles: docker ps/images/logs, kubectl, terraform plan/apply.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, collapseWhitespace, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const INFRA_PATTERNS = [
|
|
8
|
-
/CONTAINER ID/,
|
|
9
|
-
/REPOSITORY\s+TAG/,
|
|
10
|
-
/^NAME\s+READY\s+STATUS/m,
|
|
11
|
-
/^(kubectl|docker|terraform)\s/m,
|
|
12
|
-
/Terraform will perform/,
|
|
13
|
-
/Plan:\s+\d+ to add/,
|
|
14
|
-
/Apply complete/
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export function looksLike(text) {
|
|
18
|
-
const clean = stripAnsi(text);
|
|
19
|
-
return INFRA_PATTERNS.some((p) => p.test(clean));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function compact(text) {
|
|
23
|
-
const clean = stripAnsi(text);
|
|
24
|
-
|
|
25
|
-
// docker ps / docker images — compact table
|
|
26
|
-
if (/CONTAINER ID/.test(clean) || /REPOSITORY\s+TAG/.test(clean)) {
|
|
27
|
-
return compactTable(clean);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// terraform plan/apply
|
|
31
|
-
if (/Terraform will perform|Plan:\s+\d+|Apply complete/.test(clean)) {
|
|
32
|
-
return compactTerraform(clean);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// kubectl — compact table
|
|
36
|
-
if (/^NAME\s+READY\s+STATUS/m.test(clean)) {
|
|
37
|
-
return compactTable(clean);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// docker logs — truncate
|
|
41
|
-
return truncateLines(collapseWhitespace(clean), 30);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function compactTable(text) {
|
|
45
|
-
const lines = text.split("\n").filter((l) => l.trim());
|
|
46
|
-
if (lines.length <= 15) return lines.map((l) => collapseWhitespace(l)).join("\n");
|
|
47
|
-
const header = lines[0];
|
|
48
|
-
const rows = lines.slice(1, 11);
|
|
49
|
-
return `${collapseWhitespace(header)}\n${rows.map((r) => collapseWhitespace(r)).join("\n")}\n... (${lines.length - 1} total rows)`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function compactTerraform(text) {
|
|
53
|
-
const lines = text.split("\n");
|
|
54
|
-
const kept = [];
|
|
55
|
-
for (const line of lines) {
|
|
56
|
-
if (
|
|
57
|
-
/Plan:|Apply complete|Terraform will perform|^\s*[#~+-]/.test(line) &&
|
|
58
|
-
!/Refreshing state/.test(line)
|
|
59
|
-
) {
|
|
60
|
-
kept.push(line.trim());
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return truncateLines(kept.join("\n"), 40) || truncateLines(collapseWhitespace(text), 30);
|
|
64
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for miscellaneous Bash output.
|
|
3
|
-
* Handles: curl/wget, gh CLI, npx, general long output.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, collapseWhitespace, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const MISC_PATTERNS = [
|
|
8
|
-
/HTTP\/[\d.]+\s+\d{3}/,
|
|
9
|
-
/curl:|wget:/i,
|
|
10
|
-
/gh (pr|issue|repo|release)/i,
|
|
11
|
-
/npx:/i
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
export function looksLike(text) {
|
|
15
|
-
const clean = stripAnsi(text);
|
|
16
|
-
return MISC_PATTERNS.some((p) => p.test(clean));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function compact(text) {
|
|
20
|
-
const clean = stripAnsi(text);
|
|
21
|
-
|
|
22
|
-
// curl/wget — keep status + relevant headers
|
|
23
|
-
if (/HTTP\/[\d.]+\s+\d{3}/.test(clean)) {
|
|
24
|
-
return compactHttp(clean);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// gh CLI — compact lists
|
|
28
|
-
if (/gh (pr|issue|repo|release)/i.test(clean)) {
|
|
29
|
-
return truncateLines(collapseWhitespace(clean), 20);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return truncateLines(collapseWhitespace(clean), 40);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function compactHttp(text) {
|
|
36
|
-
const lines = text.split("\n");
|
|
37
|
-
const kept = [];
|
|
38
|
-
for (const line of lines) {
|
|
39
|
-
if (
|
|
40
|
-
/HTTP\/[\d.]+\s+\d{3}/.test(line) ||
|
|
41
|
-
/^(content-type|location|x-[-\w]+|authorization):/i.test(line.trim())
|
|
42
|
-
) {
|
|
43
|
-
kept.push(line.trim());
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// Also keep body, truncated
|
|
47
|
-
const bodyStart = text.indexOf("\r\n\r\n");
|
|
48
|
-
if (bodyStart > -1) {
|
|
49
|
-
const body = text.slice(bodyStart + 4).trim();
|
|
50
|
-
if (body) kept.push(truncateLines(body, 20));
|
|
51
|
-
}
|
|
52
|
-
return kept.join("\n") || truncateLines(collapseWhitespace(text), 30);
|
|
53
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for package manager output.
|
|
3
|
-
* Handles: npm/yarn/pnpm install, npm list, npm outdated.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, collapseWhitespace, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const PKG_PATTERNS = [
|
|
8
|
-
/added \d+ packages?/i,
|
|
9
|
-
/npm warn|npm error/i,
|
|
10
|
-
/packages? in \d/,
|
|
11
|
-
/├──|└──|│/,
|
|
12
|
-
/\d+ vulnerabilities/i,
|
|
13
|
-
/npm outdated/i,
|
|
14
|
-
/Current\s+Wanted\s+Latest/i
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export function looksLike(text) {
|
|
18
|
-
const clean = stripAnsi(text);
|
|
19
|
-
return PKG_PATTERNS.some((p) => p.test(clean));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function compact(text) {
|
|
23
|
-
const clean = stripAnsi(text);
|
|
24
|
-
|
|
25
|
-
// npm install summary
|
|
26
|
-
if (/added \d+ packages?/i.test(clean)) {
|
|
27
|
-
const lines = clean.split("\n");
|
|
28
|
-
const kept = [];
|
|
29
|
-
for (const line of lines) {
|
|
30
|
-
const trimmed = line.trim();
|
|
31
|
-
if (/added \d+ packages?/i.test(trimmed) || /vulnerabilit/i.test(trimmed)) {
|
|
32
|
-
kept.push(trimmed);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return kept.join("\n") || clean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// npm list — truncate tree
|
|
39
|
-
if (/├──|└──/.test(clean)) {
|
|
40
|
-
return truncateLines(clean, 25);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// npm outdated — compact table
|
|
44
|
-
if (/Current\s+Wanted\s+Latest/i.test(clean)) {
|
|
45
|
-
return truncateLines(collapseWhitespace(clean), 20);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return truncateLines(collapseWhitespace(clean), 30);
|
|
49
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for test runner output.
|
|
3
|
-
* Handles: vitest, jest, mocha, playwright, pytest, cargo test, go test.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, collapseWhitespace, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const TEST_PATTERNS = [
|
|
8
|
-
/Tests?\s+\d+\s+(passed|failed)/i,
|
|
9
|
-
/✓|✗|✘|PASS|FAIL/,
|
|
10
|
-
/Test Suites?:/,
|
|
11
|
-
/test result:/i,
|
|
12
|
-
/FAILED|PASSED|ERROR/,
|
|
13
|
-
/vitest|jest|mocha|playwright|pytest|cargo test|go test/i,
|
|
14
|
-
/\d+ passing/,
|
|
15
|
-
/\d+ failing/
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
export function looksLike(text) {
|
|
19
|
-
const clean = stripAnsi(text);
|
|
20
|
-
return TEST_PATTERNS.some((p) => p.test(clean));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function compact(text) {
|
|
24
|
-
const clean = stripAnsi(text);
|
|
25
|
-
|
|
26
|
-
// Extract summary line if present
|
|
27
|
-
const summaryPatterns = [
|
|
28
|
-
/Tests?\s+\d+\s+(passed|failed).*/gi,
|
|
29
|
-
/Test Suites?:.*$/gm,
|
|
30
|
-
/\d+ passing.*$/gm,
|
|
31
|
-
/\d+ failing.*$/gm,
|
|
32
|
-
/test result:.*$/gim,
|
|
33
|
-
/Tests:.*\d+ total$/gm
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const summaries = [];
|
|
37
|
-
for (const p of summaryPatterns) {
|
|
38
|
-
const matches = clean.match(p);
|
|
39
|
-
if (matches) summaries.push(...matches.map((m) => m.trim()));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Extract failed test details (name + error)
|
|
43
|
-
const failures = [];
|
|
44
|
-
const failBlocks = clean.split(/(?=(?:FAIL|✗|✘|×)\s)/);
|
|
45
|
-
for (const block of failBlocks) {
|
|
46
|
-
if (/^(?:FAIL|✗|✘|×)\s/.test(block)) {
|
|
47
|
-
failures.push(truncateLines(block.trim(), 8));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const parts = [];
|
|
52
|
-
if (summaries.length) parts.push(summaries.join("\n"));
|
|
53
|
-
if (failures.length) {
|
|
54
|
-
parts.push(`Failed tests:\n${failures.join("\n---\n")}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (parts.length) return parts.join("\n\n");
|
|
58
|
-
|
|
59
|
-
// Fallback: collapse and truncate
|
|
60
|
-
return truncateLines(collapseWhitespace(clean), 50);
|
|
61
|
-
}
|