sigmap 6.15.0 → 7.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/AGENTS.md +798 -401
- package/CHANGELOG.md +36 -0
- package/README.md +8 -8
- package/gen-context.js +1498 -462
- package/llms-full.txt +295 -0
- package/llms.txt +56 -0
- package/package.json +31 -14
- package/packages/adapters/claude.js +0 -6
- package/packages/adapters/codex.js +1 -61
- package/packages/adapters/copilot.js +0 -8
- package/packages/adapters/cursor.js +0 -4
- package/packages/adapters/gemini.js +0 -2
- package/packages/adapters/openai.js +0 -2
- package/packages/adapters/windsurf.js +0 -4
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/loader.js +6 -6
- package/src/discovery/source-root-scorer.js +2 -2
- package/src/extractors/prdiff.js +28 -3
- package/src/format/llms-txt.js +2 -3
- package/src/format/usage-guidance.js +28 -0
- package/src/mcp/handlers.js +3 -8
- package/src/mcp/server.js +1 -1
- package/src/nudge.js +92 -0
- package/src/session/notes.js +2 -8
- package/src/squeeze/cilog.js +71 -0
- package/src/squeeze/classify.js +115 -0
- package/src/squeeze/index.js +69 -0
- package/src/squeeze/jsonpayload.js +54 -0
- package/src/squeeze/stacktrace.js +135 -0
- package/src/util/git.js +31 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stack-trace squeeze (v7.0.0) — the highest-value squeeze module.
|
|
5
|
+
*
|
|
6
|
+
* Dedupes repeated exceptions, strips vendor frames, keeps frames in the user's
|
|
7
|
+
* own source dirs, and — the differentiator — **enriches the top kept frame**
|
|
8
|
+
* with its real signature from the SigMap symbol index (`buildSigIndex`).
|
|
9
|
+
* Generic log summarizers can't do this; SigMap has the repo's symbol map.
|
|
10
|
+
*
|
|
11
|
+
* Pure/deterministic. The symbol index is injected via `opts.symbolIndex` so
|
|
12
|
+
* the module is unit-testable without touching the filesystem.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const VENDOR_RE = /(?:^|[\\/])(?:node_modules|vendor|site-packages|dist|build|\.venv|venv|third_party|external|\.cargo|go\/pkg\/mod)[\\/]/;
|
|
18
|
+
|
|
19
|
+
/** Parse a frame line across JS/TS, Python, Java/Kotlin, Go, Rust, native. */
|
|
20
|
+
function parseFrame(line) {
|
|
21
|
+
let m;
|
|
22
|
+
if ((m = line.match(/^\s*at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/))) return { fn: m[1], file: m[2], line: +m[3], raw: line };
|
|
23
|
+
if ((m = line.match(/^\s*at\s+(.+?):(\d+):(\d+)\s*$/))) return { fn: '', file: m[1], line: +m[2], raw: line };
|
|
24
|
+
if ((m = line.match(/^\s*at\s+([\w$.<>]+)\((.+?):(\d+)\)/))) return { fn: m[1], file: m[2], line: +m[3], raw: line };
|
|
25
|
+
if ((m = line.match(/^\s*File\s+"(.+?)",\s+line\s+(\d+)(?:,\s+in\s+(.+))?/))) return { fn: (m[3] || '').trim(), file: m[1], line: +m[2], raw: line };
|
|
26
|
+
if ((m = line.match(/^\s*(.+\.(?:go|rs)):(\d+)/))) return { fn: '', file: m[1], line: +m[2], raw: line };
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isVendor(file) { return VENDOR_RE.test(String(file).replace(/\\/g, '/')); }
|
|
31
|
+
|
|
32
|
+
function inSrcDirs(file, srcDirs) {
|
|
33
|
+
const f = String(file).replace(/\\/g, '/');
|
|
34
|
+
return srcDirs.some((d) => {
|
|
35
|
+
const dd = String(d).replace(/^\.\//, '').replace(/\/$/, '');
|
|
36
|
+
return dd && (f === dd || f.startsWith(dd + '/') || f.includes('/' + dd + '/'));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Look up the real signature for a frame in the SigMap symbol index. */
|
|
41
|
+
function enrichFrame(frame, symbolIndex) {
|
|
42
|
+
if (!symbolIndex || !frame) return null;
|
|
43
|
+
const want = String(frame.file).replace(/\\/g, '/');
|
|
44
|
+
const base = path.basename(want);
|
|
45
|
+
let key = null;
|
|
46
|
+
for (const k0 of symbolIndex.keys()) {
|
|
47
|
+
const k = String(k0).replace(/\\/g, '/');
|
|
48
|
+
if (k === want || want.endsWith('/' + k) || k.endsWith('/' + want)) { key = k0; break; }
|
|
49
|
+
if (!key && path.basename(k) === base) key = k0;
|
|
50
|
+
}
|
|
51
|
+
if (!key) return null;
|
|
52
|
+
const sigs = symbolIndex.get(key) || [];
|
|
53
|
+
const wantFn = frame.fn ? frame.fn.split('.').pop() : '';
|
|
54
|
+
let byLine = null, byName = null;
|
|
55
|
+
for (const sig of sigs) {
|
|
56
|
+
const s = String(sig);
|
|
57
|
+
const mm = s.match(/:(\d+)(?:-(\d+))?\s*$/);
|
|
58
|
+
if (mm) {
|
|
59
|
+
const a = +mm[1], b = mm[2] ? +mm[2] : a;
|
|
60
|
+
if (frame.line >= a && frame.line <= b) byLine = s;
|
|
61
|
+
}
|
|
62
|
+
if (wantFn && new RegExp('\\b' + wantFn.replace(/[^\w$]/g, '') + '\\b').test(s)) byName = byName || s;
|
|
63
|
+
}
|
|
64
|
+
const sig = byLine || byName;
|
|
65
|
+
return sig ? { file: key, sig: sig.replace(/\s*:\d+(?:-\d+)?\s*$/, '').trim() } : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} input
|
|
70
|
+
* @param {object} [opts]
|
|
71
|
+
* @param {string[]} [opts.srcDirs] user source dirs (default ['src'])
|
|
72
|
+
* @param {Map} [opts.symbolIndex] SigMap signature index for enrichment
|
|
73
|
+
* @param {number} [opts.maxFrames=8] cap on kept source frames
|
|
74
|
+
* @returns {{ squeezed, kept, stripped, enriched }}
|
|
75
|
+
*/
|
|
76
|
+
function squeezeStackTrace(input, opts = {}) {
|
|
77
|
+
const srcDirs = (opts.srcDirs && opts.srcDirs.length) ? opts.srcDirs : ['src'];
|
|
78
|
+
const maxFrames = opts.maxFrames != null ? opts.maxFrames : 8;
|
|
79
|
+
const lines = input.split('\n');
|
|
80
|
+
|
|
81
|
+
const headerCount = new Map();
|
|
82
|
+
const headerOrder = [];
|
|
83
|
+
const frames = [];
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const f = parseFrame(line);
|
|
86
|
+
if (f) { frames.push(f); continue; }
|
|
87
|
+
const t = line.trim();
|
|
88
|
+
if (!t) continue;
|
|
89
|
+
if (!headerCount.has(t)) headerOrder.push(t);
|
|
90
|
+
headerCount.set(t, (headerCount.get(t) || 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
let dupFrames = 0;
|
|
95
|
+
const nonVendor = [];
|
|
96
|
+
const sourceFrames = [];
|
|
97
|
+
let vendorCount = 0;
|
|
98
|
+
for (const f of frames) {
|
|
99
|
+
const k = f.file + ':' + f.line;
|
|
100
|
+
if (seen.has(k)) { dupFrames++; continue; }
|
|
101
|
+
seen.add(k);
|
|
102
|
+
if (isVendor(f.file)) { vendorCount++; continue; }
|
|
103
|
+
nonVendor.push(f);
|
|
104
|
+
if (inSrcDirs(f.file, srcDirs)) sourceFrames.push(f);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Prefer source frames; never return empty (fall back to top non-vendor, then raw).
|
|
108
|
+
const shown = sourceFrames.length ? sourceFrames.slice(0, maxFrames)
|
|
109
|
+
: (nonVendor.length ? nonVendor.slice(0, 3) : frames.slice(0, 3));
|
|
110
|
+
|
|
111
|
+
const enrichment = shown.length ? enrichFrame(shown[0], opts.symbolIndex) : null;
|
|
112
|
+
|
|
113
|
+
const out = [];
|
|
114
|
+
for (const h of headerOrder) {
|
|
115
|
+
const n = headerCount.get(h);
|
|
116
|
+
out.push(n > 1 ? `${h} (occurred ×${n})` : h);
|
|
117
|
+
}
|
|
118
|
+
for (let i = 0; i < shown.length; i++) {
|
|
119
|
+
out.push(' ' + shown[i].raw.trim());
|
|
120
|
+
if (i === 0 && enrichment) out.push(` ↳ ${enrichment.sig} [${enrichment.file}]`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
squeezed: out.join('\n'),
|
|
125
|
+
kept: [
|
|
126
|
+
`${headerOrder.length} unique exception(s)`,
|
|
127
|
+
`top ${shown.length} ${sourceFrames.length ? 'source ' : ''}frame(s)`,
|
|
128
|
+
...(enrichment ? [`enriched ${path.basename(shown[0].file)}:${shown[0].line}`] : []),
|
|
129
|
+
],
|
|
130
|
+
stripped: [`${vendorCount} vendor frame(s)`, `${dupFrames} duplicate frame(s)`],
|
|
131
|
+
enriched: !!enrichment,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { squeezeStackTrace, parseFrame, isVendor, inSrcDirs, enrichFrame };
|
package/src/util/git.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shell-free git invocation.
|
|
5
|
+
*
|
|
6
|
+
* Uses `execFileSync('git', [...])`, which executes the git binary directly —
|
|
7
|
+
* it never spawns a system shell (`/bin/sh -c`). That means:
|
|
8
|
+
* - no shell-injection surface (arguments are passed as an array, never
|
|
9
|
+
* interpolated into a command string), and
|
|
10
|
+
* - supply-chain scanners (e.g. Socket) do not flag a "Shell access" capability.
|
|
11
|
+
*
|
|
12
|
+
* stderr is discarded by default (replaces the old `2>/dev/null` redirects).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { execFileSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
function git(args, opts = {}) {
|
|
18
|
+
return execFileSync('git', args, {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
21
|
+
...opts,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Convenience: run git and return trimmed stdout, or '' on any failure.
|
|
26
|
+
function tryGit(args, opts = {}) {
|
|
27
|
+
try { return git(args, opts).toString().trim(); }
|
|
28
|
+
catch (_) { return ''; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { git, tryGit };
|