agentic-compaction 0.0.3 → 0.0.4

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.
@@ -0,0 +1,37 @@
1
+ ## src/cli.js
2
+ imports: 3 ext, ./index.js, ./formatter.js
3
+ fn: getDateStamp():9, pad(n):11
4
+ const: now, args, targetPath, jsonOutput, i +5 more
5
+ ## src/formatter.js
6
+ imports: ./parsers/python.js, ./parsers/babel.js, ./parsers/python.js
7
+ fn: estimateTokens(text)+:5, formatTokenCount(count)+:10, formatOutput(results)+:16
8
+ const: lines, output
9
+ ## src/index.js
10
+ imports: 1 ext, ./walker.js, ./formatter.js, ./parsers/babel.js, ./parsers/python.js
11
+ fn: compactFile(filePath, content)+:17, compactProject(rootPath, options = ...)+:38
12
+ const: skeleton, formatted, files, results, rawTokens +4 more
13
+ ## src/parsers/babel.js
14
+ imports: 2 ext
15
+ fn: isBabelParseable(path)+:9, isPascalCase(name):13, getReactHOCInfo(node):17, isCreateContext(node):52, extractDependencyArray(node):69, getParamsString(params):97, getTypeName(typeAnnotation):127, getReturnType(node):145, extractSignatures(code, filePath = ...)+:150, extractSkeleton(code, filePath = ...)+:246, formatSignaturesForPrompt(signatures)+:438, formatSkeletonForPrompt(skeleton)+:443
16
+ const: traverse, BABEL_EXTENSIONS, callee, hocType, method +75 more
17
+ ## src/parsers/python.js
18
+ fn: isPythonParseable(path)+:3, extractSkeleton(code, filePath = ...)+:14, formatSkeletonForPrompt(skeleton)+:148
19
+ const: PYTHON_EXTENSIONS, lines, skeleton, pendingDecorators, i +25 more
20
+ ## src/walker.js
21
+ imports: 2 ext, ./parsers/babel.js, ./parsers/python.js
22
+ fn: collectFiles(dir, rootDir = ..., files = ...)+:24, SKIP_DIRS_OR_DOT(name):48, isParseable(path):52
23
+ const: SKIP_DIRECTORIES, fullPath, relativePath
24
+ ## test/fixtures/sample.js
25
+ imports: 1 ext, ./api
26
+ components: MyComponent({ id })*:6
27
+ fn: helper(x):16
28
+ const: API_URL
29
+ hooks: useState: data, useEffect([id])
30
+ ## test/fixtures/sample.py
31
+ imports: 4 ext
32
+ classes: BaseProcessor:9, Config @dataclass (BaseProcessor):13
33
+ fn: process_data(items, timeout=30):17, fetch_remote(url, **kwargs):20, @app.route api_handler(request):24
34
+ const: MAX_RETRIES, DEFAULT_TIMEOUT
35
+ ## test/test.js
36
+ imports: 5 ext, ../src/index.js, ../src/parsers/babel.js, ../src/parsers/python.js, ../src/walker.js
37
+ const: __dirname, fixturesDir, code, skeleton, code +14 more
@@ -0,0 +1,37 @@
1
+ ## src/cli.js
2
+ imports: 3 ext, ./index.js, ./formatter.js
3
+ fn: getDateStamp():9, pad(n):11
4
+ const: now, args, targetPath, jsonOutput, i +5 more
5
+ ## src/formatter.js
6
+ imports: ./parsers/python.js, ./parsers/babel.js, ./parsers/python.js
7
+ fn: estimateTokens(text)+:5, formatTokenCount(count)+:10, formatOutput(results)+:16
8
+ const: lines, output
9
+ ## src/index.js
10
+ imports: 1 ext, ./walker.js, ./formatter.js, ./parsers/babel.js, ./parsers/python.js
11
+ fn: compactFile(filePath, content)+:17, compactProject(rootPath, options = ...)+:38
12
+ const: skeleton, formatted, files, results, rawTokens +4 more
13
+ ## src/parsers/babel.js
14
+ imports: 2 ext
15
+ fn: isBabelParseable(path)+:9, isPascalCase(name):13, getReactHOCInfo(node):17, isCreateContext(node):52, extractDependencyArray(node):69, getParamsString(params):97, getTypeName(typeAnnotation):127, getReturnType(node):145, extractSignatures(code, filePath = ...)+:150, extractSkeleton(code, filePath = ...)+:246, formatSignaturesForPrompt(signatures)+:438, formatSkeletonForPrompt(skeleton)+:443
16
+ const: traverse, BABEL_EXTENSIONS, callee, hocType, method +75 more
17
+ ## src/parsers/python.js
18
+ fn: isPythonParseable(path)+:3, extractSkeleton(code, filePath = ...)+:14, formatSkeletonForPrompt(skeleton)+:148
19
+ const: PYTHON_EXTENSIONS, lines, skeleton, pendingDecorators, i +25 more
20
+ ## src/walker.js
21
+ imports: 2 ext, ./parsers/babel.js, ./parsers/python.js
22
+ fn: collectFiles(dir, rootDir = ..., files = ...)+:24, SKIP_DIRS_OR_DOT(name):48, isParseable(path):52
23
+ const: SKIP_DIRECTORIES, fullPath, relativePath
24
+ ## test/fixtures/sample.js
25
+ imports: 1 ext, ./api
26
+ components: MyComponent({ id })*:6
27
+ fn: helper(x):16
28
+ const: API_URL
29
+ hooks: useState: data, useEffect([id])
30
+ ## test/fixtures/sample.py
31
+ imports: 4 ext
32
+ classes: BaseProcessor:9, Config @dataclass (BaseProcessor):13
33
+ fn: process_data(items, timeout=30):17, fetch_remote(url, **kwargs):20, @app.route api_handler(request):24
34
+ const: MAX_RETRIES, DEFAULT_TIMEOUT
35
+ ## test/test.js
36
+ imports: 5 ext, ../src/index.js, ../src/parsers/babel.js, ../src/parsers/python.js, ../src/walker.js
37
+ const: __dirname, fixturesDir, code, skeleton, code +14 more
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-compaction",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Walk a project directory and output a compact structural skeleton of the entire codebase",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -111,7 +111,11 @@ const getParamsString = (params) => {
111
111
  return `...${param.argument?.name || 'args'}`;
112
112
  }
113
113
  if (param.type === 'ObjectPattern') {
114
- return '{ ... }';
114
+ const keys = param.properties.map(p => {
115
+ if (p.type === 'RestElement') return `...${p.argument?.name || 'rest'}`;
116
+ return p.key?.name || '?';
117
+ });
118
+ return `{ ${keys.join(', ')} }`;
115
119
  }
116
120
  if (param.type === 'ArrayPattern') {
117
121
  return '[ ... ]';
@@ -265,16 +269,18 @@ export const extractSkeleton = (code, filePath = '') => {
265
269
 
266
270
  const skeleton = {
267
271
  imports: [],
268
- exports: [],
269
272
  components: [],
270
273
  functions: [],
271
- hooks: { useState: 0, useEffect: [], useCallback: 0, useMemo: 0, useRef: 0, custom: [] },
272
- constants: 0,
274
+ hooks: { useState: [], useEffect: [], useCallback: 0, useMemo: 0, useRef: 0, custom: [] },
275
+ constants: [],
273
276
  classes: [],
274
277
  interfaces: [],
275
278
  types: [],
276
279
  };
277
280
 
281
+ // Collect export info: name -> 'default' | 'named'
282
+ const exportMap = new Map();
283
+
278
284
  traverse(ast, {
279
285
  ImportDeclaration(path) {
280
286
  const source = path.node.source.value;
@@ -289,9 +295,9 @@ export const extractSkeleton = (code, filePath = '') => {
289
295
  ExportDefaultDeclaration(path) {
290
296
  const decl = path.node.declaration;
291
297
  if (decl.type === 'Identifier') {
292
- skeleton.exports.push({ name: decl.name, type: 'default' });
298
+ exportMap.set(decl.name, 'default');
293
299
  } else if (decl.type === 'FunctionDeclaration' && decl.id) {
294
- skeleton.exports.push({ name: decl.id.name, type: 'default' });
300
+ exportMap.set(decl.id.name, 'default');
295
301
  }
296
302
  },
297
303
 
@@ -299,18 +305,18 @@ export const extractSkeleton = (code, filePath = '') => {
299
305
  if (path.node.declaration) {
300
306
  const decl = path.node.declaration;
301
307
  if (decl.type === 'FunctionDeclaration' && decl.id) {
302
- skeleton.exports.push({ name: decl.id.name, type: 'named' });
308
+ exportMap.set(decl.id.name, 'named');
303
309
  } else if (decl.type === 'VariableDeclaration') {
304
310
  decl.declarations.forEach(d => {
305
311
  if (d.id?.name) {
306
- skeleton.exports.push({ name: d.id.name, type: 'named' });
312
+ exportMap.set(d.id.name, 'named');
307
313
  }
308
314
  });
309
315
  }
310
316
  }
311
317
  if (path.node.specifiers) {
312
318
  path.node.specifiers.forEach(s => {
313
- skeleton.exports.push({ name: s.exported?.name || s.local.name, type: 'named' });
319
+ exportMap.set(s.exported?.name || s.local.name, 'named');
314
320
  });
315
321
  }
316
322
  },
@@ -319,10 +325,14 @@ export const extractSkeleton = (code, filePath = '') => {
319
325
  const name = path.node.id?.name;
320
326
  if (!name) return;
321
327
 
328
+ const params = getParamsString(path.node.params);
329
+ const isAsync = path.node.async || false;
330
+ const entry = { name, line: path.node.loc?.start?.line || 0, params, async: isAsync };
331
+
322
332
  if (isPascalCase(name)) {
323
- skeleton.components.push({ name, line: path.node.loc?.start?.line || 0 });
333
+ skeleton.components.push(entry);
324
334
  } else {
325
- skeleton.functions.push({ name, line: path.node.loc?.start?.line || 0 });
335
+ skeleton.functions.push(entry);
326
336
  }
327
337
  },
328
338
 
@@ -332,17 +342,25 @@ export const extractSkeleton = (code, filePath = '') => {
332
342
  if (!name || !init) return;
333
343
 
334
344
  if (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression') {
345
+ const params = getParamsString(init.params);
346
+ const isAsync = init.async || false;
347
+ const entry = { name, line: path.node.loc?.start?.line || 0, params, async: isAsync };
348
+
335
349
  if (isPascalCase(name)) {
336
- skeleton.components.push({ name, line: path.node.loc?.start?.line || 0 });
350
+ skeleton.components.push(entry);
337
351
  } else {
338
- skeleton.functions.push({ name, line: path.node.loc?.start?.line || 0 });
352
+ skeleton.functions.push(entry);
339
353
  }
340
354
  return;
341
355
  }
342
356
 
343
357
  const hocInfo = getReactHOCInfo(init);
344
358
  if (hocInfo) {
345
- skeleton.components.push({ name, line: path.node.loc?.start?.line || 0, hoc: hocInfo.type });
359
+ let params = '?';
360
+ if (hocInfo.innerFn) {
361
+ params = getParamsString(hocInfo.innerFn.params);
362
+ }
363
+ skeleton.components.push({ name, line: path.node.loc?.start?.line || 0, hoc: hocInfo.type, params, async: false });
346
364
  return;
347
365
  }
348
366
 
@@ -352,7 +370,7 @@ export const extractSkeleton = (code, filePath = '') => {
352
370
  return;
353
371
  }
354
372
 
355
- skeleton.constants++;
373
+ skeleton.constants.push(name);
356
374
  },
357
375
 
358
376
  CallExpression(path) {
@@ -361,7 +379,18 @@ export const extractSkeleton = (code, filePath = '') => {
361
379
  const hookName = callee.name;
362
380
  const line = path.node.loc?.start?.line || 0;
363
381
 
364
- if (hookName === 'useEffect') {
382
+ if (hookName === 'useState') {
383
+ // Extract destructured variable name from parent: const [name, setName] = useState(...)
384
+ let varName = null;
385
+ const parent = path.parent;
386
+ if (parent?.type === 'VariableDeclarator' && parent.id?.type === 'ArrayPattern') {
387
+ const first = parent.id.elements[0];
388
+ if (first?.type === 'Identifier') {
389
+ varName = first.name;
390
+ }
391
+ }
392
+ skeleton.hooks.useState.push(varName || '?');
393
+ } else if (hookName === 'useEffect') {
365
394
  const deps = extractDependencyArray(path.node.arguments[1]);
366
395
  skeleton.hooks.useEffect.push({ line, deps });
367
396
  } else if (skeleton.hooks[hookName] !== undefined) {
@@ -396,6 +425,13 @@ export const extractSkeleton = (code, filePath = '') => {
396
425
  },
397
426
  });
398
427
 
428
+ // Apply export markers to components, functions, classes
429
+ for (const entry of [...skeleton.components, ...skeleton.functions, ...skeleton.classes]) {
430
+ const exportType = exportMap.get(entry.name);
431
+ if (exportType === 'default') entry.exportMarker = '*';
432
+ else if (exportType === 'named') entry.exportMarker = '+';
433
+ }
434
+
399
435
  return skeleton;
400
436
  };
401
437
 
@@ -418,13 +454,13 @@ export const formatSkeletonForPrompt = (skeleton) => {
418
454
  lines.push(`imports: ${parts.join(', ')}`);
419
455
  }
420
456
 
421
- if (skeleton.exports.length > 0) {
422
- const exportNames = skeleton.exports.map(e => e.type === 'default' ? `${e.name}*` : e.name).join(', ');
423
- lines.push(`exports: ${exportNames}`);
424
- }
425
-
426
457
  if (skeleton.components.length > 0) {
427
- const componentList = skeleton.components.map(c => c.hoc ? `${c.name}(${c.hoc}):${c.line}` : `${c.name}:${c.line}`).join(', ');
458
+ const componentList = skeleton.components.map(c => {
459
+ const marker = c.exportMarker || '';
460
+ const params = c.params !== undefined ? `(${c.params})` : '';
461
+ const hoc = c.hoc ? `(${c.hoc})` : '';
462
+ return `${c.name}${hoc}${params}${marker}:${c.line}`;
463
+ }).join(', ');
428
464
  lines.push(`components: ${componentList}`);
429
465
  }
430
466
 
@@ -433,11 +469,26 @@ export const formatSkeletonForPrompt = (skeleton) => {
433
469
  }
434
470
 
435
471
  if (skeleton.functions.length > 0) {
436
- lines.push(`fn: ${skeleton.functions.map(f => `${f.name}:${f.line}`).join(', ')}`);
472
+ const funcList = skeleton.functions.map(f => {
473
+ const marker = f.exportMarker || '';
474
+ const asyncPrefix = f.async ? 'async ' : '';
475
+ const params = f.params !== undefined ? `(${f.params})` : '';
476
+ return `${asyncPrefix}${f.name}${params}${marker}:${f.line}`;
477
+ }).join(', ');
478
+ lines.push(`fn: ${funcList}`);
479
+ }
480
+
481
+ if (skeleton.constants.length > 0) {
482
+ const names = skeleton.constants;
483
+ if (names.length > 5) {
484
+ lines.push(`const: ${names.slice(0, 5).join(', ')} +${names.length - 5} more`);
485
+ } else {
486
+ lines.push(`const: ${names.join(', ')}`);
487
+ }
437
488
  }
438
489
 
439
490
  const hookParts = [];
440
- if (skeleton.hooks.useState > 0) hookParts.push(`useState(${skeleton.hooks.useState})`);
491
+ if (skeleton.hooks.useState.length > 0) hookParts.push(`useState: ${skeleton.hooks.useState.join(', ')}`);
441
492
  if (skeleton.hooks.useCallback > 0) hookParts.push(`useCallback(${skeleton.hooks.useCallback})`);
442
493
  if (skeleton.hooks.useMemo > 0) hookParts.push(`useMemo(${skeleton.hooks.useMemo})`);
443
494
  if (skeleton.hooks.useRef > 0) hookParts.push(`useRef(${skeleton.hooks.useRef})`);
@@ -445,9 +496,9 @@ export const formatSkeletonForPrompt = (skeleton) => {
445
496
 
446
497
  if (skeleton.hooks.useEffect.length > 0) {
447
498
  const effects = skeleton.hooks.useEffect.map(eff => {
448
- if (eff.deps === null) return `useEffect(∞):${eff.line}`;
449
- if (eff.deps === '?') return `useEffect(?):${eff.line}`;
450
- return `useEffect([${eff.deps.join(',')}]):${eff.line}`;
499
+ if (eff.deps === null) return `useEffect(∞)`;
500
+ if (eff.deps === '?') return `useEffect(?)`;
501
+ return `useEffect([${eff.deps.join(',')}])`;
451
502
  });
452
503
  hookParts.push(...effects);
453
504
  }
@@ -457,7 +508,11 @@ export const formatSkeletonForPrompt = (skeleton) => {
457
508
  }
458
509
 
459
510
  if (skeleton.classes.length > 0) {
460
- lines.push(`classes: ${skeleton.classes.map(c => `${c.name}:${c.line}`).join(', ')}`);
511
+ const classList = skeleton.classes.map(c => {
512
+ const marker = c.exportMarker || '';
513
+ return `${c.name}${marker}:${c.line}`;
514
+ }).join(', ');
515
+ lines.push(`classes: ${classList}`);
461
516
  }
462
517
 
463
518
  if (skeleton.interfaces.length > 0 || skeleton.types.length > 0) {
@@ -17,7 +17,7 @@ export const extractSkeleton = (code, filePath = '') => {
17
17
  imports: [],
18
18
  functions: [],
19
19
  classes: [],
20
- constants: 0,
20
+ constants: [],
21
21
  };
22
22
 
23
23
  // Collect decorators as we scan
@@ -126,9 +126,9 @@ export const extractSkeleton = (code, filePath = '') => {
126
126
  }
127
127
 
128
128
  // Top-level assignments (constants)
129
- const assignMatch = line.match(/^[A-Za-z_]\w*\s*[=:]/) || line.match(/^[A-Za-z_]\w*\s*:/);
129
+ const assignMatch = line.match(/^([A-Za-z_]\w*)\s*[=:]/);
130
130
  if (assignMatch) {
131
- skeleton.constants++;
131
+ skeleton.constants.push(assignMatch[1]);
132
132
  pendingDecorators = [];
133
133
  continue;
134
134
  }
@@ -172,10 +172,20 @@ export const formatSkeletonForPrompt = (skeleton) => {
172
172
  if (skeleton.functions.length > 0) {
173
173
  const funcList = skeleton.functions.map(f => {
174
174
  const deco = f.decorators.length > 0 ? `@${f.decorators[0]} ` : '';
175
- return `${deco}${f.name}:${f.line}`;
175
+ const params = f.params ? `(${f.params})` : '()';
176
+ return `${deco}${f.name}${params}:${f.line}`;
176
177
  }).join(', ');
177
178
  lines.push(`fn: ${funcList}`);
178
179
  }
179
180
 
181
+ if (skeleton.constants.length > 0) {
182
+ const names = skeleton.constants;
183
+ if (names.length > 5) {
184
+ lines.push(`const: ${names.slice(0, 5).join(', ')} +${names.length - 5} more`);
185
+ } else {
186
+ lines.push(`const: ${names.join(', ')}`);
187
+ }
188
+ }
189
+
180
190
  return lines.join('\n');
181
191
  };
package/test/test.js CHANGED
@@ -21,7 +21,7 @@ describe('babel parser', () => {
21
21
  assert.ok(skeleton.imports.length > 0);
22
22
  assert.ok(skeleton.components.length > 0);
23
23
  assert.ok(skeleton.functions.length > 0);
24
- assert.strictEqual(skeleton.hooks.useState, 1);
24
+ assert.deepStrictEqual(skeleton.hooks.useState, ['data']);
25
25
  assert.strictEqual(skeleton.hooks.useEffect.length, 1);
26
26
  });
27
27
  });
@@ -35,7 +35,7 @@ describe('python parser', () => {
35
35
  assert.strictEqual(skeleton.imports.length, 4);
36
36
  assert.strictEqual(skeleton.functions.length, 3);
37
37
  assert.strictEqual(skeleton.classes.length, 2);
38
- assert.ok(skeleton.constants >= 2);
38
+ assert.ok(skeleton.constants.length >= 2);
39
39
  });
40
40
 
41
41
  it('parses decorators', () => {