karajan-code 1.59.0 → 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 +34 -6
- 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 +3 -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/domains/domain-loader.js +85 -4
- 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 +2 -2
- package/src/mcp/tools.js +3 -2
- 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 +435 -661
- 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 +34 -91
- 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 +52 -118
- package/src/roles/reviewer-role.js +69 -126
- package/src/roles/security-role.js +38 -102
- package/src/roles/solomon-role.js +71 -15
- 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/banner.js +66 -0
- package/src/utils/display.js +35 -17
- 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/utils/welcome.js +3 -8
- 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 -198
- 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,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
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for glob/find/ls output.
|
|
3
|
-
* Truncates long file listings while keeping count.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const MAX_FILES_SHOWN = 40;
|
|
8
|
-
|
|
9
|
-
export function looksLike(text) {
|
|
10
|
-
const clean = stripAnsi(text);
|
|
11
|
-
const lines = clean.split("\n").filter((l) => l.trim());
|
|
12
|
-
if (lines.length < 5) return false;
|
|
13
|
-
// Most lines look like file paths
|
|
14
|
-
const pathLike = lines.filter((l) => /^[\w./-]+\.\w+$/.test(l.trim()) || /^\//.test(l.trim()));
|
|
15
|
-
return pathLike.length >= lines.length * 0.6;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function compact(text) {
|
|
19
|
-
const clean = stripAnsi(text);
|
|
20
|
-
const lines = clean.split("\n").filter((l) => l.trim());
|
|
21
|
-
if (lines.length <= MAX_FILES_SHOWN) return clean;
|
|
22
|
-
|
|
23
|
-
const shown = lines.slice(0, MAX_FILES_SHOWN);
|
|
24
|
-
shown.push(`\n... (${lines.length} total files, showing first ${MAX_FILES_SHOWN})`);
|
|
25
|
-
return shown.join("\n");
|
|
26
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for grep/ripgrep output.
|
|
3
|
-
* Collapses repetitive results, keeps first N per file + total count.
|
|
4
|
-
*/
|
|
5
|
-
import { stripAnsi, truncateLines } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const MAX_MATCHES_PER_FILE = 3;
|
|
8
|
-
const MAX_FILES = 15;
|
|
9
|
-
|
|
10
|
-
export function looksLike(text) {
|
|
11
|
-
const clean = stripAnsi(text);
|
|
12
|
-
// grep-style output: file:line:content or file:content
|
|
13
|
-
const lines = clean.split("\n").filter((l) => l.trim());
|
|
14
|
-
if (lines.length < 2) return false;
|
|
15
|
-
const matchingLines = lines.filter((l) => /^[^\s:]+:\d+:/.test(l) || /^[^\s:]+:/.test(l));
|
|
16
|
-
return matchingLines.length >= lines.length * 0.5;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function compact(text) {
|
|
20
|
-
const clean = stripAnsi(text);
|
|
21
|
-
const lines = clean.split("\n").filter((l) => l.trim());
|
|
22
|
-
|
|
23
|
-
// Group by file
|
|
24
|
-
const byFile = new Map();
|
|
25
|
-
for (const line of lines) {
|
|
26
|
-
const match = line.match(/^([^\s:]+):/);
|
|
27
|
-
const file = match ? match[1] : "__other__";
|
|
28
|
-
if (!byFile.has(file)) byFile.set(file, []);
|
|
29
|
-
byFile.get(file).push(line);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const totalFiles = byFile.size;
|
|
33
|
-
const totalMatches = lines.length;
|
|
34
|
-
const result = [];
|
|
35
|
-
let fileCount = 0;
|
|
36
|
-
|
|
37
|
-
for (const [file, matches] of byFile) {
|
|
38
|
-
if (fileCount >= MAX_FILES) break;
|
|
39
|
-
fileCount++;
|
|
40
|
-
const shown = matches.slice(0, MAX_MATCHES_PER_FILE);
|
|
41
|
-
result.push(...shown);
|
|
42
|
-
if (matches.length > MAX_MATCHES_PER_FILE) {
|
|
43
|
-
result.push(` ... (${matches.length - MAX_MATCHES_PER_FILE} more matches in ${file})`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (totalFiles > MAX_FILES) {
|
|
48
|
-
result.push(`\n... (${totalFiles - MAX_FILES} more files)`);
|
|
49
|
-
}
|
|
50
|
-
result.push(`\nTotal: ${totalMatches} matches in ${totalFiles} files`);
|
|
51
|
-
|
|
52
|
-
return result.join("\n");
|
|
53
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression dispatcher.
|
|
3
|
-
* Tries known patterns in order of specificity; returns original text if nothing matches.
|
|
4
|
-
*/
|
|
5
|
-
import * as bashGit from "./bash-git.js";
|
|
6
|
-
import * as bashTest from "./bash-test.js";
|
|
7
|
-
import * as bashBuild from "./bash-build.js";
|
|
8
|
-
import * as bashInfra from "./bash-infra.js";
|
|
9
|
-
import * as bashPkg from "./bash-pkg.js";
|
|
10
|
-
import * as bashMisc from "./bash-misc.js";
|
|
11
|
-
import * as grepMod from "./grep.js";
|
|
12
|
-
import * as readMod from "./read.js";
|
|
13
|
-
import * as globMod from "./glob.js";
|
|
14
|
-
|
|
15
|
-
/** Compressors keyed by tool name hint. */
|
|
16
|
-
const TOOL_COMPRESSORS = {
|
|
17
|
-
Grep: grepMod,
|
|
18
|
-
Read: readMod,
|
|
19
|
-
Glob: globMod
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/** Bash compressors tried in order of specificity. */
|
|
23
|
-
const BASH_COMPRESSORS = [
|
|
24
|
-
bashGit,
|
|
25
|
-
bashTest,
|
|
26
|
-
bashBuild,
|
|
27
|
-
bashInfra,
|
|
28
|
-
bashPkg,
|
|
29
|
-
bashMisc
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Compress tool output deterministically.
|
|
34
|
-
* @param {string} text - raw tool output
|
|
35
|
-
* @param {string} [toolName] - tool name hint (e.g. "Bash", "Grep", "Read", "Glob")
|
|
36
|
-
* @returns {{ text: string, compressed: boolean }}
|
|
37
|
-
*/
|
|
38
|
-
export function compressDeterministic(text, toolName = "") {
|
|
39
|
-
if (!text || typeof text !== "string") return { text: text ?? "", compressed: false };
|
|
40
|
-
|
|
41
|
-
// Direct tool match
|
|
42
|
-
const directCompressor = TOOL_COMPRESSORS[toolName];
|
|
43
|
-
if (directCompressor && directCompressor.looksLike(text)) {
|
|
44
|
-
return { text: directCompressor.compact(text), compressed: true };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// For Bash or unknown tools, try all bash compressors in order
|
|
48
|
-
if (!toolName || toolName === "Bash") {
|
|
49
|
-
for (const compressor of BASH_COMPRESSORS) {
|
|
50
|
-
if (compressor.looksLike(text)) {
|
|
51
|
-
return { text: compressor.compact(text), compressed: true };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// No pattern matched
|
|
57
|
-
return { text, compressed: false };
|
|
58
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic compression for file read output.
|
|
3
|
-
* Truncates very long file contents while preserving structure.
|
|
4
|
-
*/
|
|
5
|
-
import { truncateLines, countTokens } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
const MAX_LINES = 200;
|
|
8
|
-
const TOKEN_THRESHOLD = 2000;
|
|
9
|
-
|
|
10
|
-
export function looksLike(text) {
|
|
11
|
-
// Read output is generic text; we use this as a fallback for very long content
|
|
12
|
-
// that looks like source code or structured data.
|
|
13
|
-
if (countTokens(text) < TOKEN_THRESHOLD) return false;
|
|
14
|
-
const lines = text.split("\n");
|
|
15
|
-
if (lines.length < 50) return false;
|
|
16
|
-
// Heuristic: numbered lines (cat -n style) or code-like content
|
|
17
|
-
return /^\s*\d+\t/.test(lines[0]) || /^(import |export |const |function |class |def |package )/.test(lines[0]);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function compact(text) {
|
|
21
|
-
return truncateLines(text, MAX_LINES);
|
|
22
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared utilities for deterministic compression.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/** Strip ANSI escape sequences (colors, bold, etc.) */
|
|
6
|
-
export function stripAnsi(text) {
|
|
7
|
-
// eslint-disable-next-line no-control-regex
|
|
8
|
-
return text.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Collapse consecutive whitespace (spaces/tabs) into a single space per line. */
|
|
12
|
-
export function collapseWhitespace(text) {
|
|
13
|
-
return text
|
|
14
|
-
.split("\n")
|
|
15
|
-
.map((line) => line.replace(/[ \t]+/g, " ").trim())
|
|
16
|
-
.join("\n")
|
|
17
|
-
.replace(/\n{3,}/g, "\n\n");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Keep at most `max` lines, appending a truncation note. */
|
|
21
|
-
export function truncateLines(text, max = 100) {
|
|
22
|
-
const lines = text.split("\n");
|
|
23
|
-
if (lines.length <= max) return text;
|
|
24
|
-
const kept = lines.slice(0, max);
|
|
25
|
-
kept.push(`... (${lines.length - max} more lines truncated)`);
|
|
26
|
-
return kept.join("\n");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Rough token estimate: chars / 4. */
|
|
30
|
-
export function countTokens(text) {
|
|
31
|
-
if (!text) return 0;
|
|
32
|
-
return Math.ceil(text.length / 4);
|
|
33
|
-
}
|