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
package/gen-context.js
CHANGED
|
@@ -236,12 +236,11 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
try {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
);
|
|
239
|
+
// Shell-free: run the node binary directly with the URL passed as an argv
|
|
240
|
+
// value (never interpolated into a command string, never via /bin/sh).
|
|
241
|
+
const { execFileSync } = require('child_process');
|
|
242
|
+
const SYNC_FETCH = "const u=process.argv[1];const h=require(u.startsWith('https')?'https':'http');let d='';h.get(u,r=>{r.on('data',c=>d+=c);r.on('end',()=>process.stdout.write(d))}).on('error',()=>process.exit(1))";
|
|
243
|
+
const out = execFileSync(process.execPath, ['-e', SYNC_FETCH, extendsVal], { timeout: 10000, encoding: 'utf8' });
|
|
245
244
|
const parsed = JSON.parse(out);
|
|
246
245
|
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
247
246
|
fs.writeFileSync(cachePath, JSON.stringify(parsed), 'utf8');
|
|
@@ -3164,55 +3163,78 @@ __factories["./src/extractors/coverage"] = function(module, exports) {
|
|
|
3164
3163
|
|
|
3165
3164
|
// ── ./src/extractors/prdiff ──
|
|
3166
3165
|
__factories["./src/extractors/prdiff"] = function(module, exports) {
|
|
3166
|
+
'use strict';
|
|
3167
3167
|
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
const
|
|
3184
|
-
const
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
return m;
|
|
3192
|
-
};
|
|
3193
|
-
|
|
3194
|
-
const aBy = byName(added);
|
|
3195
|
-
const rBy = byName(removed);
|
|
3196
|
-
const modified = [];
|
|
3168
|
+
/**
|
|
3169
|
+
* Compare signature arrays and produce compact diff markers.
|
|
3170
|
+
* @param {string[]} baseSigs
|
|
3171
|
+
* @param {string[]} currentSigs
|
|
3172
|
+
* @returns {{added:string[], removed:string[], modified:string[]}}
|
|
3173
|
+
*/
|
|
3174
|
+
function diffSignatures(baseSigs, currentSigs) {
|
|
3175
|
+
const base = new Set(baseSigs || []);
|
|
3176
|
+
const curr = new Set(currentSigs || []);
|
|
3177
|
+
|
|
3178
|
+
const added = [...curr].filter((s) => !base.has(s));
|
|
3179
|
+
const removed = [...base].filter((s) => !curr.has(s));
|
|
3180
|
+
|
|
3181
|
+
const byName = (arr) => {
|
|
3182
|
+
const m = new Map();
|
|
3183
|
+
for (const s of arr) {
|
|
3184
|
+
const n = extractName(s);
|
|
3185
|
+
if (!n) continue;
|
|
3186
|
+
if (!m.has(n)) m.set(n, []);
|
|
3187
|
+
m.get(n).push(s);
|
|
3188
|
+
}
|
|
3189
|
+
return m;
|
|
3190
|
+
};
|
|
3197
3191
|
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3192
|
+
const aBy = byName(added);
|
|
3193
|
+
const rBy = byName(removed);
|
|
3194
|
+
const modified = [];
|
|
3201
3195
|
|
|
3202
|
-
|
|
3196
|
+
for (const [name] of aBy) {
|
|
3197
|
+
if (rBy.has(name)) modified.push(name);
|
|
3203
3198
|
}
|
|
3204
3199
|
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3200
|
+
return { added, removed, modified };
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
/**
|
|
3204
|
+
* Extract the declared symbol name from a signature line.
|
|
3205
|
+
*
|
|
3206
|
+
* Robust to the real forms SigMap emits — `export class X`, `const x = () =>`,
|
|
3207
|
+
* `async function x`, members, a trailing `:start-end` anchor, and `→ return`
|
|
3208
|
+
* suffixes. Anchored so it never returns a mid-string fragment (the old regex
|
|
3209
|
+
* could turn a signature into a 2-char name like `is`), and returns '' for
|
|
3210
|
+
* non-symbol lines (`module.exports = {…}`, markdown headers) instead of guessing.
|
|
3211
|
+
*/
|
|
3212
|
+
function extractName(sig) {
|
|
3213
|
+
if (!sig) return '';
|
|
3214
|
+
// Drop a trailing `:start-end` (or `:line`) line anchor.
|
|
3215
|
+
let t = String(sig).replace(/\s*:\d+(?:-\d+)?\s*$/, '').trim();
|
|
3216
|
+
// Re-export / barrel lines carry no single declared name.
|
|
3217
|
+
if (/^(?:module\.)?exports\b/.test(t) || /^export\s*\{/.test(t) || /^export\s+\*/.test(t)) return '';
|
|
3218
|
+
// Strip leading modifiers so the keyword/name is at the start.
|
|
3219
|
+
t = t.replace(/^export\s+/, '')
|
|
3220
|
+
.replace(/^default\s+/, '')
|
|
3221
|
+
.replace(/^(?:public|private|protected|static|abstract|final|override|readonly)\s+/g, '')
|
|
3222
|
+
.replace(/^async\s+/, '');
|
|
3223
|
+
let m;
|
|
3224
|
+
// Declared forms: function/def/func/fn/class/interface/trait/struct/enum/record/type <name>
|
|
3225
|
+
if ((m = t.match(/^(?:def|function|func|fn|class|interface|trait|struct|enum|record|type)\s+([A-Za-z_$][\w$]*)/))) return m[1];
|
|
3226
|
+
// const/let/var/val <name> = … (arrow functions, assigned values)
|
|
3227
|
+
if ((m = t.match(/^(?:const|let|var|val)\s+([A-Za-z_$][\w$]*)/))) return m[1];
|
|
3228
|
+
// Call / method form: <name>(…)
|
|
3229
|
+
if ((m = t.match(/^([A-Za-z_$][\w$]*)\s*\(/))) return m[1];
|
|
3230
|
+
// Lone identifier (e.g. a collapsed `symbol` after the anchor was stripped).
|
|
3231
|
+
if ((m = t.match(/^([A-Za-z_$][\w$]*)$/))) return m[1];
|
|
3232
|
+
return '';
|
|
3233
|
+
}
|
|
3211
3234
|
|
|
3212
|
-
|
|
3235
|
+
module.exports = { diffSignatures, extractName };
|
|
3213
3236
|
|
|
3214
3237
|
};
|
|
3215
|
-
|
|
3216
3238
|
// ── ./src/extractors/r ──
|
|
3217
3239
|
__factories["./src/extractors/r"] = function(module, exports) {
|
|
3218
3240
|
|
|
@@ -5510,7 +5532,6 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
|
|
|
5510
5532
|
|
|
5511
5533
|
const fs = require('fs');
|
|
5512
5534
|
const path = require('path');
|
|
5513
|
-
const { execSync } = require('child_process');
|
|
5514
5535
|
|
|
5515
5536
|
const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
|
|
5516
5537
|
const CONTEXT_COLD_FILE = path.join('.github', 'context-cold.md');
|
|
@@ -5657,19 +5678,14 @@ function createCheckpoint(args, cwd) {
|
|
|
5657
5678
|
// ── Git info ────────────────────────────────────────────────────────────
|
|
5658
5679
|
lines.push('## Git state');
|
|
5659
5680
|
try {
|
|
5660
|
-
const branch =
|
|
5661
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
5662
|
-
}).trim();
|
|
5681
|
+
const branch = __git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }).trim();
|
|
5663
5682
|
lines.push(`**Branch:** ${branch}`);
|
|
5664
5683
|
} catch (_) {
|
|
5665
5684
|
lines.push('**Branch:** (not a git repo)');
|
|
5666
5685
|
}
|
|
5667
5686
|
|
|
5668
5687
|
try {
|
|
5669
|
-
const log =
|
|
5670
|
-
'git log --oneline -5 --no-decorate 2>/dev/null',
|
|
5671
|
-
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
5672
|
-
).trim();
|
|
5688
|
+
const log = __git(['log', '--oneline', '-5', '--no-decorate'], { cwd }).trim();
|
|
5673
5689
|
if (log) {
|
|
5674
5690
|
lines.push('');
|
|
5675
5691
|
lines.push('**Recent commits:**');
|
|
@@ -6238,7 +6254,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
|
|
|
6238
6254
|
|
|
6239
6255
|
const SERVER_INFO = {
|
|
6240
6256
|
name: 'sigmap',
|
|
6241
|
-
version: '
|
|
6257
|
+
version: '7.0.1',
|
|
6242
6258
|
description: 'SigMap MCP server — code signatures on demand',
|
|
6243
6259
|
};
|
|
6244
6260
|
|
|
@@ -7129,8 +7145,6 @@ __factories["./src/session/notes"] = function(module, exports) {
|
|
|
7129
7145
|
|
|
7130
7146
|
const fs = require('fs');
|
|
7131
7147
|
const path = require('path');
|
|
7132
|
-
const { execSync } = require('child_process');
|
|
7133
|
-
|
|
7134
7148
|
const NOTES_FILE = path.join('.context', 'notes.ndjson');
|
|
7135
7149
|
const MAX_TEXT = 2000;
|
|
7136
7150
|
|
|
@@ -7139,13 +7153,7 @@ function notesPath(cwd) {
|
|
|
7139
7153
|
}
|
|
7140
7154
|
|
|
7141
7155
|
function _currentBranch(cwd) {
|
|
7142
|
-
|
|
7143
|
-
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
7144
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
7145
|
-
}).trim() || null;
|
|
7146
|
-
} catch (_) {
|
|
7147
|
-
return null;
|
|
7148
|
-
}
|
|
7156
|
+
return __tryGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }) || null;
|
|
7149
7157
|
}
|
|
7150
7158
|
|
|
7151
7159
|
/**
|
|
@@ -7988,334 +7996,641 @@ __factories["./src/eval/usefulness-scorer"] = function(module, exports) {
|
|
|
7988
7996
|
|
|
7989
7997
|
// ── ./packages/adapters/copilot (bundled) ──
|
|
7990
7998
|
__factories["./packages/adapters/copilot"] = function(module, exports) {
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
7999
|
+
'use strict';
|
|
8000
|
+
|
|
8001
|
+
/**
|
|
8002
|
+
* Copilot adapter — writes to .github/copilot-instructions.md
|
|
8003
|
+
* GitHub Copilot reads this file automatically in every workspace.
|
|
8004
|
+
*
|
|
8005
|
+
* Contract:
|
|
8006
|
+
* format(context, opts?) → string
|
|
8007
|
+
* outputPath(cwd) → string
|
|
8008
|
+
*/
|
|
8009
|
+
|
|
8010
|
+
const path = require('path');
|
|
8011
|
+
const fs = require('fs');
|
|
8012
|
+
|
|
8013
|
+
const name = 'copilot';
|
|
8014
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8015
|
+
|
|
8016
|
+
/**
|
|
8017
|
+
* Format context for GitHub Copilot instructions.
|
|
8018
|
+
* @param {string} context - Raw signature context string
|
|
8019
|
+
* @param {object} [opts]
|
|
8020
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8021
|
+
* @returns {string}
|
|
8022
|
+
*/
|
|
8023
|
+
function format(context, opts = {}) {
|
|
8024
|
+
if (!context || typeof context !== 'string') return '';
|
|
8025
|
+
const version = opts.version || 'unknown';
|
|
8026
|
+
const timestamp = new Date().toISOString();
|
|
8027
|
+
const meta = _confidenceMeta(opts);
|
|
8028
|
+
const header = [
|
|
8029
|
+
`<!-- Generated by SigMap gen-context.js v${version} -->`,
|
|
8030
|
+
`<!-- Updated: ${timestamp} -->`,
|
|
8031
|
+
meta,
|
|
8032
|
+
`<!-- Do not edit below — regenerate with: node gen-context.js -->`,
|
|
8033
|
+
'',
|
|
8034
|
+
'# Code signatures',
|
|
8035
|
+
'',
|
|
8036
|
+
].join('\n');
|
|
8037
|
+
return header + context;
|
|
8038
|
+
}
|
|
8039
|
+
|
|
8040
|
+
function _confidenceMeta(opts) {
|
|
8041
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8042
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8043
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8044
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8045
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8046
|
+
return `<!-- sigmap: ${parts.join(' ')} -->`;
|
|
8047
|
+
}
|
|
8048
|
+
|
|
8049
|
+
/**
|
|
8050
|
+
* Return the output file path for this adapter.
|
|
8051
|
+
* @param {string} cwd - Project root
|
|
8052
|
+
* @returns {string}
|
|
8053
|
+
*/
|
|
8054
|
+
function outputPath(cwd) {
|
|
8055
|
+
return path.join(cwd, '.github', 'copilot-instructions.md');
|
|
8056
|
+
}
|
|
8057
|
+
|
|
8058
|
+
/**
|
|
8059
|
+
* Write signatures into copilot-instructions.md using append-under-marker.
|
|
8060
|
+
* If marker exists, content above marker is preserved.
|
|
8061
|
+
* If legacy generated content exists without marker, replace it cleanly.
|
|
8062
|
+
* @param {string} context - Raw signature context string
|
|
8063
|
+
* @param {string} cwd - Project root
|
|
8064
|
+
* @param {object} [opts]
|
|
8065
|
+
*/
|
|
8066
|
+
function write(context, cwd, opts = {}) {
|
|
8067
|
+
const filePath = outputPath(cwd);
|
|
8068
|
+
let existing = '';
|
|
8069
|
+
if (fs.existsSync(filePath)) {
|
|
8070
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8016
8071
|
}
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
const
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8072
|
+
|
|
8073
|
+
const formatted = format(context, opts);
|
|
8074
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8075
|
+
|
|
8076
|
+
let newContent;
|
|
8077
|
+
if (markerIdx !== -1) {
|
|
8078
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8079
|
+
} else {
|
|
8080
|
+
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js')
|
|
8081
|
+
|| existing.includes('# Code signatures');
|
|
8082
|
+
newContent = isLegacyGenerated
|
|
8083
|
+
? MARKER.trimStart() + formatted
|
|
8084
|
+
: existing + MARKER + formatted;
|
|
8030
8085
|
}
|
|
8031
|
-
module.exports = { name, format, outputPath, write };
|
|
8032
|
-
};
|
|
8033
8086
|
|
|
8087
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8088
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8089
|
+
}
|
|
8090
|
+
|
|
8091
|
+
module.exports = { name, format, outputPath, write };
|
|
8092
|
+
|
|
8093
|
+
};
|
|
8034
8094
|
// ── ./packages/adapters/claude (bundled) ──
|
|
8035
8095
|
__factories["./packages/adapters/claude"] = function(module, exports) {
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8096
|
+
'use strict';
|
|
8097
|
+
|
|
8098
|
+
/**
|
|
8099
|
+
* Claude adapter — appends to CLAUDE.md under a marker line.
|
|
8100
|
+
* Never overwrites human-written content above the marker.
|
|
8101
|
+
*
|
|
8102
|
+
* Contract:
|
|
8103
|
+
* format(context, opts?) → string
|
|
8104
|
+
* outputPath(cwd) → string
|
|
8105
|
+
* write(context, cwd, opts?) → void (handles append logic)
|
|
8106
|
+
*/
|
|
8107
|
+
|
|
8108
|
+
const path = require('path');
|
|
8109
|
+
const fs = require('fs');
|
|
8110
|
+
|
|
8111
|
+
const name = 'claude';
|
|
8112
|
+
|
|
8113
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8114
|
+
const ALLOWLIST_MARKER = '<!-- sigmap-bash-allowlist -->';
|
|
8115
|
+
|
|
8116
|
+
const ALLOWLIST_BLOCK = [
|
|
8117
|
+
'## Bash allowlist',
|
|
8118
|
+
'',
|
|
8119
|
+
ALLOWLIST_MARKER,
|
|
8120
|
+
'',
|
|
8121
|
+
'The following sigmap commands are pre-approved — Claude Code will not prompt for confirmation:',
|
|
8122
|
+
'',
|
|
8123
|
+
'```json',
|
|
8124
|
+
JSON.stringify({
|
|
8125
|
+
permissions: {
|
|
8126
|
+
allow: [
|
|
8127
|
+
'Bash(sigmap ask*)',
|
|
8128
|
+
'Bash(sigmap validate*)',
|
|
8129
|
+
'Bash(sigmap judge*)',
|
|
8130
|
+
'Bash(sigmap weights*)',
|
|
8131
|
+
'Bash(sigmap history*)',
|
|
8132
|
+
'Bash(sigmap --query*)',
|
|
8133
|
+
'Bash(sigmap --diff*)',
|
|
8134
|
+
'Bash(sigmap --health*)',
|
|
8135
|
+
'Bash(sigmap --coverage*)',
|
|
8136
|
+
'Bash(node gen-context.js*)',
|
|
8137
|
+
],
|
|
8138
|
+
},
|
|
8139
|
+
}, null, 2),
|
|
8140
|
+
'```',
|
|
8141
|
+
'',
|
|
8142
|
+
'Add the `permissions.allow` array above to `.claude/settings.json` to activate.',
|
|
8143
|
+
'',
|
|
8144
|
+
].join('\n');
|
|
8145
|
+
|
|
8146
|
+
/**
|
|
8147
|
+
* Format context suited for CLAUDE.md.
|
|
8148
|
+
* @param {string} context - Raw signature context string
|
|
8149
|
+
* @param {object} [opts]
|
|
8150
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8151
|
+
* @returns {string}
|
|
8152
|
+
*/
|
|
8153
|
+
function format(context, opts = {}) {
|
|
8154
|
+
if (!context || typeof context !== 'string') return '';
|
|
8155
|
+
const version = opts.version || 'unknown';
|
|
8156
|
+
const timestamp = new Date().toISOString();
|
|
8157
|
+
const meta = _confidenceMeta(opts);
|
|
8158
|
+
return [
|
|
8159
|
+
`<!-- Generated by SigMap v${version} — ${timestamp} -->`,
|
|
8160
|
+
meta,
|
|
8068
8161
|
'',
|
|
8162
|
+
context,
|
|
8069
8163
|
].join('\n');
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
function _confidenceMeta(opts) {
|
|
8167
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8168
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8169
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8170
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8171
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8172
|
+
return `<!-- sigmap: ${parts.join(' ')} -->`;
|
|
8173
|
+
}
|
|
8174
|
+
|
|
8175
|
+
/**
|
|
8176
|
+
* Return the output file path for this adapter.
|
|
8177
|
+
* @param {string} cwd - Project root
|
|
8178
|
+
* @returns {string}
|
|
8179
|
+
*/
|
|
8180
|
+
function outputPath(cwd) {
|
|
8181
|
+
return path.join(cwd, 'CLAUDE.md');
|
|
8182
|
+
}
|
|
8183
|
+
|
|
8184
|
+
/**
|
|
8185
|
+
* Write signatures into CLAUDE.md using the append-under-marker strategy.
|
|
8186
|
+
* Human content above the marker is never touched.
|
|
8187
|
+
* @param {string} context - Raw signature context string
|
|
8188
|
+
* @param {string} cwd - Project root
|
|
8189
|
+
* @param {object} [opts]
|
|
8190
|
+
*/
|
|
8191
|
+
function write(context, cwd, opts = {}) {
|
|
8192
|
+
const filePath = outputPath(cwd);
|
|
8193
|
+
let existing = '';
|
|
8194
|
+
if (fs.existsSync(filePath)) {
|
|
8195
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8085
8196
|
}
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
}
|
|
8197
|
+
const formatted = format(context, opts);
|
|
8198
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8199
|
+
let newContent;
|
|
8200
|
+
if (markerIdx !== -1) {
|
|
8201
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8202
|
+
} else {
|
|
8203
|
+
newContent = existing + MARKER + formatted;
|
|
8204
|
+
}
|
|
8205
|
+
|
|
8206
|
+
// Inject ## Bash allowlist above the sig marker if not already present
|
|
8207
|
+
if (!newContent.includes(ALLOWLIST_MARKER)) {
|
|
8208
|
+
const sigMarkerPos = newContent.indexOf('## Auto-generated signatures');
|
|
8209
|
+
if (sigMarkerPos !== -1) {
|
|
8210
|
+
newContent = newContent.slice(0, sigMarkerPos) + ALLOWLIST_BLOCK + '\n' + newContent.slice(sigMarkerPos);
|
|
8211
|
+
} else {
|
|
8212
|
+
newContent = ALLOWLIST_BLOCK + '\n' + newContent;
|
|
8103
8213
|
}
|
|
8104
|
-
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8105
8214
|
}
|
|
8106
|
-
module.exports = { name, format, outputPath, write };
|
|
8107
|
-
};
|
|
8108
8215
|
|
|
8216
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8217
|
+
}
|
|
8218
|
+
|
|
8219
|
+
module.exports = { name, format, outputPath, write };
|
|
8220
|
+
|
|
8221
|
+
};
|
|
8109
8222
|
// ── ./packages/adapters/cursor (bundled) ──
|
|
8110
8223
|
__factories["./packages/adapters/cursor"] = function(module, exports) {
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
}
|
|
8224
|
+
'use strict';
|
|
8225
|
+
|
|
8226
|
+
/**
|
|
8227
|
+
* Cursor adapter — writes to .cursorrules
|
|
8228
|
+
* Cursor reads .cursorrules automatically in every workspace.
|
|
8229
|
+
*
|
|
8230
|
+
* Contract:
|
|
8231
|
+
* format(context, opts?) → string
|
|
8232
|
+
* outputPath(cwd) → string
|
|
8233
|
+
*/
|
|
8234
|
+
|
|
8235
|
+
const path = require('path');
|
|
8236
|
+
|
|
8237
|
+
const name = 'cursor';
|
|
8238
|
+
|
|
8239
|
+
/**
|
|
8240
|
+
* Format context for Cursor rules file.
|
|
8241
|
+
* @param {string} context - Raw signature context string
|
|
8242
|
+
* @param {object} [opts]
|
|
8243
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8244
|
+
* @returns {string}
|
|
8245
|
+
*/
|
|
8246
|
+
function format(context, opts = {}) {
|
|
8247
|
+
if (!context || typeof context !== 'string') return '';
|
|
8248
|
+
const version = opts.version || 'unknown';
|
|
8249
|
+
const timestamp = new Date().toISOString();
|
|
8250
|
+
const meta = _confidenceMeta(opts);
|
|
8251
|
+
const header = [
|
|
8252
|
+
`# Code signatures — generated by SigMap v${version}`,
|
|
8253
|
+
`# Updated: ${timestamp}`,
|
|
8254
|
+
`# ${meta}`,
|
|
8255
|
+
`# Regenerate: node gen-context.js`,
|
|
8256
|
+
'',
|
|
8257
|
+
].join('\n');
|
|
8258
|
+
return header + context;
|
|
8259
|
+
}
|
|
8260
|
+
|
|
8261
|
+
function _confidenceMeta(opts) {
|
|
8262
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8263
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8264
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8265
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8266
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8267
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8268
|
+
}
|
|
8269
|
+
|
|
8270
|
+
/**
|
|
8271
|
+
* Return the output file path for this adapter.
|
|
8272
|
+
* @param {string} cwd - Project root
|
|
8273
|
+
* @returns {string}
|
|
8274
|
+
*/
|
|
8275
|
+
function outputPath(cwd) {
|
|
8276
|
+
return path.join(cwd, '.cursorrules');
|
|
8277
|
+
}
|
|
8132
8278
|
|
|
8279
|
+
module.exports = { name, format, outputPath };
|
|
8280
|
+
|
|
8281
|
+
};
|
|
8133
8282
|
// ── ./packages/adapters/windsurf (bundled) ──
|
|
8134
8283
|
__factories["./packages/adapters/windsurf"] = function(module, exports) {
|
|
8135
|
-
|
|
8136
|
-
const name = 'windsurf';
|
|
8137
|
-
function format(context, opts = {}) {
|
|
8138
|
-
if (!context || typeof context !== 'string') return '';
|
|
8139
|
-
const version = (opts && opts.version) || 'unknown';
|
|
8140
|
-
const timestamp = new Date().toISOString();
|
|
8141
|
-
return [
|
|
8142
|
-
`# Code signatures — generated by SigMap v${version}`,
|
|
8143
|
-
`# Updated: ${timestamp}`,
|
|
8144
|
-
`# Regenerate: node gen-context.js`,
|
|
8145
|
-
'#',
|
|
8146
|
-
'# SigMap: before answering, run: sigmap ask "<query>" — finds relevant files',
|
|
8147
|
-
'# SigMap: after config changes, run: sigmap validate',
|
|
8148
|
-
'# SigMap: to verify answers, run: sigmap judge --response <file>',
|
|
8149
|
-
'',
|
|
8150
|
-
context,
|
|
8151
|
-
].join('\n');
|
|
8152
|
-
}
|
|
8153
|
-
function outputPath(cwd) { return path.join(cwd, '.windsurfrules'); }
|
|
8154
|
-
module.exports = { name, format, outputPath };
|
|
8155
|
-
};
|
|
8284
|
+
'use strict';
|
|
8156
8285
|
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
|
|
8166
|
-
return [
|
|
8167
|
-
`You are a coding assistant with full knowledge of this codebase.`,
|
|
8168
|
-
`Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
|
|
8169
|
-
projectLine,
|
|
8170
|
-
`Use these signatures to answer questions about the code accurately.`,
|
|
8171
|
-
`When the user asks about a specific file or function, refer to the signatures below.`,
|
|
8172
|
-
`Before answering questions about specific code areas, prefer running \`sigmap ask "<query>"\` to get the most relevant files. After config changes, \`sigmap validate\` confirms coverage.`,
|
|
8173
|
-
``,
|
|
8174
|
-
`## Code Signatures`,
|
|
8175
|
-
``,
|
|
8176
|
-
context,
|
|
8177
|
-
].join('\n');
|
|
8178
|
-
}
|
|
8179
|
-
function outputPath(cwd) { return path.join(cwd, '.github', 'openai-context.md'); }
|
|
8180
|
-
module.exports = { name, format, outputPath };
|
|
8181
|
-
};
|
|
8286
|
+
/**
|
|
8287
|
+
* Windsurf adapter — writes to .windsurfrules
|
|
8288
|
+
* Windsurf reads .windsurfrules automatically in every workspace.
|
|
8289
|
+
*
|
|
8290
|
+
* Contract:
|
|
8291
|
+
* format(context, opts?) → string
|
|
8292
|
+
* outputPath(cwd) → string
|
|
8293
|
+
*/
|
|
8182
8294
|
|
|
8183
|
-
|
|
8184
|
-
__factories["./packages/adapters/gemini"] = function(module, exports) {
|
|
8185
|
-
const path = require('path');
|
|
8186
|
-
const fs = require('fs');
|
|
8187
|
-
const name = 'gemini';
|
|
8188
|
-
const GEMINI_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8189
|
-
function format(context, opts = {}) {
|
|
8190
|
-
if (!context || typeof context !== 'string') return '';
|
|
8191
|
-
const version = (opts && opts.version) || 'unknown';
|
|
8192
|
-
const timestamp = new Date().toISOString();
|
|
8193
|
-
const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
|
|
8194
|
-
return [
|
|
8195
|
-
`You are a coding assistant with complete knowledge of this codebase.`,
|
|
8196
|
-
`The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
|
|
8197
|
-
projectLine,
|
|
8198
|
-
`These signatures represent every public function, class, and type in the project.`,
|
|
8199
|
-
`Refer to them when answering questions about code structure, APIs, and implementation.`,
|
|
8200
|
-
`Before answering questions about specific code areas, suggest running \`sigmap ask "<query>"\` to get the most relevant files. After config changes, \`sigmap validate\` confirms coverage.`,
|
|
8201
|
-
``,
|
|
8202
|
-
`## Code Signatures`,
|
|
8203
|
-
``,
|
|
8204
|
-
context,
|
|
8205
|
-
].join('\n');
|
|
8206
|
-
}
|
|
8207
|
-
function outputPath(cwd) { return path.join(cwd, '.github', 'gemini-context.md'); }
|
|
8208
|
-
function write(context, cwd, opts = {}) {
|
|
8209
|
-
const filePath = outputPath(cwd);
|
|
8210
|
-
let existing = '';
|
|
8211
|
-
if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
|
|
8212
|
-
const formatted = format(context, opts);
|
|
8213
|
-
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8214
|
-
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js') || existing.includes('## Code Signatures');
|
|
8215
|
-
const newContent = markerIdx !== -1
|
|
8216
|
-
? existing.slice(0, markerIdx) + GEMINI_MARKER.trimStart() + formatted
|
|
8217
|
-
: (isLegacyGenerated ? GEMINI_MARKER.trimStart() + formatted : existing + GEMINI_MARKER + formatted);
|
|
8218
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8219
|
-
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8220
|
-
}
|
|
8221
|
-
module.exports = { name, format, outputPath, write };
|
|
8222
|
-
};
|
|
8295
|
+
const path = require('path');
|
|
8223
8296
|
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
], null, 2),
|
|
8244
|
-
'```',
|
|
8297
|
+
const name = 'windsurf';
|
|
8298
|
+
|
|
8299
|
+
/**
|
|
8300
|
+
* Format context for Windsurf rules file.
|
|
8301
|
+
* @param {string} context - Raw signature context string
|
|
8302
|
+
* @param {object} [opts]
|
|
8303
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8304
|
+
* @returns {string}
|
|
8305
|
+
*/
|
|
8306
|
+
function format(context, opts = {}) {
|
|
8307
|
+
if (!context || typeof context !== 'string') return '';
|
|
8308
|
+
const version = opts.version || 'unknown';
|
|
8309
|
+
const timestamp = new Date().toISOString();
|
|
8310
|
+
const meta = _confidenceMeta(opts);
|
|
8311
|
+
const header = [
|
|
8312
|
+
`# Code signatures — generated by SigMap v${version}`,
|
|
8313
|
+
`# Updated: ${timestamp}`,
|
|
8314
|
+
`# ${meta}`,
|
|
8315
|
+
`# Regenerate: node gen-context.js`,
|
|
8245
8316
|
'',
|
|
8246
8317
|
].join('\n');
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8270
|
-
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js') || existing.includes('## Code Signatures') || existing.includes('# Code signatures');
|
|
8271
|
-
let newContent = markerIdx !== -1
|
|
8272
|
-
? existing.slice(0, markerIdx) + CODEX_MARKER.trimStart() + formatted
|
|
8273
|
-
: (isLegacyGenerated ? CODEX_MARKER.trimStart() + formatted : existing + CODEX_MARKER + formatted);
|
|
8274
|
-
if (!newContent.includes(CODEX_TOOLS_MARKER)) {
|
|
8275
|
-
const sigPos = newContent.indexOf('## Auto-generated signatures');
|
|
8276
|
-
if (sigPos !== -1) {
|
|
8277
|
-
newContent = newContent.slice(0, sigPos) + CODEX_TOOLS_BLOCK + '\n' + newContent.slice(sigPos);
|
|
8278
|
-
} else {
|
|
8279
|
-
newContent = CODEX_TOOLS_BLOCK + '\n' + newContent;
|
|
8280
|
-
}
|
|
8281
|
-
}
|
|
8282
|
-
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8283
|
-
}
|
|
8284
|
-
module.exports = { name, format, outputPath, write };
|
|
8285
|
-
};
|
|
8318
|
+
return header + context;
|
|
8319
|
+
}
|
|
8320
|
+
|
|
8321
|
+
function _confidenceMeta(opts) {
|
|
8322
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8323
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8324
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8325
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8326
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8327
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8328
|
+
}
|
|
8329
|
+
|
|
8330
|
+
/**
|
|
8331
|
+
* Return the output file path for this adapter.
|
|
8332
|
+
* @param {string} cwd - Project root
|
|
8333
|
+
* @returns {string}
|
|
8334
|
+
*/
|
|
8335
|
+
function outputPath(cwd) {
|
|
8336
|
+
return path.join(cwd, '.windsurfrules');
|
|
8337
|
+
}
|
|
8338
|
+
|
|
8339
|
+
module.exports = { name, format, outputPath };
|
|
8286
8340
|
|
|
8287
|
-
// ── ./packages/adapters/index (bundled) ──
|
|
8288
|
-
__factories["./packages/adapters/index"] = function(module, exports) {
|
|
8289
|
-
const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
|
|
8290
|
-
const _adapterCache = {};
|
|
8291
|
-
function getAdapter(name) {
|
|
8292
|
-
if (!name || typeof name !== 'string') return null;
|
|
8293
|
-
const key = name.toLowerCase();
|
|
8294
|
-
if (!ADAPTER_NAMES.includes(key)) return null;
|
|
8295
|
-
if (!_adapterCache[key]) {
|
|
8296
|
-
try { _adapterCache[key] = __require('./packages/adapters/' + key); } catch (_) { return null; }
|
|
8297
|
-
}
|
|
8298
|
-
return _adapterCache[key];
|
|
8299
|
-
}
|
|
8300
|
-
function listAdapters() { return ADAPTER_NAMES.slice(); }
|
|
8301
|
-
function adapt(context, adapterName, opts = {}) {
|
|
8302
|
-
if (!context || typeof context !== 'string') return '';
|
|
8303
|
-
const adapter = getAdapter(adapterName);
|
|
8304
|
-
if (!adapter || typeof adapter.format !== 'function') return '';
|
|
8305
|
-
try { return adapter.format(context, opts); } catch (_) { return ''; }
|
|
8306
|
-
}
|
|
8307
|
-
function outputsToAdapters(outputs) {
|
|
8308
|
-
if (!Array.isArray(outputs)) return ['copilot'];
|
|
8309
|
-
return outputs.map((o) => ADAPTER_NAMES.includes(o) ? o : o);
|
|
8310
|
-
}
|
|
8311
|
-
module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
|
|
8312
8341
|
};
|
|
8342
|
+
// ── ./packages/adapters/openai (bundled) ──
|
|
8343
|
+
__factories["./packages/adapters/openai"] = function(module, exports) {
|
|
8344
|
+
'use strict';
|
|
8313
8345
|
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8346
|
+
/**
|
|
8347
|
+
* OpenAI adapter — formats context as an OpenAI system message.
|
|
8348
|
+
* Use the output as the `content` field of a system role message.
|
|
8349
|
+
*
|
|
8350
|
+
* Example usage in code:
|
|
8351
|
+
* const { format } = require('sigmap/adapters/openai');
|
|
8352
|
+
* const systemPrompt = format(context);
|
|
8353
|
+
* // Pass to: openai.chat.completions.create({ messages: [{ role: 'system', content: systemPrompt }] })
|
|
8354
|
+
*
|
|
8355
|
+
* Contract:
|
|
8356
|
+
* format(context, opts?) → string
|
|
8357
|
+
* outputPath(cwd) → string
|
|
8358
|
+
*/
|
|
8359
|
+
|
|
8360
|
+
const path = require('path');
|
|
8361
|
+
|
|
8362
|
+
const name = 'openai';
|
|
8363
|
+
|
|
8364
|
+
/**
|
|
8365
|
+
* Format context as an OpenAI system prompt.
|
|
8366
|
+
* @param {string} context - Raw signature context string
|
|
8367
|
+
* @param {object} [opts]
|
|
8368
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8369
|
+
* @param {string} [opts.projectName] - Optional project name
|
|
8370
|
+
* @returns {string}
|
|
8371
|
+
*/
|
|
8372
|
+
function format(context, opts = {}) {
|
|
8373
|
+
if (!context || typeof context !== 'string') return '';
|
|
8374
|
+
const version = opts.version || 'unknown';
|
|
8375
|
+
const timestamp = new Date().toISOString();
|
|
8376
|
+
const projectLine = opts.projectName
|
|
8377
|
+
? `Project: ${opts.projectName}\n`
|
|
8378
|
+
: '';
|
|
8379
|
+
|
|
8380
|
+
const meta = _confidenceMeta(opts);
|
|
8381
|
+
return [
|
|
8382
|
+
`You are a coding assistant with full knowledge of this codebase.`,
|
|
8383
|
+
`Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
|
|
8384
|
+
`<!-- ${meta} -->`,
|
|
8385
|
+
projectLine,
|
|
8386
|
+
`Use these signatures to answer questions about the code accurately.`,
|
|
8387
|
+
`When the user asks about a specific file or function, refer to the signatures below.`,
|
|
8388
|
+
`## Code Signatures`,
|
|
8389
|
+
``,
|
|
8390
|
+
context,
|
|
8391
|
+
].join('\n');
|
|
8392
|
+
}
|
|
8393
|
+
|
|
8394
|
+
/**
|
|
8395
|
+
* Return the output file path for this adapter.
|
|
8396
|
+
* Writes a .openai-context.md file that can be loaded at runtime.
|
|
8397
|
+
* @param {string} cwd - Project root
|
|
8398
|
+
* @returns {string}
|
|
8399
|
+
*/
|
|
8400
|
+
function outputPath(cwd) {
|
|
8401
|
+
return path.join(cwd, '.github', 'openai-context.md');
|
|
8402
|
+
}
|
|
8403
|
+
|
|
8404
|
+
function _confidenceMeta(opts) {
|
|
8405
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8406
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8407
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8408
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8409
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8410
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8411
|
+
}
|
|
8412
|
+
|
|
8413
|
+
module.exports = { name, format, outputPath };
|
|
8414
|
+
|
|
8415
|
+
};
|
|
8416
|
+
// ── ./packages/adapters/gemini (bundled) ──
|
|
8417
|
+
__factories["./packages/adapters/gemini"] = function(module, exports) {
|
|
8418
|
+
'use strict';
|
|
8419
|
+
|
|
8420
|
+
/**
|
|
8421
|
+
* Gemini adapter — formats context as a Gemini system instruction.
|
|
8422
|
+
* Use the output as the `system_instruction` field in a Gemini API request.
|
|
8423
|
+
*
|
|
8424
|
+
* Example usage:
|
|
8425
|
+
* const { format } = require('sigmap/adapters/gemini');
|
|
8426
|
+
* const instruction = format(context);
|
|
8427
|
+
* // Pass to: genAI.getGenerativeModel({ model: 'gemini-pro', systemInstruction: instruction })
|
|
8428
|
+
*
|
|
8429
|
+
* Contract:
|
|
8430
|
+
* format(context, opts?) → string
|
|
8431
|
+
* outputPath(cwd) → string
|
|
8432
|
+
*/
|
|
8433
|
+
|
|
8434
|
+
const path = require('path');
|
|
8435
|
+
const fs = require('fs');
|
|
8436
|
+
|
|
8437
|
+
const name = 'gemini';
|
|
8438
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8439
|
+
|
|
8440
|
+
/**
|
|
8441
|
+
* Format context as a Gemini system instruction.
|
|
8442
|
+
* @param {string} context - Raw signature context string
|
|
8443
|
+
* @param {object} [opts]
|
|
8444
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8445
|
+
* @param {string} [opts.projectName] - Optional project name
|
|
8446
|
+
* @returns {string}
|
|
8447
|
+
*/
|
|
8448
|
+
function format(context, opts = {}) {
|
|
8449
|
+
if (!context || typeof context !== 'string') return '';
|
|
8450
|
+
const version = opts.version || 'unknown';
|
|
8451
|
+
const timestamp = new Date().toISOString();
|
|
8452
|
+
const projectLine = opts.projectName
|
|
8453
|
+
? `Project: ${opts.projectName}\n`
|
|
8454
|
+
: '';
|
|
8455
|
+
|
|
8456
|
+
const meta = _confidenceMeta(opts);
|
|
8457
|
+
return [
|
|
8458
|
+
`You are a coding assistant with complete knowledge of this codebase.`,
|
|
8459
|
+
`The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
|
|
8460
|
+
`<!-- ${meta} -->`,
|
|
8461
|
+
projectLine,
|
|
8462
|
+
`These signatures represent every public function, class, and type in the project.`,
|
|
8463
|
+
`Refer to them when answering questions about code structure, APIs, and implementation.`,
|
|
8464
|
+
`## Code Signatures`,
|
|
8465
|
+
``,
|
|
8466
|
+
context,
|
|
8467
|
+
].join('\n');
|
|
8468
|
+
}
|
|
8469
|
+
|
|
8470
|
+
/**
|
|
8471
|
+
* Return the output file path for this adapter.
|
|
8472
|
+
* @param {string} cwd - Project root
|
|
8473
|
+
* @returns {string}
|
|
8474
|
+
*/
|
|
8475
|
+
function outputPath(cwd) {
|
|
8476
|
+
return path.join(cwd, '.github', 'gemini-context.md');
|
|
8477
|
+
}
|
|
8478
|
+
|
|
8479
|
+
/**
|
|
8480
|
+
* Write signatures into gemini-context.md using append-under-marker.
|
|
8481
|
+
* If marker exists, content above marker is preserved.
|
|
8482
|
+
* If legacy generated content exists without marker, replace it cleanly.
|
|
8483
|
+
* @param {string} context - Raw signature context string
|
|
8484
|
+
* @param {string} cwd - Project root
|
|
8485
|
+
* @param {object} [opts]
|
|
8486
|
+
*/
|
|
8487
|
+
function write(context, cwd, opts = {}) {
|
|
8488
|
+
const filePath = outputPath(cwd);
|
|
8489
|
+
let existing = '';
|
|
8490
|
+
if (fs.existsSync(filePath)) {
|
|
8491
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8492
|
+
}
|
|
8493
|
+
|
|
8494
|
+
const formatted = format(context, opts);
|
|
8495
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8496
|
+
|
|
8497
|
+
let newContent;
|
|
8498
|
+
if (markerIdx !== -1) {
|
|
8499
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8500
|
+
} else {
|
|
8501
|
+
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js')
|
|
8502
|
+
|| existing.includes('## Code Signatures');
|
|
8503
|
+
newContent = isLegacyGenerated
|
|
8504
|
+
? MARKER.trimStart() + formatted
|
|
8505
|
+
: existing + MARKER + formatted;
|
|
8506
|
+
}
|
|
8507
|
+
|
|
8508
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8509
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8510
|
+
}
|
|
8511
|
+
|
|
8512
|
+
function _confidenceMeta(opts) {
|
|
8513
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8514
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8515
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8516
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8517
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8518
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8519
|
+
}
|
|
8520
|
+
|
|
8521
|
+
module.exports = { name, format, outputPath, write };
|
|
8522
|
+
|
|
8523
|
+
};
|
|
8524
|
+
// ── ./packages/adapters/codex (bundled) ──
|
|
8525
|
+
__factories["./packages/adapters/codex"] = function(module, exports) {
|
|
8526
|
+
'use strict';
|
|
8527
|
+
|
|
8528
|
+
/**
|
|
8529
|
+
* Codex adapter — writes OpenAI-style context to AGENTS.md.
|
|
8530
|
+
*
|
|
8531
|
+
* This adapter reuses the same prompt format as the OpenAI adapter,
|
|
8532
|
+
* but targets AGENTS.md so Codex-style agents can read repository guidance.
|
|
8533
|
+
*
|
|
8534
|
+
* Contract:
|
|
8535
|
+
* format(context, opts?) → string
|
|
8536
|
+
* outputPath(cwd) → string
|
|
8537
|
+
* write(context, cwd, opts?) → void
|
|
8538
|
+
*/
|
|
8539
|
+
|
|
8540
|
+
const path = require('path');
|
|
8541
|
+
const fs = require('fs');
|
|
8542
|
+
|
|
8543
|
+
const name = 'codex';
|
|
8544
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8545
|
+
|
|
8546
|
+
/**
|
|
8547
|
+
* Format context for AGENTS.md — clean markdown, no LLM preamble.
|
|
8548
|
+
* @param {string} context - Raw signature context string
|
|
8549
|
+
* @param {object} [opts]
|
|
8550
|
+
* @returns {string}
|
|
8551
|
+
*/
|
|
8552
|
+
function format(context, opts = {}) {
|
|
8553
|
+
if (!context || typeof context !== 'string' || !context.trim()) return '';
|
|
8554
|
+
return `# Code signatures\n\n${context}`;
|
|
8555
|
+
}
|
|
8556
|
+
|
|
8557
|
+
/**
|
|
8558
|
+
* Return the output file path for this adapter.
|
|
8559
|
+
* @param {string} cwd - Project root
|
|
8560
|
+
* @returns {string}
|
|
8561
|
+
*/
|
|
8562
|
+
function outputPath(cwd) {
|
|
8563
|
+
return path.join(cwd, 'AGENTS.md');
|
|
8564
|
+
}
|
|
8565
|
+
|
|
8566
|
+
/**
|
|
8567
|
+
* Write signatures into AGENTS.md using append-under-marker.
|
|
8568
|
+
* If marker exists, content above marker is preserved.
|
|
8569
|
+
* If legacy generated content exists without marker, replace it cleanly.
|
|
8570
|
+
* @param {string} context - Raw signature context string
|
|
8571
|
+
* @param {string} cwd - Project root
|
|
8572
|
+
* @param {object} [opts]
|
|
8573
|
+
*/
|
|
8574
|
+
function write(context, cwd, opts = {}) {
|
|
8575
|
+
const filePath = outputPath(cwd);
|
|
8576
|
+
let existing = '';
|
|
8577
|
+
if (fs.existsSync(filePath)) {
|
|
8578
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8579
|
+
}
|
|
8580
|
+
|
|
8581
|
+
const formatted = format(context, opts);
|
|
8582
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8583
|
+
|
|
8584
|
+
let newContent;
|
|
8585
|
+
if (markerIdx !== -1) {
|
|
8586
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8587
|
+
} else {
|
|
8588
|
+
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js')
|
|
8589
|
+
|| existing.includes('## Code Signatures')
|
|
8590
|
+
|| existing.includes('# Code signatures');
|
|
8591
|
+
newContent = isLegacyGenerated
|
|
8592
|
+
? MARKER.trimStart() + formatted
|
|
8593
|
+
: existing + MARKER + formatted;
|
|
8594
|
+
}
|
|
8595
|
+
|
|
8596
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8597
|
+
}
|
|
8598
|
+
|
|
8599
|
+
module.exports = { name, format, outputPath, write };
|
|
8600
|
+
|
|
8601
|
+
};
|
|
8602
|
+
// ── ./packages/adapters/index (bundled) ──
|
|
8603
|
+
__factories["./packages/adapters/index"] = function(module, exports) {
|
|
8604
|
+
const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
|
|
8605
|
+
const _adapterCache = {};
|
|
8606
|
+
function getAdapter(name) {
|
|
8607
|
+
if (!name || typeof name !== 'string') return null;
|
|
8608
|
+
const key = name.toLowerCase();
|
|
8609
|
+
if (!ADAPTER_NAMES.includes(key)) return null;
|
|
8610
|
+
if (!_adapterCache[key]) {
|
|
8611
|
+
try { _adapterCache[key] = __require('./packages/adapters/' + key); } catch (_) { return null; }
|
|
8612
|
+
}
|
|
8613
|
+
return _adapterCache[key];
|
|
8614
|
+
}
|
|
8615
|
+
function listAdapters() { return ADAPTER_NAMES.slice(); }
|
|
8616
|
+
function adapt(context, adapterName, opts = {}) {
|
|
8617
|
+
if (!context || typeof context !== 'string') return '';
|
|
8618
|
+
const adapter = getAdapter(adapterName);
|
|
8619
|
+
if (!adapter || typeof adapter.format !== 'function') return '';
|
|
8620
|
+
try { return adapter.format(context, opts); } catch (_) { return ''; }
|
|
8621
|
+
}
|
|
8622
|
+
function outputsToAdapters(outputs) {
|
|
8623
|
+
if (!Array.isArray(outputs)) return ['copilot'];
|
|
8624
|
+
return outputs.map((o) => ADAPTER_NAMES.includes(o) ? o : o);
|
|
8625
|
+
}
|
|
8626
|
+
module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
|
|
8627
|
+
};
|
|
8628
|
+
|
|
8629
|
+
// ── ./src/extractors/generic ──
|
|
8630
|
+
__factories["./src/extractors/generic"] = function(module, exports) {
|
|
8631
|
+
'use strict';
|
|
8632
|
+
const PATTERNS = [
|
|
8633
|
+
/^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
|
|
8319
8634
|
/^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
|
|
8320
8635
|
/^def\s+\w+[\s(|:]/,
|
|
8321
8636
|
/^(?:pub\s+)?func\s+\w+\s*\(/,
|
|
@@ -8373,10 +8688,9 @@ __factories["./src/format/llms-txt"] = function(module, exports) {
|
|
|
8373
8688
|
'use strict';
|
|
8374
8689
|
const path = require('path');
|
|
8375
8690
|
const fs = require('fs');
|
|
8376
|
-
const { execSync } = require('child_process');
|
|
8377
8691
|
function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
|
|
8378
8692
|
function getShortCommit(cwd) {
|
|
8379
|
-
try { return
|
|
8693
|
+
try { return __tryGit(['rev-parse', '--short', 'HEAD'], { cwd, timeout: 2000 }); }
|
|
8380
8694
|
catch (_) { return ''; }
|
|
8381
8695
|
}
|
|
8382
8696
|
function detectVersion(cwd) {
|
|
@@ -8701,14 +9015,13 @@ __factories["./src/discovery/source-root-scorer"] = function(module, exports) {
|
|
|
8701
9015
|
'use strict';
|
|
8702
9016
|
const fs = require('fs');
|
|
8703
9017
|
const path = require('path');
|
|
8704
|
-
const { execSync } = require('child_process');
|
|
8705
9018
|
const CODE_EXTS = new Set(['.js','.mjs','.cjs','.ts','.tsx','.jsx','.py','.rb','.go','.rs','.java','.kt','.cs','.cpp','.c','.h','.swift','.dart','.scala','.php']);
|
|
8706
9019
|
const AUTO_SKIP = new Set(['node_modules','dist','build','.git','.next','.nuxt','vendor','DerivedData','Pods','target','coverage','__pycache__','.venv','venv','.build','Carthage','storybook-static','.gradle','bin','obj','.vs']);
|
|
8707
9020
|
const PENALTY_DIRS = new Set(['test','tests','spec','__tests__','e2e','docs','doc','docs-vp','examples','example','fixtures','mocks','__mocks__','demo','samples','migrations','benchmarks','scripts']);
|
|
8708
9021
|
const ROOT_ENTRYPOINTS = { go: ['main.go'], python: ['app.py','main.py','wsgi.py','asgi.py'], javascript: ['index.js','server.js','app.js'], typescript: ['index.ts','main.ts'], rust: [], php: ['index.php'] };
|
|
8709
9022
|
function getRecentlyChangedDirs(cwd) {
|
|
8710
9023
|
try {
|
|
8711
|
-
const out =
|
|
9024
|
+
const out = __git(['log', '--name-only', '--format=', 'HEAD~10'], { cwd, timeout: 3000 }).toString();
|
|
8712
9025
|
return new Set(out.split('\n').filter(Boolean).map(f => f.split('/')[0]));
|
|
8713
9026
|
} catch { return new Set(); }
|
|
8714
9027
|
}
|
|
@@ -9180,6 +9493,605 @@ __factories["./src/workspace/detector"] = function(module, exports) {
|
|
|
9180
9493
|
* No npm install required. Node 18+ built-ins only.
|
|
9181
9494
|
*/
|
|
9182
9495
|
|
|
9496
|
+
// ── ./src/format/usage-guidance ──
|
|
9497
|
+
__factories["./src/format/usage-guidance"] = function(module, exports) {
|
|
9498
|
+
'use strict';
|
|
9499
|
+
|
|
9500
|
+
/**
|
|
9501
|
+
* Canonical "how to use SigMap" guidance block (v6.16/v7.0).
|
|
9502
|
+
*
|
|
9503
|
+
* Every adapter emits this one identical block so all generated context files
|
|
9504
|
+
* (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md, GEMINI.md, .cursorrules,
|
|
9505
|
+
* …) carry the same, single usage section — instead of each adapter inventing
|
|
9506
|
+
* its own wording (and codex emitting a redundant second JSON block).
|
|
9507
|
+
*/
|
|
9508
|
+
|
|
9509
|
+
function usageBlock() {
|
|
9510
|
+
return [
|
|
9511
|
+
'## SigMap commands',
|
|
9512
|
+
'',
|
|
9513
|
+
'| When | Command |',
|
|
9514
|
+
'|------|---------|',
|
|
9515
|
+
'| Before answering a question about code | `sigmap ask "<your question>"` |',
|
|
9516
|
+
'| To rank files by topic | `sigmap --query "<topic>"` |',
|
|
9517
|
+
'| After changing config or source dirs | `sigmap validate` |',
|
|
9518
|
+
'| To verify an AI answer is grounded | `sigmap judge --response <file>` |',
|
|
9519
|
+
'',
|
|
9520
|
+
'Always run `sigmap ask` (or `sigmap --query`) before searching for files relevant to a task.',
|
|
9521
|
+
'',
|
|
9522
|
+
].join('\n');
|
|
9523
|
+
}
|
|
9524
|
+
|
|
9525
|
+
module.exports = { usageBlock };
|
|
9526
|
+
|
|
9527
|
+
};
|
|
9528
|
+
|
|
9529
|
+
// ── ./src/squeeze/classify ──
|
|
9530
|
+
__factories["./src/squeeze/classify"] = function(module, exports) {
|
|
9531
|
+
'use strict';
|
|
9532
|
+
|
|
9533
|
+
/**
|
|
9534
|
+
* Squeeze input classifier (v7.0.0).
|
|
9535
|
+
*
|
|
9536
|
+
* Deterministic, zero-dep detector that labels a pasted blob as a
|
|
9537
|
+
* `stacktrace`, `cilog`, or `json` payload — or `null` (pass through). Pure
|
|
9538
|
+
* regex/heuristics; runs in well under 10ms even on large input.
|
|
9539
|
+
*
|
|
9540
|
+
* Order matters: stack traces are highest value and a CI log often *contains*
|
|
9541
|
+
* a trace, so stacktrace is checked first, then cilog, then json.
|
|
9542
|
+
*/
|
|
9543
|
+
|
|
9544
|
+
const FRAME_RE = [
|
|
9545
|
+
/^\s*at\s+.+\(.+:\d+:\d+\)\s*$/, // JS: at fn (file:line:col)
|
|
9546
|
+
/^\s*at\s+.+:\d+:\d+\s*$/, // JS: at file:line:col
|
|
9547
|
+
/^\s*at\s+[\w$.<>]+\(.+\.\w+:\d+\)/, // Java/Kotlin: at pkg.Cls.m(File.java:42)
|
|
9548
|
+
/^\s*File\s+".+",\s+line\s+\d+/, // Python frame
|
|
9549
|
+
/^\s+\w+.*\([^)]*\.(go|rs):\d+\)/, // Go/Rust frame with file:line
|
|
9550
|
+
/^\s*#\d+\s+0x[0-9a-f]+/, // native/gdb frame
|
|
9551
|
+
];
|
|
9552
|
+
|
|
9553
|
+
const STACK_HEADER_RE = [
|
|
9554
|
+
/Traceback \(most recent call last\)/,
|
|
9555
|
+
/Exception in thread/,
|
|
9556
|
+
/\bat Object\.<anonymous>\b/,
|
|
9557
|
+
/thread '.*' panicked at/,
|
|
9558
|
+
/^panic:/m,
|
|
9559
|
+
/goroutine \d+ \[/,
|
|
9560
|
+
];
|
|
9561
|
+
|
|
9562
|
+
function countFrames(lines) {
|
|
9563
|
+
let n = 0;
|
|
9564
|
+
for (const line of lines) {
|
|
9565
|
+
if (FRAME_RE.some((re) => re.test(line))) n++;
|
|
9566
|
+
}
|
|
9567
|
+
return n;
|
|
9568
|
+
}
|
|
9569
|
+
|
|
9570
|
+
function matchesStackTrace(input, lines) {
|
|
9571
|
+
const frames = countFrames(lines);
|
|
9572
|
+
const header = STACK_HEADER_RE.some((re) => re.test(input));
|
|
9573
|
+
// A single explicit header (Traceback/panic) counts as strong evidence even
|
|
9574
|
+
// with few parsed frames; otherwise require 2+ frame-like lines.
|
|
9575
|
+
if (!header && frames < 2) return { match: false, confidence: 0, frames };
|
|
9576
|
+
// Confidence scales with frame count; a header alone floors it at 0.6.
|
|
9577
|
+
let confidence = Math.min(0.97, 0.15 * frames);
|
|
9578
|
+
if (header) confidence = Math.max(confidence, 0.6 + Math.min(0.3, 0.05 * frames));
|
|
9579
|
+
return { match: true, confidence: Number(confidence.toFixed(2)), frames };
|
|
9580
|
+
}
|
|
9581
|
+
|
|
9582
|
+
const TS_RE = /(\b\d{1,2}:\d{2}:\d{2}\b)|(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})/;
|
|
9583
|
+
const PROGRESS_RE = /(\d{1,3}%)|(\bDownloading\b)|(\bnpm (WARN|notice|http)\b)|(##\[(group|endgroup|command)\])|(\[\d+\/\d+\])|(▕|█|━|⠿)|(\bETA\b)|(\r$)/;
|
|
9584
|
+
|
|
9585
|
+
function matchesCiLog(input, lines) {
|
|
9586
|
+
if (lines.length < 8) return { match: false, confidence: 0 };
|
|
9587
|
+
let logish = 0;
|
|
9588
|
+
const seen = new Map();
|
|
9589
|
+
let repeats = 0;
|
|
9590
|
+
for (const line of lines) {
|
|
9591
|
+
if (TS_RE.test(line) || PROGRESS_RE.test(line)) logish++;
|
|
9592
|
+
const norm = line.replace(/\d+/g, '#').trim();
|
|
9593
|
+
if (norm) {
|
|
9594
|
+
const c = (seen.get(norm) || 0) + 1;
|
|
9595
|
+
seen.set(norm, c);
|
|
9596
|
+
if (c > 1) repeats++;
|
|
9597
|
+
}
|
|
9598
|
+
}
|
|
9599
|
+
const density = logish / lines.length;
|
|
9600
|
+
const repeatRatio = repeats / lines.length;
|
|
9601
|
+
if (density < 0.4 && repeatRatio < 0.4) return { match: false, confidence: 0 };
|
|
9602
|
+
const confidence = Math.min(0.95, Math.max(density, repeatRatio) + 0.15);
|
|
9603
|
+
return { match: true, confidence: Number(confidence.toFixed(2)) };
|
|
9604
|
+
}
|
|
9605
|
+
|
|
9606
|
+
function matchesJsonPayload(input) {
|
|
9607
|
+
const trimmed = input.trim();
|
|
9608
|
+
if (/^[[{]/.test(trimmed)) {
|
|
9609
|
+
try {
|
|
9610
|
+
JSON.parse(trimmed);
|
|
9611
|
+
return { match: true, confidence: 0.95 };
|
|
9612
|
+
} catch (_) { /* not strict JSON — fall through to heuristic */ }
|
|
9613
|
+
}
|
|
9614
|
+
const lines = trimmed.split('\n');
|
|
9615
|
+
if (lines.length < 4) return { match: false, confidence: 0 };
|
|
9616
|
+
let kv = 0;
|
|
9617
|
+
for (const line of lines) {
|
|
9618
|
+
if (/^\s*"[^"]+"\s*:\s*.+/.test(line) || /^\s*[}\]],?\s*$/.test(line)) kv++;
|
|
9619
|
+
}
|
|
9620
|
+
const ratio = kv / lines.length;
|
|
9621
|
+
if (ratio < 0.6) return { match: false, confidence: 0 };
|
|
9622
|
+
return { match: true, confidence: Number(Math.min(0.9, ratio).toFixed(2)) };
|
|
9623
|
+
}
|
|
9624
|
+
|
|
9625
|
+
/**
|
|
9626
|
+
* @param {string} input
|
|
9627
|
+
* @returns {{ category: 'stacktrace'|'cilog'|'json'|null, confidence: number }}
|
|
9628
|
+
*/
|
|
9629
|
+
function classify(input) {
|
|
9630
|
+
if (typeof input !== 'string' || !input.trim()) return { category: null, confidence: 0 };
|
|
9631
|
+
const lines = input.split('\n');
|
|
9632
|
+
|
|
9633
|
+
const st = matchesStackTrace(input, lines);
|
|
9634
|
+
if (st.match) return { category: 'stacktrace', confidence: st.confidence };
|
|
9635
|
+
|
|
9636
|
+
const ci = matchesCiLog(input, lines);
|
|
9637
|
+
if (ci.match) return { category: 'cilog', confidence: ci.confidence };
|
|
9638
|
+
|
|
9639
|
+
const js = matchesJsonPayload(input);
|
|
9640
|
+
if (js.match) return { category: 'json', confidence: js.confidence };
|
|
9641
|
+
|
|
9642
|
+
return { category: null, confidence: 0 };
|
|
9643
|
+
}
|
|
9644
|
+
|
|
9645
|
+
module.exports = { classify, countFrames };
|
|
9646
|
+
|
|
9647
|
+
};
|
|
9648
|
+
|
|
9649
|
+
// ── ./src/squeeze/cilog ──
|
|
9650
|
+
__factories["./src/squeeze/cilog"] = function(module, exports) {
|
|
9651
|
+
'use strict';
|
|
9652
|
+
|
|
9653
|
+
/**
|
|
9654
|
+
* CI / build-log squeeze (v7.0.0).
|
|
9655
|
+
*
|
|
9656
|
+
* Strips timestamps, progress bars, and repeated noise; keeps every error line
|
|
9657
|
+
* plus a small context window around it. Never returns empty — when there are
|
|
9658
|
+
* no errors it falls back to a head/tail summary. Also reused by the stacktrace
|
|
9659
|
+
* squeezer to clean noise surrounding a trace.
|
|
9660
|
+
*/
|
|
9661
|
+
|
|
9662
|
+
const TS_PREFIX_RE = /^\s*(?:\[?\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[.,]\d+)?Z?\]?\s*|\[?\d{1,2}:\d{2}:\d{2}(?:[.,]\d+)?\]?\s*)+/;
|
|
9663
|
+
const PROGRESS_LINE_RE = /(?:^|\s)(?:\d{1,3}%|Downloading|Receiving objects|Resolving deltas|Compressing objects|npm (?:WARN|notice|http|sill|verb)|##\[(?:group|endgroup|command|section)\]|\[\d+\/\d+\]|ETA[: ]|█|━|▕|⣿)/;
|
|
9664
|
+
const ERROR_RE = /\b(?:error|err!|fail(?:ed|ure)?|exception|panic|fatal|traceback|ERR_|E[A-Z]{3,})\b|✗|❌/i;
|
|
9665
|
+
|
|
9666
|
+
/** Remove a leading timestamp prefix from a single line. */
|
|
9667
|
+
function stripTimestamp(line) {
|
|
9668
|
+
return line.replace(TS_PREFIX_RE, '');
|
|
9669
|
+
}
|
|
9670
|
+
|
|
9671
|
+
/**
|
|
9672
|
+
* @param {string} input
|
|
9673
|
+
* @param {object} [opts]
|
|
9674
|
+
* @param {number} [opts.context=2] context lines kept around each error
|
|
9675
|
+
* @returns {{ squeezed: string, kept: string[], stripped: string[] }}
|
|
9676
|
+
*/
|
|
9677
|
+
function squeezeCiLog(input, opts = {}) {
|
|
9678
|
+
const ctx = opts.context != null ? opts.context : 2;
|
|
9679
|
+
const lines = input.split('\n');
|
|
9680
|
+
const keep = new Set();
|
|
9681
|
+
const errorIdx = [];
|
|
9682
|
+
|
|
9683
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9684
|
+
if (ERROR_RE.test(lines[i])) {
|
|
9685
|
+
errorIdx.push(i);
|
|
9686
|
+
for (let j = Math.max(0, i - ctx); j <= Math.min(lines.length - 1, i + ctx); j++) keep.add(j);
|
|
9687
|
+
}
|
|
9688
|
+
}
|
|
9689
|
+
|
|
9690
|
+
let body;
|
|
9691
|
+
let keptDesc;
|
|
9692
|
+
if (errorIdx.length === 0) {
|
|
9693
|
+
const head = lines.slice(0, 10).map(stripTimestamp);
|
|
9694
|
+
const tail = lines.length > 20 ? lines.slice(-10).map(stripTimestamp) : [];
|
|
9695
|
+
body = head.slice();
|
|
9696
|
+
if (tail.length) body.push(`… (${lines.length - 20} lines omitted) …`, ...tail);
|
|
9697
|
+
keptDesc = ['head/tail summary (no error lines found)'];
|
|
9698
|
+
} else {
|
|
9699
|
+
const sorted = [...keep].sort((a, b) => a - b);
|
|
9700
|
+
body = [];
|
|
9701
|
+
let prev = -2;
|
|
9702
|
+
for (const idx of sorted) {
|
|
9703
|
+
// Drop pure progress/noise lines unless they are themselves errors.
|
|
9704
|
+
const line = stripTimestamp(lines[idx]);
|
|
9705
|
+
if (PROGRESS_LINE_RE.test(line) && !ERROR_RE.test(line)) continue;
|
|
9706
|
+
if (idx > prev + 1) body.push(`… (${idx - prev - 1} lines) …`);
|
|
9707
|
+
body.push(line);
|
|
9708
|
+
prev = idx;
|
|
9709
|
+
}
|
|
9710
|
+
if (body.length === 0) body = errorIdx.map((i) => stripTimestamp(lines[i])); // safety net
|
|
9711
|
+
keptDesc = [`${errorIdx.length} error line(s) + ${ctx}-line context`];
|
|
9712
|
+
}
|
|
9713
|
+
|
|
9714
|
+
return {
|
|
9715
|
+
squeezed: body.join('\n'),
|
|
9716
|
+
kept: keptDesc,
|
|
9717
|
+
stripped: [`${Math.max(0, lines.length - body.length)} timestamp/progress/noise line(s)`],
|
|
9718
|
+
};
|
|
9719
|
+
}
|
|
9720
|
+
|
|
9721
|
+
module.exports = { squeezeCiLog, stripTimestamp, ERROR_RE };
|
|
9722
|
+
|
|
9723
|
+
};
|
|
9724
|
+
|
|
9725
|
+
// ── ./src/squeeze/jsonpayload ──
|
|
9726
|
+
__factories["./src/squeeze/jsonpayload"] = function(module, exports) {
|
|
9727
|
+
'use strict';
|
|
9728
|
+
|
|
9729
|
+
/**
|
|
9730
|
+
* JSON-payload squeeze (v7.0.0).
|
|
9731
|
+
*
|
|
9732
|
+
* Collapses repeated array elements, truncates long string values, and
|
|
9733
|
+
* preserves the schema shape at every depth — so an LLM still sees the
|
|
9734
|
+
* structure of an API/GraphQL/validation error without the bulk.
|
|
9735
|
+
*/
|
|
9736
|
+
|
|
9737
|
+
const MAX_STR = 500;
|
|
9738
|
+
const ARRAY_KEEP = 2;
|
|
9739
|
+
|
|
9740
|
+
function squeezeValue(v, opts) {
|
|
9741
|
+
const maxStr = opts.maxStr;
|
|
9742
|
+
const keep = opts.arrayKeep;
|
|
9743
|
+
if (Array.isArray(v)) {
|
|
9744
|
+
if (v.length <= keep + 1) return v.map((x) => squeezeValue(x, opts));
|
|
9745
|
+
const head = v.slice(0, keep).map((x) => squeezeValue(x, opts));
|
|
9746
|
+
head.push(`…${v.length - keep} more similar items`);
|
|
9747
|
+
return head;
|
|
9748
|
+
}
|
|
9749
|
+
if (v && typeof v === 'object') {
|
|
9750
|
+
const o = {};
|
|
9751
|
+
for (const k of Object.keys(v)) o[k] = squeezeValue(v[k], opts);
|
|
9752
|
+
return o;
|
|
9753
|
+
}
|
|
9754
|
+
if (typeof v === 'string' && v.length > maxStr) {
|
|
9755
|
+
return v.slice(0, maxStr) + `…(${v.length} chars)`;
|
|
9756
|
+
}
|
|
9757
|
+
return v;
|
|
9758
|
+
}
|
|
9759
|
+
|
|
9760
|
+
/**
|
|
9761
|
+
* @param {string} input
|
|
9762
|
+
* @param {object} [opts]
|
|
9763
|
+
* @param {number} [opts.maxStr=500] truncate strings longer than this
|
|
9764
|
+
* @param {number} [opts.arrayKeep=2] array items kept before collapsing
|
|
9765
|
+
* @returns {{ squeezed, kept, stripped }}
|
|
9766
|
+
*/
|
|
9767
|
+
function squeezeJsonPayload(input, opts = {}) {
|
|
9768
|
+
let parsed;
|
|
9769
|
+
try { parsed = JSON.parse(input); }
|
|
9770
|
+
catch (_) { return { squeezed: input, kept: ['(not valid JSON — unchanged)'], stripped: [] }; }
|
|
9771
|
+
const cfg = { maxStr: opts.maxStr != null ? opts.maxStr : MAX_STR, arrayKeep: opts.arrayKeep != null ? opts.arrayKeep : ARRAY_KEEP };
|
|
9772
|
+
const squeezed = JSON.stringify(squeezeValue(parsed, cfg), null, 2);
|
|
9773
|
+
return {
|
|
9774
|
+
squeezed,
|
|
9775
|
+
kept: ['schema shape preserved at all depths'],
|
|
9776
|
+
stripped: ['collapsed repeated array items; truncated long string values'],
|
|
9777
|
+
};
|
|
9778
|
+
}
|
|
9779
|
+
|
|
9780
|
+
module.exports = { squeezeJsonPayload, squeezeValue };
|
|
9781
|
+
|
|
9782
|
+
};
|
|
9783
|
+
|
|
9784
|
+
// ── ./src/squeeze/stacktrace ──
|
|
9785
|
+
__factories["./src/squeeze/stacktrace"] = function(module, exports) {
|
|
9786
|
+
'use strict';
|
|
9787
|
+
|
|
9788
|
+
/**
|
|
9789
|
+
* Stack-trace squeeze (v7.0.0) — the highest-value squeeze module.
|
|
9790
|
+
*
|
|
9791
|
+
* Dedupes repeated exceptions, strips vendor frames, keeps frames in the user's
|
|
9792
|
+
* own source dirs, and — the differentiator — **enriches the top kept frame**
|
|
9793
|
+
* with its real signature from the SigMap symbol index (`buildSigIndex`).
|
|
9794
|
+
* Generic log summarizers can't do this; SigMap has the repo's symbol map.
|
|
9795
|
+
*
|
|
9796
|
+
* Pure/deterministic. The symbol index is injected via `opts.symbolIndex` so
|
|
9797
|
+
* the module is unit-testable without touching the filesystem.
|
|
9798
|
+
*/
|
|
9799
|
+
|
|
9800
|
+
const path = require('path');
|
|
9801
|
+
|
|
9802
|
+
const VENDOR_RE = /(?:^|[\\/])(?:node_modules|vendor|site-packages|dist|build|\.venv|venv|third_party|external|\.cargo|go\/pkg\/mod)[\\/]/;
|
|
9803
|
+
|
|
9804
|
+
/** Parse a frame line across JS/TS, Python, Java/Kotlin, Go, Rust, native. */
|
|
9805
|
+
function parseFrame(line) {
|
|
9806
|
+
let m;
|
|
9807
|
+
if ((m = line.match(/^\s*at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/))) return { fn: m[1], file: m[2], line: +m[3], raw: line };
|
|
9808
|
+
if ((m = line.match(/^\s*at\s+(.+?):(\d+):(\d+)\s*$/))) return { fn: '', file: m[1], line: +m[2], raw: line };
|
|
9809
|
+
if ((m = line.match(/^\s*at\s+([\w$.<>]+)\((.+?):(\d+)\)/))) return { fn: m[1], file: m[2], line: +m[3], raw: line };
|
|
9810
|
+
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 };
|
|
9811
|
+
if ((m = line.match(/^\s*(.+\.(?:go|rs)):(\d+)/))) return { fn: '', file: m[1], line: +m[2], raw: line };
|
|
9812
|
+
return null;
|
|
9813
|
+
}
|
|
9814
|
+
|
|
9815
|
+
function isVendor(file) { return VENDOR_RE.test(String(file).replace(/\\/g, '/')); }
|
|
9816
|
+
|
|
9817
|
+
function inSrcDirs(file, srcDirs) {
|
|
9818
|
+
const f = String(file).replace(/\\/g, '/');
|
|
9819
|
+
return srcDirs.some((d) => {
|
|
9820
|
+
const dd = String(d).replace(/^\.\//, '').replace(/\/$/, '');
|
|
9821
|
+
return dd && (f === dd || f.startsWith(dd + '/') || f.includes('/' + dd + '/'));
|
|
9822
|
+
});
|
|
9823
|
+
}
|
|
9824
|
+
|
|
9825
|
+
/** Look up the real signature for a frame in the SigMap symbol index. */
|
|
9826
|
+
function enrichFrame(frame, symbolIndex) {
|
|
9827
|
+
if (!symbolIndex || !frame) return null;
|
|
9828
|
+
const want = String(frame.file).replace(/\\/g, '/');
|
|
9829
|
+
const base = path.basename(want);
|
|
9830
|
+
let key = null;
|
|
9831
|
+
for (const k0 of symbolIndex.keys()) {
|
|
9832
|
+
const k = String(k0).replace(/\\/g, '/');
|
|
9833
|
+
if (k === want || want.endsWith('/' + k) || k.endsWith('/' + want)) { key = k0; break; }
|
|
9834
|
+
if (!key && path.basename(k) === base) key = k0;
|
|
9835
|
+
}
|
|
9836
|
+
if (!key) return null;
|
|
9837
|
+
const sigs = symbolIndex.get(key) || [];
|
|
9838
|
+
const wantFn = frame.fn ? frame.fn.split('.').pop() : '';
|
|
9839
|
+
let byLine = null, byName = null;
|
|
9840
|
+
for (const sig of sigs) {
|
|
9841
|
+
const s = String(sig);
|
|
9842
|
+
const mm = s.match(/:(\d+)(?:-(\d+))?\s*$/);
|
|
9843
|
+
if (mm) {
|
|
9844
|
+
const a = +mm[1], b = mm[2] ? +mm[2] : a;
|
|
9845
|
+
if (frame.line >= a && frame.line <= b) byLine = s;
|
|
9846
|
+
}
|
|
9847
|
+
if (wantFn && new RegExp('\\b' + wantFn.replace(/[^\w$]/g, '') + '\\b').test(s)) byName = byName || s;
|
|
9848
|
+
}
|
|
9849
|
+
const sig = byLine || byName;
|
|
9850
|
+
return sig ? { file: key, sig: sig.replace(/\s*:\d+(?:-\d+)?\s*$/, '').trim() } : null;
|
|
9851
|
+
}
|
|
9852
|
+
|
|
9853
|
+
/**
|
|
9854
|
+
* @param {string} input
|
|
9855
|
+
* @param {object} [opts]
|
|
9856
|
+
* @param {string[]} [opts.srcDirs] user source dirs (default ['src'])
|
|
9857
|
+
* @param {Map} [opts.symbolIndex] SigMap signature index for enrichment
|
|
9858
|
+
* @param {number} [opts.maxFrames=8] cap on kept source frames
|
|
9859
|
+
* @returns {{ squeezed, kept, stripped, enriched }}
|
|
9860
|
+
*/
|
|
9861
|
+
function squeezeStackTrace(input, opts = {}) {
|
|
9862
|
+
const srcDirs = (opts.srcDirs && opts.srcDirs.length) ? opts.srcDirs : ['src'];
|
|
9863
|
+
const maxFrames = opts.maxFrames != null ? opts.maxFrames : 8;
|
|
9864
|
+
const lines = input.split('\n');
|
|
9865
|
+
|
|
9866
|
+
const headerCount = new Map();
|
|
9867
|
+
const headerOrder = [];
|
|
9868
|
+
const frames = [];
|
|
9869
|
+
for (const line of lines) {
|
|
9870
|
+
const f = parseFrame(line);
|
|
9871
|
+
if (f) { frames.push(f); continue; }
|
|
9872
|
+
const t = line.trim();
|
|
9873
|
+
if (!t) continue;
|
|
9874
|
+
if (!headerCount.has(t)) headerOrder.push(t);
|
|
9875
|
+
headerCount.set(t, (headerCount.get(t) || 0) + 1);
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
const seen = new Set();
|
|
9879
|
+
let dupFrames = 0;
|
|
9880
|
+
const nonVendor = [];
|
|
9881
|
+
const sourceFrames = [];
|
|
9882
|
+
let vendorCount = 0;
|
|
9883
|
+
for (const f of frames) {
|
|
9884
|
+
const k = f.file + ':' + f.line;
|
|
9885
|
+
if (seen.has(k)) { dupFrames++; continue; }
|
|
9886
|
+
seen.add(k);
|
|
9887
|
+
if (isVendor(f.file)) { vendorCount++; continue; }
|
|
9888
|
+
nonVendor.push(f);
|
|
9889
|
+
if (inSrcDirs(f.file, srcDirs)) sourceFrames.push(f);
|
|
9890
|
+
}
|
|
9891
|
+
|
|
9892
|
+
// Prefer source frames; never return empty (fall back to top non-vendor, then raw).
|
|
9893
|
+
const shown = sourceFrames.length ? sourceFrames.slice(0, maxFrames)
|
|
9894
|
+
: (nonVendor.length ? nonVendor.slice(0, 3) : frames.slice(0, 3));
|
|
9895
|
+
|
|
9896
|
+
const enrichment = shown.length ? enrichFrame(shown[0], opts.symbolIndex) : null;
|
|
9897
|
+
|
|
9898
|
+
const out = [];
|
|
9899
|
+
for (const h of headerOrder) {
|
|
9900
|
+
const n = headerCount.get(h);
|
|
9901
|
+
out.push(n > 1 ? `${h} (occurred ×${n})` : h);
|
|
9902
|
+
}
|
|
9903
|
+
for (let i = 0; i < shown.length; i++) {
|
|
9904
|
+
out.push(' ' + shown[i].raw.trim());
|
|
9905
|
+
if (i === 0 && enrichment) out.push(` ↳ ${enrichment.sig} [${enrichment.file}]`);
|
|
9906
|
+
}
|
|
9907
|
+
|
|
9908
|
+
return {
|
|
9909
|
+
squeezed: out.join('\n'),
|
|
9910
|
+
kept: [
|
|
9911
|
+
`${headerOrder.length} unique exception(s)`,
|
|
9912
|
+
`top ${shown.length} ${sourceFrames.length ? 'source ' : ''}frame(s)`,
|
|
9913
|
+
...(enrichment ? [`enriched ${path.basename(shown[0].file)}:${shown[0].line}`] : []),
|
|
9914
|
+
],
|
|
9915
|
+
stripped: [`${vendorCount} vendor frame(s)`, `${dupFrames} duplicate frame(s)`],
|
|
9916
|
+
enriched: !!enrichment,
|
|
9917
|
+
};
|
|
9918
|
+
}
|
|
9919
|
+
|
|
9920
|
+
module.exports = { squeezeStackTrace, parseFrame, isVendor, inSrcDirs, enrichFrame };
|
|
9921
|
+
|
|
9922
|
+
};
|
|
9923
|
+
|
|
9924
|
+
// ── ./src/squeeze/index ──
|
|
9925
|
+
__factories["./src/squeeze/index"] = function(module, exports) {
|
|
9926
|
+
'use strict';
|
|
9927
|
+
|
|
9928
|
+
/**
|
|
9929
|
+
* Squeeze orchestrator (v7.0.0): classify → squeeze → reduction → decision.
|
|
9930
|
+
*
|
|
9931
|
+
* Always-on and silent: callers run `squeeze()` on pasted input, then use
|
|
9932
|
+
* `shouldPrompt()` to decide whether the reduction is worth interrupting for.
|
|
9933
|
+
* Everything is deterministic and offline; the symbol index for stack-trace
|
|
9934
|
+
* enrichment is passed through via `opts.symbolIndex`.
|
|
9935
|
+
*/
|
|
9936
|
+
|
|
9937
|
+
const { classify } = __require('./src/squeeze/classify');
|
|
9938
|
+
const { squeezeStackTrace } = __require('./src/squeeze/stacktrace');
|
|
9939
|
+
const { squeezeCiLog } = __require('./src/squeeze/cilog');
|
|
9940
|
+
const { squeezeJsonPayload } = __require('./src/squeeze/jsonpayload');
|
|
9941
|
+
|
|
9942
|
+
function estimateTokens(s) { return Math.ceil(String(s || '').length / 4); }
|
|
9943
|
+
|
|
9944
|
+
/**
|
|
9945
|
+
* @param {string} input
|
|
9946
|
+
* @param {object} [opts] forwarded to the category squeezer (srcDirs, symbolIndex, …)
|
|
9947
|
+
* @returns {{ category, confidence, original, squeezed, rawTokens, squeezedTokens, reduction, kept, stripped, enriched, applies }}
|
|
9948
|
+
*/
|
|
9949
|
+
function squeeze(input, opts = {}) {
|
|
9950
|
+
const { category, confidence } = classify(input);
|
|
9951
|
+
const rawTokens = estimateTokens(input);
|
|
9952
|
+
const base = {
|
|
9953
|
+
category, confidence, original: input, squeezed: input,
|
|
9954
|
+
rawTokens, squeezedTokens: rawTokens, reduction: 0,
|
|
9955
|
+
kept: [], stripped: [], enriched: false, applies: false,
|
|
9956
|
+
};
|
|
9957
|
+
if (!category) return base;
|
|
9958
|
+
|
|
9959
|
+
let r;
|
|
9960
|
+
if (category === 'stacktrace') r = squeezeStackTrace(input, opts);
|
|
9961
|
+
else if (category === 'cilog') r = squeezeCiLog(input, opts);
|
|
9962
|
+
else r = squeezeJsonPayload(input, opts);
|
|
9963
|
+
|
|
9964
|
+
const squeezedTokens = estimateTokens(r.squeezed);
|
|
9965
|
+
const reduction = rawTokens > 0 ? (rawTokens - squeezedTokens) / rawTokens : 0;
|
|
9966
|
+
return {
|
|
9967
|
+
category, confidence,
|
|
9968
|
+
original: input, squeezed: r.squeezed,
|
|
9969
|
+
rawTokens, squeezedTokens,
|
|
9970
|
+
reduction: Math.max(0, Number(reduction.toFixed(4))),
|
|
9971
|
+
kept: r.kept || [], stripped: r.stripped || [], enriched: !!r.enriched,
|
|
9972
|
+
applies: squeezedTokens < rawTokens,
|
|
9973
|
+
};
|
|
9974
|
+
}
|
|
9975
|
+
|
|
9976
|
+
/** True when the reduction clears the threshold (accepts 0–1 or 0–100). */
|
|
9977
|
+
function shouldPrompt(reduction, threshold) {
|
|
9978
|
+
const t = threshold > 1 ? threshold / 100 : threshold;
|
|
9979
|
+
return reduction >= t;
|
|
9980
|
+
}
|
|
9981
|
+
|
|
9982
|
+
/** A compact human summary of what squeeze would do (for the prompt). */
|
|
9983
|
+
function formatSummary(result) {
|
|
9984
|
+
const pct = Math.round(result.reduction * 100);
|
|
9985
|
+
const lines = [
|
|
9986
|
+
`Input: ${result.rawTokens.toLocaleString()} tokens`,
|
|
9987
|
+
`Can reduce to ${result.squeezedTokens.toLocaleString()} tokens (${pct}% smaller):`,
|
|
9988
|
+
];
|
|
9989
|
+
for (const k of result.kept) lines.push(` ✓ Kept: ${k}`);
|
|
9990
|
+
for (const s of result.stripped) lines.push(` ✗ Stripped: ${s}`);
|
|
9991
|
+
return lines.join('\n');
|
|
9992
|
+
}
|
|
9993
|
+
|
|
9994
|
+
module.exports = { squeeze, shouldPrompt, formatSummary, estimateTokens };
|
|
9995
|
+
|
|
9996
|
+
};
|
|
9997
|
+
|
|
9998
|
+
// ── ./src/nudge ──
|
|
9999
|
+
__factories["./src/nudge"] = function(module, exports) {
|
|
10000
|
+
'use strict';
|
|
10001
|
+
|
|
10002
|
+
/**
|
|
10003
|
+
* Star nudge + usage tracking (v7.0.0).
|
|
10004
|
+
*
|
|
10005
|
+
* Records run counts in `.context/usage.json` and shows a one-time GitHub-star
|
|
10006
|
+
* message after the tool has been genuinely useful (≥10 runs, ≥8 successes).
|
|
10007
|
+
* Shown exactly once per machine — even under concurrent runs (an `wx` lock
|
|
10008
|
+
* file makes the show race-safe). Wired into `ask` (and the `squeeze` path).
|
|
10009
|
+
*/
|
|
10010
|
+
|
|
10011
|
+
const fs = require('fs');
|
|
10012
|
+
const path = require('path');
|
|
10013
|
+
|
|
10014
|
+
const RUN_THRESHOLD = 10;
|
|
10015
|
+
const SUCCESS_THRESHOLD = 8;
|
|
10016
|
+
|
|
10017
|
+
function usagePath(cwd) { return path.join(cwd, '.context', 'usage.json'); }
|
|
10018
|
+
|
|
10019
|
+
function defaultUsage() {
|
|
10020
|
+
return {
|
|
10021
|
+
totalRuns: 0, successfulRuns: 0, squeezeOffered: 0, squeezeAccepted: 0,
|
|
10022
|
+
starNudgeShown: false, firstRunDate: null, lastRunDate: null,
|
|
10023
|
+
};
|
|
10024
|
+
}
|
|
10025
|
+
|
|
10026
|
+
function readUsage(cwd) {
|
|
10027
|
+
try { return { ...defaultUsage(), ...JSON.parse(fs.readFileSync(usagePath(cwd), 'utf8')) }; }
|
|
10028
|
+
catch (_) { return defaultUsage(); }
|
|
10029
|
+
}
|
|
10030
|
+
|
|
10031
|
+
function writeUsageAtomic(cwd, usage) {
|
|
10032
|
+
const p = usagePath(cwd);
|
|
10033
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
10034
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
10035
|
+
fs.writeFileSync(tmp, JSON.stringify(usage, null, 2));
|
|
10036
|
+
fs.renameSync(tmp, p); // atomic on POSIX
|
|
10037
|
+
}
|
|
10038
|
+
|
|
10039
|
+
const STAR_MESSAGE = [
|
|
10040
|
+
'─────────────────────────────────────────────────────────',
|
|
10041
|
+
' SigMap has helped you 10 times now.',
|
|
10042
|
+
'',
|
|
10043
|
+
" If it's been useful, a GitHub star takes 5 seconds and",
|
|
10044
|
+
' helps other developers find it:',
|
|
10045
|
+
' → github.com/manojmallick/sigmap',
|
|
10046
|
+
'',
|
|
10047
|
+
" (Won't ask again. Press Enter to continue.)",
|
|
10048
|
+
'─────────────────────────────────────────────────────────',
|
|
10049
|
+
].join('\n');
|
|
10050
|
+
|
|
10051
|
+
function showStarNudge(write) {
|
|
10052
|
+
(write || ((s) => process.stderr.write(s)))('\n' + STAR_MESSAGE + '\n');
|
|
10053
|
+
}
|
|
10054
|
+
|
|
10055
|
+
/**
|
|
10056
|
+
* Record one run and, when the thresholds are first met, show the star nudge.
|
|
10057
|
+
* @param {string} cwd
|
|
10058
|
+
* @param {boolean} runSuccess
|
|
10059
|
+
* @param {object} [opts]
|
|
10060
|
+
* @param {boolean} [opts.silent] record only — never print
|
|
10061
|
+
* @param {function} [opts.write] sink for the message (default stderr)
|
|
10062
|
+
* @param {string} [opts.today] override date (testing)
|
|
10063
|
+
* @param {object} [opts.bump] counter deltas to merge (e.g. { squeezeAccepted: 1 })
|
|
10064
|
+
* @returns {{ usage, nudged }}
|
|
10065
|
+
*/
|
|
10066
|
+
function checkStarNudge(cwd, runSuccess, opts = {}) {
|
|
10067
|
+
const usage = readUsage(cwd);
|
|
10068
|
+
usage.totalRuns += 1;
|
|
10069
|
+
if (runSuccess) usage.successfulRuns += 1;
|
|
10070
|
+
if (opts.bump) for (const k of Object.keys(opts.bump)) usage[k] = (usage[k] || 0) + opts.bump[k];
|
|
10071
|
+
|
|
10072
|
+
const today = opts.today || new Date().toISOString().slice(0, 10);
|
|
10073
|
+
if (!usage.firstRunDate) usage.firstRunDate = today;
|
|
10074
|
+
usage.lastRunDate = today;
|
|
10075
|
+
|
|
10076
|
+
let nudged = false;
|
|
10077
|
+
if (!usage.starNudgeShown && usage.totalRuns >= RUN_THRESHOLD && usage.successfulRuns >= SUCCESS_THRESHOLD) {
|
|
10078
|
+
// Race-safe single-show: only the process that creates the lock prints.
|
|
10079
|
+
let won = false;
|
|
10080
|
+
try { fs.closeSync(fs.openSync(usagePath(cwd) + '.nudge.lock', 'wx')); won = true; }
|
|
10081
|
+
catch (_) { won = false; }
|
|
10082
|
+
if (won && !opts.silent) showStarNudge(opts.write);
|
|
10083
|
+
nudged = won;
|
|
10084
|
+
usage.starNudgeShown = true;
|
|
10085
|
+
}
|
|
10086
|
+
|
|
10087
|
+
writeUsageAtomic(cwd, usage);
|
|
10088
|
+
return { usage, nudged };
|
|
10089
|
+
}
|
|
10090
|
+
|
|
10091
|
+
module.exports = { checkStarNudge, readUsage, usagePath, showStarNudge, RUN_THRESHOLD, SUCCESS_THRESHOLD };
|
|
10092
|
+
|
|
10093
|
+
};
|
|
10094
|
+
|
|
9183
10095
|
// ── ./src/verify/parsers ──
|
|
9184
10096
|
__factories["./src/verify/parsers"] = function(module, exports) {
|
|
9185
10097
|
'use strict';
|
|
@@ -10022,9 +10934,20 @@ module.exports = { verify, buildSymbolSet, loadDeps, loadScripts, isTestPath };
|
|
|
10022
10934
|
const fs = require('fs');
|
|
10023
10935
|
const path = require('path');
|
|
10024
10936
|
const os = require('os');
|
|
10025
|
-
const { execSync } = require('child_process');
|
|
10026
10937
|
|
|
10027
|
-
|
|
10938
|
+
// Shell-free subprocess helpers. execFileSync runs the binary directly (never
|
|
10939
|
+
// /bin/sh), so there is no shell-injection surface and no "Shell access"
|
|
10940
|
+
// capability for supply-chain scanners. stderr is discarded by default.
|
|
10941
|
+
function __git(args, opts = {}) {
|
|
10942
|
+
const { execFileSync } = require('child_process');
|
|
10943
|
+
return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], ...opts });
|
|
10944
|
+
}
|
|
10945
|
+
function __tryGit(args, opts = {}) {
|
|
10946
|
+
try { return __git(args, opts).toString().trim(); }
|
|
10947
|
+
catch (_) { return ''; }
|
|
10948
|
+
}
|
|
10949
|
+
|
|
10950
|
+
const VERSION = '7.0.1';
|
|
10028
10951
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
10029
10952
|
|
|
10030
10953
|
function requireSourceOrBundled(key) {
|
|
@@ -10324,29 +11247,17 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
10324
11247
|
let total = renderedTotal(fileEntries);
|
|
10325
11248
|
if (total <= budgetForEntries) return fileEntries;
|
|
10326
11249
|
|
|
10327
|
-
//
|
|
10328
|
-
//
|
|
10329
|
-
//
|
|
10330
|
-
//
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
});
|
|
10339
|
-
const collapsedRendered = renderedTotal(collapsed);
|
|
10340
|
-
if (collapsedRendered < total) {
|
|
10341
|
-
console.warn(`[sigmap] budget: collapsed bodies to anchors, reclaimed ~${total - collapsedRendered} tokens`);
|
|
10342
|
-
working = collapsed;
|
|
10343
|
-
total = collapsedRendered;
|
|
10344
|
-
// Collapsing keeps every file, so the section overhead must fit too — not just sigs.
|
|
10345
|
-
if (total <= budgetForEntries) return working;
|
|
10346
|
-
}
|
|
10347
|
-
|
|
10348
|
-
// Sort by drop priority (drop first = index 0)
|
|
10349
|
-
const withPriority = working.map((e) => {
|
|
11250
|
+
// Over budget — degrade gracefully, best file first:
|
|
11251
|
+
// 1. keep FULL signatures (params + return type) while they fit;
|
|
11252
|
+
// 2. when a file no longer fits with full sigs, collapse just THAT file to
|
|
11253
|
+
// its line-anchor pointers (still discoverable via buildSigIndex / the
|
|
11254
|
+
// ranker — it parses the context file, so an anchored file stays findable);
|
|
11255
|
+
// 3. drop a file only when even the anchor form won't fit.
|
|
11256
|
+
// The important, high-priority files therefore keep their real signatures —
|
|
11257
|
+
// the user-visible value — while only low-priority overflow degrades to
|
|
11258
|
+
// anchors instead of vanishing. (Earlier versions collapsed EVERY file to an
|
|
11259
|
+
// anchor the moment you went 1 token over budget, gutting all signatures.)
|
|
11260
|
+
const withPriority = fileEntries.map((e) => {
|
|
10350
11261
|
let priority = 0;
|
|
10351
11262
|
let dropReason = 'budget: low recency';
|
|
10352
11263
|
if (isGeneratedFile(e.filePath)) { priority = 10; dropReason = 'budget: generated file'; }
|
|
@@ -10354,41 +11265,59 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
10354
11265
|
else if (isTestFile(e.filePath)) { priority = 8; dropReason = 'budget: test file'; }
|
|
10355
11266
|
else if (isConfigFile(e.filePath)) { priority = 6; dropReason = 'budget: config file'; }
|
|
10356
11267
|
else priority = 4;
|
|
10357
|
-
// v4.0: signal quality = sigs per line-of-code (higher = more informative)
|
|
10358
11268
|
const loc = e.content ? e.content.split('\n').length : 1;
|
|
10359
11269
|
const signalQuality = loc > 0 ? (e.sigs ? e.sigs.length : 0) / loc : 0;
|
|
10360
11270
|
return { ...e, priority, dropReason, signalQuality };
|
|
10361
11271
|
});
|
|
10362
11272
|
|
|
10363
|
-
//
|
|
10364
|
-
withPriority.sort((a, b) => {
|
|
10365
|
-
if (
|
|
10366
|
-
if ((
|
|
10367
|
-
return (
|
|
11273
|
+
// Best-first order: lowest drop-priority, then most-recent, then most-informative.
|
|
11274
|
+
const bestFirst = withPriority.slice().sort((a, b) => {
|
|
11275
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
11276
|
+
if ((b.mtime || 0) !== (a.mtime || 0)) return (b.mtime || 0) - (a.mtime || 0);
|
|
11277
|
+
return (b.signalQuality || 0) - (a.signalQuality || 0);
|
|
10368
11278
|
});
|
|
10369
11279
|
|
|
10370
|
-
const
|
|
11280
|
+
const entryCost = (e) => estimateTokens(e.sigs.join('\n')) + sectionOverhead(e);
|
|
11281
|
+
const collapseEntry = (e) => ({
|
|
11282
|
+
...e,
|
|
11283
|
+
sigs: (e.sigs || []).map((s) => {
|
|
11284
|
+
const line = toIndexLine(s);
|
|
11285
|
+
return line && /:\d+-\d+/.test(line) ? line : s;
|
|
11286
|
+
}),
|
|
11287
|
+
});
|
|
11288
|
+
// Keep FULL signatures for the best files while they fit (signatures are the
|
|
11289
|
+
// value the user reads). When a file's full form no longer fits, fall back to
|
|
11290
|
+
// its anchor form (still discoverable via the ranker, which parses this file);
|
|
11291
|
+
// drop only when even the anchor won't fit. So the important files keep real
|
|
11292
|
+
// signatures and only the lowest-priority overflow degrades — instead of every
|
|
11293
|
+
// signature being gutted to an anchor the moment you go over budget.
|
|
11294
|
+
const finalByPath = new Map();
|
|
10371
11295
|
const verboseDropped = [];
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
kept.push(entry);
|
|
11296
|
+
let collapsedCount = 0;
|
|
11297
|
+
let used = 0;
|
|
11298
|
+
for (const entry of bestFirst) { // best first
|
|
11299
|
+
if (used + entryCost(entry) <= budgetForEntries) {
|
|
11300
|
+
finalByPath.set(entry.filePath, entry); used += entryCost(entry); continue;
|
|
11301
|
+
}
|
|
11302
|
+
const slim = collapseEntry(entry);
|
|
11303
|
+
if (used + entryCost(slim) <= budgetForEntries) {
|
|
11304
|
+
finalByPath.set(entry.filePath, slim); used += entryCost(slim); collapsedCount++; continue;
|
|
10382
11305
|
}
|
|
11306
|
+
verboseDropped.push({ filePath: entry.filePath, reason: entry.dropReason });
|
|
10383
11307
|
}
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
11308
|
+
// Restore the original file order for stable output.
|
|
11309
|
+
const kept = withPriority.filter((e) => finalByPath.has(e.filePath)).map((e) => finalByPath.get(e.filePath));
|
|
11310
|
+
|
|
11311
|
+
if (verboseDropped.length > 0 || collapsedCount > 0) {
|
|
11312
|
+
const parts = [];
|
|
11313
|
+
if (verboseDropped.length) parts.push(`dropped ${verboseDropped.length} file(s)`);
|
|
11314
|
+
if (collapsedCount) parts.push(`collapsed ${collapsedCount} low-priority file(s) to anchors`);
|
|
11315
|
+
console.warn(`[sigmap] budget: ${parts.join(', ')} to stay under ${maxTokens} tokens`);
|
|
10387
11316
|
if (process.argv.includes('--verbose')) {
|
|
10388
11317
|
for (const { filePath, reason } of verboseDropped) {
|
|
10389
11318
|
console.warn(`[sigmap] dropped: ${path.relative(process.cwd(), filePath)} — ${reason}`);
|
|
10390
11319
|
}
|
|
10391
|
-
console.warn(`[sigmap] included: ${kept.length} files, dropped: ${verboseDropped.length}`);
|
|
11320
|
+
console.warn(`[sigmap] included: ${kept.length} files (${collapsedCount} collapsed), dropped: ${verboseDropped.length}`);
|
|
10392
11321
|
}
|
|
10393
11322
|
}
|
|
10394
11323
|
return kept;
|
|
@@ -10400,9 +11329,7 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
10400
11329
|
function getRecentlyCommittedFiles(cwd, count) {
|
|
10401
11330
|
const n = count || 10;
|
|
10402
11331
|
try {
|
|
10403
|
-
const out =
|
|
10404
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
10405
|
-
});
|
|
11332
|
+
const out = __git(['log', '--name-only', '--format=', '-n', String(n)], { cwd });
|
|
10406
11333
|
return new Set(out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
|
|
10407
11334
|
} catch (_) {
|
|
10408
11335
|
return new Set();
|
|
@@ -10414,8 +11341,8 @@ function getRecentlyCommittedFiles(cwd, count) {
|
|
|
10414
11341
|
// ---------------------------------------------------------------------------
|
|
10415
11342
|
function getDiffFiles(cwd, stagedOnly) {
|
|
10416
11343
|
try {
|
|
10417
|
-
const
|
|
10418
|
-
const out =
|
|
11344
|
+
const gitArgs = stagedOnly ? ['diff', '--cached', '--name-only'] : ['diff', 'HEAD', '--name-only'];
|
|
11345
|
+
const out = __git(gitArgs, { cwd });
|
|
10419
11346
|
return new Set(
|
|
10420
11347
|
out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f))
|
|
10421
11348
|
);
|
|
@@ -10513,17 +11440,13 @@ function buildChangesSection(cwd, config, fileEntries) {
|
|
|
10513
11440
|
try {
|
|
10514
11441
|
let range = `HEAD~${n}..HEAD`;
|
|
10515
11442
|
try {
|
|
10516
|
-
|
|
10517
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
10518
|
-
});
|
|
11443
|
+
__git(['rev-parse', '--verify', `HEAD~${n}`], { cwd });
|
|
10519
11444
|
} catch (_) {
|
|
10520
11445
|
// HEAD~n doesn't exist (shallow repo) — clamp to deepest valid ancestor
|
|
10521
11446
|
let best = 1;
|
|
10522
11447
|
for (let k = n - 1; k >= 2; k--) {
|
|
10523
11448
|
try {
|
|
10524
|
-
|
|
10525
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
10526
|
-
});
|
|
11449
|
+
__git(['rev-parse', '--verify', `HEAD~${k}`], { cwd });
|
|
10527
11450
|
best = k;
|
|
10528
11451
|
break;
|
|
10529
11452
|
} catch (_) {}
|
|
@@ -10531,15 +11454,11 @@ function buildChangesSection(cwd, config, fileEntries) {
|
|
|
10531
11454
|
range = `HEAD~${best}..HEAD`;
|
|
10532
11455
|
}
|
|
10533
11456
|
|
|
10534
|
-
const namesOnly =
|
|
10535
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
10536
|
-
});
|
|
11457
|
+
const namesOnly = __git(['diff', range, '--name-only'], { cwd });
|
|
10537
11458
|
const changed = new Set(namesOnly.split('\n').map((l) => l.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
|
|
10538
11459
|
if (changed.size === 0) return [];
|
|
10539
11460
|
|
|
10540
|
-
const timeAgo =
|
|
10541
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
10542
|
-
}).trim();
|
|
11461
|
+
const timeAgo = __git(['log', '-1', '--format=%cr'], { cwd }).trim();
|
|
10543
11462
|
|
|
10544
11463
|
const lines = [`## changes (last ${n} commits — ${timeAgo})`, '```'];
|
|
10545
11464
|
let hasDelta = false;
|
|
@@ -10548,9 +11467,7 @@ function buildChangesSection(cwd, config, fileEntries) {
|
|
|
10548
11467
|
const rel = path.relative(cwd, entry.filePath);
|
|
10549
11468
|
let diff = '';
|
|
10550
11469
|
try {
|
|
10551
|
-
diff =
|
|
10552
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
10553
|
-
});
|
|
11470
|
+
diff = __git(['diff', range, '--', rel], { cwd });
|
|
10554
11471
|
} catch (_) {
|
|
10555
11472
|
continue;
|
|
10556
11473
|
}
|
|
@@ -10610,12 +11527,17 @@ function resolveImpactRadius(fileEntries, cwd, config) {
|
|
|
10610
11527
|
}
|
|
10611
11528
|
|
|
10612
11529
|
function formatOutput(fileEntries, cwd, routingEnabled, config, extras) {
|
|
11530
|
+
// One canonical usage block for every context file (CLAUDE.md, AGENTS.md,
|
|
11531
|
+
// copilot-instructions.md, …). Lives here — the single source all writers
|
|
11532
|
+
// consume — so adapters don't each invent their own (now-removed) variant.
|
|
11533
|
+
const { usageBlock } = requireSourceOrBundled('./src/format/usage-guidance');
|
|
10613
11534
|
const lines = [
|
|
10614
11535
|
'<!-- Generated by SigMap gen-context.js v' + VERSION + ' -->',
|
|
10615
11536
|
'<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->',
|
|
10616
11537
|
'',
|
|
10617
11538
|
'# Code signatures',
|
|
10618
11539
|
'',
|
|
11540
|
+
usageBlock(),
|
|
10619
11541
|
];
|
|
10620
11542
|
|
|
10621
11543
|
// Compact dependency map section (shows import relationships, ~50-100 tokens)
|
|
@@ -11525,6 +12447,23 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
11525
12447
|
}
|
|
11526
12448
|
}
|
|
11527
12449
|
|
|
12450
|
+
// v7.0.1: count a plain context-generation run for the one-time star nudge.
|
|
12451
|
+
// Most users only ever run `sigmap` to build the context file — count that too,
|
|
12452
|
+
// not just ask/squeeze. Guard so a monorepo (many runGenerate calls per process)
|
|
12453
|
+
// counts as a single run. Interactive only — silent under --json/--report or non-TTY.
|
|
12454
|
+
if (!global.__sigmapGenCounted) {
|
|
12455
|
+
global.__sigmapGenCounted = true;
|
|
12456
|
+
try {
|
|
12457
|
+
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
12458
|
+
const showNudge = !!process.stderr.isTTY
|
|
12459
|
+
&& !reportJson
|
|
12460
|
+
&& !process.argv.includes('--json')
|
|
12461
|
+
&& !process.argv.includes('--report')
|
|
12462
|
+
&& !process.argv.includes('--quiet');
|
|
12463
|
+
checkStarNudge(global.__sigmapRootCwd || cwd, true, { silent: !showNudge });
|
|
12464
|
+
} catch (_) {}
|
|
12465
|
+
}
|
|
12466
|
+
|
|
11528
12467
|
return result;
|
|
11529
12468
|
}
|
|
11530
12469
|
|
|
@@ -11706,11 +12645,7 @@ function suggestTool(description) {
|
|
|
11706
12645
|
|
|
11707
12646
|
function resolveProjectRoot(startDir) {
|
|
11708
12647
|
try {
|
|
11709
|
-
const gitRoot =
|
|
11710
|
-
cwd: startDir,
|
|
11711
|
-
encoding: 'utf8',
|
|
11712
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
11713
|
-
}).trim();
|
|
12648
|
+
const gitRoot = __git(['rev-parse', '--show-toplevel'], { cwd: startDir }).trim();
|
|
11714
12649
|
if (gitRoot) return gitRoot;
|
|
11715
12650
|
} catch (_) {}
|
|
11716
12651
|
return startDir;
|
|
@@ -11786,6 +12721,10 @@ Usage:
|
|
|
11786
12721
|
${cmd} verify-ai-output <answer.md> Flag fake files/tests/imports/symbols/npm-scripts in an AI answer
|
|
11787
12722
|
${cmd} verify-ai-output <answer.md> --json Hallucination report as JSON (exits 1 if issues)
|
|
11788
12723
|
${cmd} verify-ai-output <answer.md> --report Write a standalone HTML report (red/amber/green)
|
|
12724
|
+
${cmd} squeeze <file|-> Minimize a pasted stacktrace/CI-log/JSON blob (--json for stats)
|
|
12725
|
+
${cmd} ask "<query>" --squeeze Auto-accept input minimization (no prompt; for scripts/CI)
|
|
12726
|
+
${cmd} ask "<query>" --no-squeeze Disable input minimization entirely
|
|
12727
|
+
${cmd} ask "<query>" --squeeze-threshold N Min reduction %% to prompt (default 30)
|
|
11789
12728
|
${cmd} note "<text>" Append a note to the cross-session decision log
|
|
11790
12729
|
${cmd} note List recent notes (also: note --list <N>)
|
|
11791
12730
|
${cmd} status Show repo state — branch, dirty files, index freshness, notes
|
|
@@ -11977,8 +12916,7 @@ function buildIndexContext(ranked, cwd) {
|
|
|
11977
12916
|
|
|
11978
12917
|
function computeCurrentRisk(cwd) {
|
|
11979
12918
|
try {
|
|
11980
|
-
const
|
|
11981
|
-
const out = execSync('git diff --name-only HEAD', { cwd, timeout: 3000, encoding: 'utf8' });
|
|
12919
|
+
const out = __git(['diff', '--name-only', 'HEAD'], { cwd, timeout: 3000 });
|
|
11982
12920
|
const count = out.trim().split('\n').filter(Boolean).length;
|
|
11983
12921
|
if (count === 0) return 'NONE';
|
|
11984
12922
|
if (count <= 3) return 'LOW';
|
|
@@ -12062,6 +13000,9 @@ function main() {
|
|
|
12062
13000
|
const cwd = cwdFlag
|
|
12063
13001
|
? path.resolve(invokedFrom, cwdFlag)
|
|
12064
13002
|
: resolveProjectRoot(invokedFrom);
|
|
13003
|
+
// v7.0.1: remember the invocation root so the generation star-nudge always tracks
|
|
13004
|
+
// usage at the repo root, even when runGenerate is called per-package (monorepo).
|
|
13005
|
+
global.__sigmapRootCwd = cwd;
|
|
12065
13006
|
const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');
|
|
12066
13007
|
|
|
12067
13008
|
if (cwdFlag) {
|
|
@@ -12174,8 +13115,8 @@ function main() {
|
|
|
12174
13115
|
const { loadSession, saveSession, mergeSessionContext } = requireSourceOrBundled('./src/session/memory');
|
|
12175
13116
|
const { detectWorkspaces, inferPackage, scopeToPackage } = requireSourceOrBundled('./src/workspace/detector');
|
|
12176
13117
|
|
|
12177
|
-
|
|
12178
|
-
|
|
13118
|
+
let intent = detectIntent(query);
|
|
13119
|
+
let intentWeights = getIntentWeights(intent);
|
|
12179
13120
|
|
|
12180
13121
|
const sigIndex = buildSigIndex(cwd);
|
|
12181
13122
|
if (sigIndex.size === 0) {
|
|
@@ -12183,6 +13124,44 @@ function main() {
|
|
|
12183
13124
|
process.exit(1);
|
|
12184
13125
|
}
|
|
12185
13126
|
|
|
13127
|
+
// v7.0.0: Squeeze — classify and minimize pasted stacktrace/CI-log/JSON
|
|
13128
|
+
// input before ranking. Always runs silently; only prompts (interactive
|
|
13129
|
+
// TTY) when the reduction clears the threshold. Never blocks pipes/CI.
|
|
13130
|
+
let squeezeOffered = false;
|
|
13131
|
+
let squeezeAccepted = false;
|
|
13132
|
+
if (!args.includes('--no-squeeze')) {
|
|
13133
|
+
try {
|
|
13134
|
+
const { squeeze: runSqueeze, shouldPrompt, formatSummary } = requireSourceOrBundled('./src/squeeze/index');
|
|
13135
|
+
const sq = runSqueeze(query, { srcDirs: config.srcDirs, symbolIndex: sigIndex });
|
|
13136
|
+
if (sq.applies && sq.reduction > 0) {
|
|
13137
|
+
squeezeOffered = true;
|
|
13138
|
+
const thrIdx = args.indexOf('--squeeze-threshold');
|
|
13139
|
+
const threshold = thrIdx !== -1 ? parseFloat(args[thrIdx + 1]) : 30;
|
|
13140
|
+
const auto = args.includes('--squeeze');
|
|
13141
|
+
const interactive = !!(process.stdin.isTTY && process.stderr.isTTY) && !args.includes('--json');
|
|
13142
|
+
let accept = false;
|
|
13143
|
+
if (auto) {
|
|
13144
|
+
accept = true;
|
|
13145
|
+
} else if (interactive && shouldPrompt(sq.reduction, threshold)) {
|
|
13146
|
+
process.stderr.write('\n' + formatSummary(sq) + '\n\n');
|
|
13147
|
+
process.stderr.write('Proceed with minimized input? [Y/n] ');
|
|
13148
|
+
const buf = Buffer.alloc(8);
|
|
13149
|
+
try {
|
|
13150
|
+
const n = fs.readSync(0, buf, 0, 8, null);
|
|
13151
|
+
const ans = buf.toString('utf8', 0, n).trim().toLowerCase();
|
|
13152
|
+
accept = ans === '' || ans === 'y' || ans === 'yes';
|
|
13153
|
+
} catch (_) { accept = false; }
|
|
13154
|
+
}
|
|
13155
|
+
if (accept) {
|
|
13156
|
+
query = sq.squeezed;
|
|
13157
|
+
intent = detectIntent(query);
|
|
13158
|
+
intentWeights = getIntentWeights(intent);
|
|
13159
|
+
squeezeAccepted = true;
|
|
13160
|
+
}
|
|
13161
|
+
}
|
|
13162
|
+
} catch (_) { /* squeeze is best-effort — never break ask */ }
|
|
13163
|
+
}
|
|
13164
|
+
|
|
12186
13165
|
let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });
|
|
12187
13166
|
|
|
12188
13167
|
// v6.10: Workspace scoping — infer package from query and apply boost
|
|
@@ -12282,6 +13261,15 @@ function main() {
|
|
|
12282
13261
|
bar,
|
|
12283
13262
|
].join('\n'));
|
|
12284
13263
|
}
|
|
13264
|
+
// v7.0.0: record the run and show the one-time star nudge (interactive only).
|
|
13265
|
+
try {
|
|
13266
|
+
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
13267
|
+
const showNudge = !!process.stderr.isTTY && !args.includes('--json');
|
|
13268
|
+
checkStarNudge(cwd, true, {
|
|
13269
|
+
silent: !showNudge,
|
|
13270
|
+
bump: { squeezeOffered: squeezeOffered ? 1 : 0, squeezeAccepted: squeezeAccepted ? 1 : 0 },
|
|
13271
|
+
});
|
|
13272
|
+
} catch (_) {}
|
|
12285
13273
|
process.exit(0);
|
|
12286
13274
|
}
|
|
12287
13275
|
|
|
@@ -12290,9 +13278,8 @@ function main() {
|
|
|
12290
13278
|
const short = args.includes('--short');
|
|
12291
13279
|
let msg = '', diff = '';
|
|
12292
13280
|
try {
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
diff = execSync('git diff --cached --name-only', { cwd, timeout: 3000, encoding: 'utf8' });
|
|
13281
|
+
msg = __git(['log', '-1', '--format=%s'], { cwd, timeout: 3000 }).trim();
|
|
13282
|
+
diff = __git(['diff', '--cached', '--name-only'], { cwd, timeout: 3000 });
|
|
12296
13283
|
} catch (_) {}
|
|
12297
13284
|
|
|
12298
13285
|
let profile = 'default';
|
|
@@ -12313,13 +13300,15 @@ function main() {
|
|
|
12313
13300
|
|
|
12314
13301
|
// v4.2: `sigmap compare` — human-readable benchmark CLI
|
|
12315
13302
|
if (args[0] === 'compare') {
|
|
12316
|
-
const {
|
|
13303
|
+
const { execFileSync } = require('child_process');
|
|
12317
13304
|
console.log('[sigmap] Running comparison benchmark (this may take ~30s)...\n');
|
|
12318
13305
|
|
|
12319
13306
|
let raw = '';
|
|
12320
13307
|
try {
|
|
12321
|
-
|
|
12322
|
-
|
|
13308
|
+
// Shell-free: run the node binary directly with the script path as an argv.
|
|
13309
|
+
raw = execFileSync(
|
|
13310
|
+
process.execPath,
|
|
13311
|
+
[path.join(__dirname, 'scripts', 'run-retrieval-benchmark.mjs'), '--compare'],
|
|
12323
13312
|
{ cwd, timeout: 90_000, encoding: 'utf8' }
|
|
12324
13313
|
);
|
|
12325
13314
|
} catch (e) { raw = (e && e.stdout) ? e.stdout : ''; }
|
|
@@ -12376,9 +13365,13 @@ function main() {
|
|
|
12376
13365
|
console.log(shareText);
|
|
12377
13366
|
|
|
12378
13367
|
try {
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
13368
|
+
// Shell-free: spawn the clipboard binary directly and write the text to its
|
|
13369
|
+
// stdin — no shell, no pipe, no string-built command.
|
|
13370
|
+
const { execFileSync } = require('child_process');
|
|
13371
|
+
const [clipBin, clipArgs] = process.platform === 'darwin'
|
|
13372
|
+
? ['pbcopy', []]
|
|
13373
|
+
: ['xclip', ['-selection', 'clipboard']];
|
|
13374
|
+
execFileSync(clipBin, clipArgs, { input: shareText, timeout: 2000 });
|
|
12382
13375
|
console.log('\n[sigmap] Copied to clipboard.');
|
|
12383
13376
|
} catch (_) {}
|
|
12384
13377
|
|
|
@@ -12959,16 +13952,16 @@ function main() {
|
|
|
12959
13952
|
// `sigmap status` — environment / repo state at a glance.
|
|
12960
13953
|
if (args[0] === 'status') {
|
|
12961
13954
|
const jsonOut = args.includes('--json');
|
|
12962
|
-
const gitOpts = { cwd
|
|
13955
|
+
const gitOpts = { cwd };
|
|
12963
13956
|
const st = { branch: null, dirty: 0, lastIndex: null, indexVersion: null, indexFiles: null, changedSinceIndex: null, notes: 0, lastNote: null };
|
|
12964
13957
|
|
|
12965
|
-
|
|
13958
|
+
st.branch = __tryGit(['rev-parse', '--abbrev-ref', 'HEAD'], gitOpts) || null;
|
|
12966
13959
|
// Fallback for an unborn branch (fresh repo, no commits yet).
|
|
12967
13960
|
if (!st.branch || st.branch === 'HEAD') {
|
|
12968
|
-
|
|
13961
|
+
st.branch = __tryGit(['symbolic-ref', '--short', 'HEAD'], gitOpts) || st.branch;
|
|
12969
13962
|
}
|
|
12970
13963
|
try {
|
|
12971
|
-
const porcelain =
|
|
13964
|
+
const porcelain = __git(['status', '--porcelain'], gitOpts).trim();
|
|
12972
13965
|
st.dirty = porcelain ? porcelain.split('\n').filter(Boolean).length : 0;
|
|
12973
13966
|
} catch (_) {}
|
|
12974
13967
|
|
|
@@ -12987,7 +13980,7 @@ function main() {
|
|
|
12987
13980
|
if (st.lastIndex) {
|
|
12988
13981
|
try {
|
|
12989
13982
|
const since = Date.parse(st.lastIndex);
|
|
12990
|
-
const tracked =
|
|
13983
|
+
const tracked = __git(['ls-files'], gitOpts).split('\n').filter(Boolean);
|
|
12991
13984
|
let changed = 0;
|
|
12992
13985
|
for (const f of tracked.slice(0, 5000)) {
|
|
12993
13986
|
try { if (fs.statSync(path.join(cwd, f)).mtimeMs > since) changed++; } catch (_) {}
|
|
@@ -13037,6 +14030,49 @@ function main() {
|
|
|
13037
14030
|
process.exit(0);
|
|
13038
14031
|
}
|
|
13039
14032
|
|
|
14033
|
+
// v7.0.0: `sigmap squeeze <file|->` — minimize a pasted stacktrace / CI-log / JSON blob
|
|
14034
|
+
if (args[0] === 'squeeze') {
|
|
14035
|
+
const jsonOut = args.includes('--json');
|
|
14036
|
+
const target = args[1] && !args[1].startsWith('--') ? args[1] : '-';
|
|
14037
|
+
let input = '';
|
|
14038
|
+
try {
|
|
14039
|
+
input = fs.readFileSync(target === '-' ? 0 : path.resolve(cwd, target), 'utf8');
|
|
14040
|
+
} catch (e) {
|
|
14041
|
+
console.error(`[sigmap] cannot read input: ${e.message}`);
|
|
14042
|
+
process.exit(1);
|
|
14043
|
+
}
|
|
14044
|
+
|
|
14045
|
+
const { squeeze: runSqueeze, formatSummary } = requireSourceOrBundled('./src/squeeze/index');
|
|
14046
|
+
let symbolIndex = null;
|
|
14047
|
+
try {
|
|
14048
|
+
const { buildSigIndex } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
14049
|
+
symbolIndex = buildSigIndex(cwd);
|
|
14050
|
+
} catch (_) {}
|
|
14051
|
+
const sq = runSqueeze(input, { srcDirs: config.srcDirs, symbolIndex });
|
|
14052
|
+
|
|
14053
|
+
try {
|
|
14054
|
+
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
14055
|
+
checkStarNudge(cwd, true, { silent: !process.stderr.isTTY || jsonOut, bump: { squeezeOffered: sq.applies ? 1 : 0 } });
|
|
14056
|
+
} catch (_) {}
|
|
14057
|
+
|
|
14058
|
+
if (jsonOut) {
|
|
14059
|
+
process.stdout.write(JSON.stringify({
|
|
14060
|
+
category: sq.category, confidence: sq.confidence,
|
|
14061
|
+
rawTokens: sq.rawTokens, squeezedTokens: sq.squeezedTokens,
|
|
14062
|
+
reduction: sq.reduction, enriched: sq.enriched, squeezed: sq.squeezed,
|
|
14063
|
+
}) + '\n');
|
|
14064
|
+
process.exit(0);
|
|
14065
|
+
}
|
|
14066
|
+
if (!sq.category) {
|
|
14067
|
+
process.stderr.write('[sigmap] no squeezable structure detected — input unchanged\n');
|
|
14068
|
+
process.stdout.write(input);
|
|
14069
|
+
process.exit(0);
|
|
14070
|
+
}
|
|
14071
|
+
process.stderr.write(formatSummary(sq) + '\n\n');
|
|
14072
|
+
process.stdout.write(sq.squeezed + '\n');
|
|
14073
|
+
process.exit(0);
|
|
14074
|
+
}
|
|
14075
|
+
|
|
13040
14076
|
// `sigmap verify-ai-output <answer.md>` — Hallucination Guard (deterministic core)
|
|
13041
14077
|
if (args[0] === 'verify-ai-output') {
|
|
13042
14078
|
const target = args[1];
|