agentic-compaction 0.0.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/.orchestration/orchestration.md +52 -0
- package/.orchestration/workflows/react/bugfix.md +32 -0
- package/.orchestration/workflows/react/docs.md +59 -0
- package/.orchestration/workflows/react/feature.md +37 -0
- package/.orchestration/workflows/react/performance.md +59 -0
- package/.orchestration/workflows/react/pr.md +19 -0
- package/.orchestration/workflows/react/refactor.md +32 -0
- package/.orchestration/workflows/react/review.md +46 -0
- package/ORCHESTRATION.md +5 -0
- package/README.md +118 -0
- package/compacted_2026-02-12_15-24-24.txt +34 -0
- package/compacted_standalonecompaction_2026-02-12_15-26-38.md +34 -0
- package/compacted_standalonecompaction_2026-02-12_15-28-25.md +34 -0
- package/package.json +25 -0
- package/src/cli.js +59 -0
- package/src/formatter.js +33 -0
- package/src/index.js +75 -0
- package/src/parsers/babel.js +469 -0
- package/src/parsers/python.js +181 -0
- package/src/walker.js +54 -0
- package/test/fixtures/sample.js +18 -0
- package/test/fixtures/sample.py +25 -0
- package/test/test.js +102 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const PYTHON_EXTENSIONS = ['.py'];
|
|
2
|
+
|
|
3
|
+
export const isPythonParseable = (path) => {
|
|
4
|
+
return PYTHON_EXTENSIONS.some(ext => path.endsWith(ext));
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract skeleton from Python source code using regex-based line-by-line parsing.
|
|
9
|
+
* Only parses top-level statements (no indentation).
|
|
10
|
+
* @param {string} code - Source code to parse
|
|
11
|
+
* @param {string} filePath - File path for error reporting
|
|
12
|
+
* @returns {Object|null} Skeleton data
|
|
13
|
+
*/
|
|
14
|
+
export const extractSkeleton = (code, filePath = '') => {
|
|
15
|
+
const lines = code.split('\n');
|
|
16
|
+
const skeleton = {
|
|
17
|
+
imports: [],
|
|
18
|
+
functions: [],
|
|
19
|
+
classes: [],
|
|
20
|
+
constants: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Collect decorators as we scan
|
|
24
|
+
let pendingDecorators = [];
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < lines.length; i++) {
|
|
27
|
+
const line = lines[i];
|
|
28
|
+
const lineNum = i + 1;
|
|
29
|
+
|
|
30
|
+
// Skip empty lines and comments
|
|
31
|
+
if (/^\s*$/.test(line) || /^\s*#/.test(line)) continue;
|
|
32
|
+
|
|
33
|
+
// Only process top-level (no indentation)
|
|
34
|
+
if (/^\s/.test(line) && !/^@/.test(line)) {
|
|
35
|
+
pendingDecorators = [];
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Decorators
|
|
40
|
+
const decoratorMatch = line.match(/^@(\w[\w.]*)/);
|
|
41
|
+
if (decoratorMatch) {
|
|
42
|
+
pendingDecorators.push(decoratorMatch[1]);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// import x / import x, y
|
|
47
|
+
const importMatch = line.match(/^import\s+(.+)/);
|
|
48
|
+
if (importMatch) {
|
|
49
|
+
const modules = importMatch[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0]);
|
|
50
|
+
for (const mod of modules) {
|
|
51
|
+
skeleton.imports.push({ module: mod, names: [] });
|
|
52
|
+
}
|
|
53
|
+
pendingDecorators = [];
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// from x import y, z
|
|
58
|
+
const fromImportMatch = line.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
|
|
59
|
+
if (fromImportMatch) {
|
|
60
|
+
const module = fromImportMatch[1];
|
|
61
|
+
let namesStr = fromImportMatch[2].trim();
|
|
62
|
+
|
|
63
|
+
// Handle multi-line imports with parentheses
|
|
64
|
+
if (namesStr.startsWith('(')) {
|
|
65
|
+
namesStr = namesStr.slice(1);
|
|
66
|
+
while (i + 1 < lines.length && !namesStr.includes(')')) {
|
|
67
|
+
i++;
|
|
68
|
+
namesStr += ' ' + lines[i].trim();
|
|
69
|
+
}
|
|
70
|
+
namesStr = namesStr.replace(')', '');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const names = namesStr
|
|
74
|
+
.split(',')
|
|
75
|
+
.map(s => s.trim().split(/\s+as\s+/)[0])
|
|
76
|
+
.filter(Boolean);
|
|
77
|
+
|
|
78
|
+
skeleton.imports.push({ module, names });
|
|
79
|
+
pendingDecorators = [];
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// async def / def
|
|
84
|
+
const funcMatch = line.match(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
|
|
85
|
+
if (funcMatch) {
|
|
86
|
+
// For multi-line params, grab until closing paren
|
|
87
|
+
let params = funcMatch[2];
|
|
88
|
+
if (!line.includes(')')) {
|
|
89
|
+
let j = i + 1;
|
|
90
|
+
while (j < lines.length && !lines[j].includes(')')) {
|
|
91
|
+
params += ' ' + lines[j].trim();
|
|
92
|
+
j++;
|
|
93
|
+
}
|
|
94
|
+
if (j < lines.length) {
|
|
95
|
+
params += ' ' + lines[j].split(')')[0].trim();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Clean up params: remove type annotations and defaults for brevity
|
|
99
|
+
params = params.replace(/\s+/g, ' ').trim();
|
|
100
|
+
|
|
101
|
+
skeleton.functions.push({
|
|
102
|
+
name: funcMatch[1],
|
|
103
|
+
line: lineNum,
|
|
104
|
+
decorators: [...pendingDecorators],
|
|
105
|
+
params,
|
|
106
|
+
});
|
|
107
|
+
pendingDecorators = [];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// class Name(bases):
|
|
112
|
+
const classMatch = line.match(/^class\s+(\w+)\s*(?:\(([^)]*)\))?\s*:/);
|
|
113
|
+
if (classMatch) {
|
|
114
|
+
const bases = classMatch[2]
|
|
115
|
+
? classMatch[2].split(',').map(s => s.trim()).filter(Boolean)
|
|
116
|
+
: [];
|
|
117
|
+
|
|
118
|
+
skeleton.classes.push({
|
|
119
|
+
name: classMatch[1],
|
|
120
|
+
line: lineNum,
|
|
121
|
+
decorators: [...pendingDecorators],
|
|
122
|
+
bases,
|
|
123
|
+
});
|
|
124
|
+
pendingDecorators = [];
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Top-level assignments (constants)
|
|
129
|
+
const assignMatch = line.match(/^[A-Za-z_]\w*\s*[=:]/) || line.match(/^[A-Za-z_]\w*\s*:/);
|
|
130
|
+
if (assignMatch) {
|
|
131
|
+
skeleton.constants++;
|
|
132
|
+
pendingDecorators = [];
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Reset decorators for any other top-level statement
|
|
137
|
+
pendingDecorators = [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return skeleton;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Format Python skeleton for prompt output
|
|
145
|
+
* @param {Object} skeleton - Skeleton data object
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
export const formatSkeletonForPrompt = (skeleton) => {
|
|
149
|
+
if (!skeleton) return '';
|
|
150
|
+
|
|
151
|
+
const lines = [];
|
|
152
|
+
|
|
153
|
+
if (skeleton.imports.length > 0) {
|
|
154
|
+
const local = skeleton.imports.filter(i => i.module.startsWith('.'));
|
|
155
|
+
const extCount = skeleton.imports.length - local.length;
|
|
156
|
+
const parts = [];
|
|
157
|
+
if (extCount > 0) parts.push(`${extCount} ext`);
|
|
158
|
+
parts.push(...local.map(i => i.module));
|
|
159
|
+
lines.push(`imports: ${parts.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (skeleton.classes.length > 0) {
|
|
163
|
+
const classList = skeleton.classes.map(c => {
|
|
164
|
+
const parts = [c.name];
|
|
165
|
+
if (c.decorators.length > 0) parts.push(`@${c.decorators[0]}`);
|
|
166
|
+
if (c.bases.length > 0) parts.push(`(${c.bases.join(',')})`);
|
|
167
|
+
return `${parts.join(' ')}:${c.line}`;
|
|
168
|
+
}).join(', ');
|
|
169
|
+
lines.push(`classes: ${classList}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (skeleton.functions.length > 0) {
|
|
173
|
+
const funcList = skeleton.functions.map(f => {
|
|
174
|
+
const deco = f.decorators.length > 0 ? `@${f.decorators[0]} ` : '';
|
|
175
|
+
return `${deco}${f.name}:${f.line}`;
|
|
176
|
+
}).join(', ');
|
|
177
|
+
lines.push(`fn: ${funcList}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return lines.join('\n');
|
|
181
|
+
};
|
package/src/walker.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { isBabelParseable } from './parsers/babel.js';
|
|
4
|
+
import { isPythonParseable } from './parsers/python.js';
|
|
5
|
+
|
|
6
|
+
const SKIP_DIRECTORIES = new Set([
|
|
7
|
+
'node_modules',
|
|
8
|
+
'dist',
|
|
9
|
+
'.git',
|
|
10
|
+
'target',
|
|
11
|
+
'build',
|
|
12
|
+
'.next',
|
|
13
|
+
'.turbo',
|
|
14
|
+
'out',
|
|
15
|
+
'coverage',
|
|
16
|
+
'.cache',
|
|
17
|
+
'__pycache__',
|
|
18
|
+
'.venv',
|
|
19
|
+
'venv',
|
|
20
|
+
'.idea',
|
|
21
|
+
'.vscode',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export function collectFiles(dir, rootDir = dir, files = []) {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
28
|
+
} catch {
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const fullPath = join(dir, entry.name);
|
|
34
|
+
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
if (!SKIP_DIRS_OR_DOT(entry.name)) {
|
|
37
|
+
collectFiles(fullPath, rootDir, files);
|
|
38
|
+
}
|
|
39
|
+
} else if (entry.isFile() && isParseable(fullPath)) {
|
|
40
|
+
const relativePath = fullPath.slice(rootDir.length + 1);
|
|
41
|
+
files.push({ path: fullPath, relativePath });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return files;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function SKIP_DIRS_OR_DOT(name) {
|
|
49
|
+
return SKIP_DIRECTORIES.has(name) || name.startsWith('.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isParseable(path) {
|
|
53
|
+
return isBabelParseable(path) || isPythonParseable(path);
|
|
54
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { fetchData } from './api';
|
|
3
|
+
|
|
4
|
+
const API_URL = 'https://example.com';
|
|
5
|
+
|
|
6
|
+
export function MyComponent({ id }) {
|
|
7
|
+
const [data, setData] = useState(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
fetchData(id).then(setData);
|
|
11
|
+
}, [id]);
|
|
12
|
+
|
|
13
|
+
return <div>{data}</div>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const helper = (x) => x * 2;
|
|
17
|
+
|
|
18
|
+
export default MyComponent;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
MAX_RETRIES = 3
|
|
7
|
+
DEFAULT_TIMEOUT = 30
|
|
8
|
+
|
|
9
|
+
class BaseProcessor:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Config(BaseProcessor):
|
|
14
|
+
name: str
|
|
15
|
+
value: int
|
|
16
|
+
|
|
17
|
+
def process_data(items, timeout=30):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
async def fetch_remote(url, **kwargs):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@app.route('/api')
|
|
24
|
+
def api_handler(request):
|
|
25
|
+
pass
|
package/test/test.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { resolve, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
import { compactFile, compactProject } from '../src/index.js';
|
|
8
|
+
import { extractSkeleton as extractBabelSkeleton } from '../src/parsers/babel.js';
|
|
9
|
+
import { extractSkeleton as extractPythonSkeleton, formatSkeletonForPrompt as formatPythonSkeleton } from '../src/parsers/python.js';
|
|
10
|
+
import { collectFiles } from '../src/walker.js';
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const fixturesDir = resolve(__dirname, 'fixtures');
|
|
14
|
+
|
|
15
|
+
describe('babel parser', () => {
|
|
16
|
+
it('extracts skeleton from JS file', () => {
|
|
17
|
+
const code = readFileSync(resolve(fixturesDir, 'sample.js'), 'utf-8');
|
|
18
|
+
const skeleton = extractBabelSkeleton(code, 'sample.js');
|
|
19
|
+
|
|
20
|
+
assert.ok(skeleton);
|
|
21
|
+
assert.ok(skeleton.imports.length > 0);
|
|
22
|
+
assert.ok(skeleton.components.length > 0);
|
|
23
|
+
assert.ok(skeleton.functions.length > 0);
|
|
24
|
+
assert.strictEqual(skeleton.hooks.useState, 1);
|
|
25
|
+
assert.strictEqual(skeleton.hooks.useEffect.length, 1);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('python parser', () => {
|
|
30
|
+
it('extracts skeleton from Python file', () => {
|
|
31
|
+
const code = readFileSync(resolve(fixturesDir, 'sample.py'), 'utf-8');
|
|
32
|
+
const skeleton = extractPythonSkeleton(code, 'sample.py');
|
|
33
|
+
|
|
34
|
+
assert.ok(skeleton);
|
|
35
|
+
assert.strictEqual(skeleton.imports.length, 4);
|
|
36
|
+
assert.strictEqual(skeleton.functions.length, 3);
|
|
37
|
+
assert.strictEqual(skeleton.classes.length, 2);
|
|
38
|
+
assert.ok(skeleton.constants >= 2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('parses decorators', () => {
|
|
42
|
+
const code = readFileSync(resolve(fixturesDir, 'sample.py'), 'utf-8');
|
|
43
|
+
const skeleton = extractPythonSkeleton(code, 'sample.py');
|
|
44
|
+
|
|
45
|
+
const config = skeleton.classes.find(c => c.name === 'Config');
|
|
46
|
+
assert.ok(config);
|
|
47
|
+
assert.deepStrictEqual(config.decorators, ['dataclass']);
|
|
48
|
+
assert.deepStrictEqual(config.bases, ['BaseProcessor']);
|
|
49
|
+
|
|
50
|
+
const apiHandler = skeleton.functions.find(f => f.name === 'api_handler');
|
|
51
|
+
assert.ok(apiHandler);
|
|
52
|
+
assert.deepStrictEqual(apiHandler.decorators, ['app.route']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('formats skeleton', () => {
|
|
56
|
+
const code = readFileSync(resolve(fixturesDir, 'sample.py'), 'utf-8');
|
|
57
|
+
const skeleton = extractPythonSkeleton(code, 'sample.py');
|
|
58
|
+
const output = formatPythonSkeleton(skeleton);
|
|
59
|
+
|
|
60
|
+
assert.ok(output.includes('imports:'));
|
|
61
|
+
assert.ok(output.includes('classes:'));
|
|
62
|
+
assert.ok(output.includes('fn:'));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('walker', () => {
|
|
67
|
+
it('collects files from fixtures', () => {
|
|
68
|
+
const files = collectFiles(fixturesDir);
|
|
69
|
+
assert.ok(files.length >= 2);
|
|
70
|
+
assert.ok(files.some(f => f.relativePath.endsWith('.js')));
|
|
71
|
+
assert.ok(files.some(f => f.relativePath.endsWith('.py')));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('compactFile', () => {
|
|
76
|
+
it('compacts a JS file', () => {
|
|
77
|
+
const code = readFileSync(resolve(fixturesDir, 'sample.js'), 'utf-8');
|
|
78
|
+
const result = compactFile('sample.js', code);
|
|
79
|
+
assert.ok(result.skeleton);
|
|
80
|
+
assert.ok(result.formatted.length > 0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('compacts a Python file', () => {
|
|
84
|
+
const code = readFileSync(resolve(fixturesDir, 'sample.py'), 'utf-8');
|
|
85
|
+
const result = compactFile('sample.py', code);
|
|
86
|
+
assert.ok(result.skeleton);
|
|
87
|
+
assert.ok(result.formatted.length > 0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('compactProject', () => {
|
|
92
|
+
it('compacts the fixtures directory', () => {
|
|
93
|
+
const result = compactProject(fixturesDir);
|
|
94
|
+
assert.ok(result.output.length > 0);
|
|
95
|
+
assert.ok(result.output.includes('## sample.js'));
|
|
96
|
+
assert.ok(result.output.includes('## sample.py'));
|
|
97
|
+
assert.ok(result.stats.files >= 2);
|
|
98
|
+
assert.ok(result.stats.rawTokens > 0);
|
|
99
|
+
assert.ok(result.stats.compactedTokens > 0);
|
|
100
|
+
assert.ok(result.stats.compactedTokens < result.stats.rawTokens);
|
|
101
|
+
});
|
|
102
|
+
});
|