sigmap 6.15.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +798 -401
- package/CHANGELOG.md +22 -0
- package/README.md +8 -8
- package/gen-context.js +1439 -400
- package/llms-full.txt +295 -0
- package/llms.txt +56 -0
- package/package.json +30 -13
- 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/extractors/prdiff.js +28 -3
- package/src/format/usage-guidance.js +28 -0
- package/src/mcp/server.js +1 -1
- package/src/nudge.js +97 -0
- 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/gen-context.js
CHANGED
|
@@ -3164,55 +3164,78 @@ __factories["./src/extractors/coverage"] = function(module, exports) {
|
|
|
3164
3164
|
|
|
3165
3165
|
// ── ./src/extractors/prdiff ──
|
|
3166
3166
|
__factories["./src/extractors/prdiff"] = function(module, exports) {
|
|
3167
|
+
'use strict';
|
|
3167
3168
|
|
|
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 = [];
|
|
3169
|
+
/**
|
|
3170
|
+
* Compare signature arrays and produce compact diff markers.
|
|
3171
|
+
* @param {string[]} baseSigs
|
|
3172
|
+
* @param {string[]} currentSigs
|
|
3173
|
+
* @returns {{added:string[], removed:string[], modified:string[]}}
|
|
3174
|
+
*/
|
|
3175
|
+
function diffSignatures(baseSigs, currentSigs) {
|
|
3176
|
+
const base = new Set(baseSigs || []);
|
|
3177
|
+
const curr = new Set(currentSigs || []);
|
|
3178
|
+
|
|
3179
|
+
const added = [...curr].filter((s) => !base.has(s));
|
|
3180
|
+
const removed = [...base].filter((s) => !curr.has(s));
|
|
3181
|
+
|
|
3182
|
+
const byName = (arr) => {
|
|
3183
|
+
const m = new Map();
|
|
3184
|
+
for (const s of arr) {
|
|
3185
|
+
const n = extractName(s);
|
|
3186
|
+
if (!n) continue;
|
|
3187
|
+
if (!m.has(n)) m.set(n, []);
|
|
3188
|
+
m.get(n).push(s);
|
|
3189
|
+
}
|
|
3190
|
+
return m;
|
|
3191
|
+
};
|
|
3197
3192
|
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3193
|
+
const aBy = byName(added);
|
|
3194
|
+
const rBy = byName(removed);
|
|
3195
|
+
const modified = [];
|
|
3201
3196
|
|
|
3202
|
-
|
|
3197
|
+
for (const [name] of aBy) {
|
|
3198
|
+
if (rBy.has(name)) modified.push(name);
|
|
3203
3199
|
}
|
|
3204
3200
|
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3201
|
+
return { added, removed, modified };
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
/**
|
|
3205
|
+
* Extract the declared symbol name from a signature line.
|
|
3206
|
+
*
|
|
3207
|
+
* Robust to the real forms SigMap emits — `export class X`, `const x = () =>`,
|
|
3208
|
+
* `async function x`, members, a trailing `:start-end` anchor, and `→ return`
|
|
3209
|
+
* suffixes. Anchored so it never returns a mid-string fragment (the old regex
|
|
3210
|
+
* could turn a signature into a 2-char name like `is`), and returns '' for
|
|
3211
|
+
* non-symbol lines (`module.exports = {…}`, markdown headers) instead of guessing.
|
|
3212
|
+
*/
|
|
3213
|
+
function extractName(sig) {
|
|
3214
|
+
if (!sig) return '';
|
|
3215
|
+
// Drop a trailing `:start-end` (or `:line`) line anchor.
|
|
3216
|
+
let t = String(sig).replace(/\s*:\d+(?:-\d+)?\s*$/, '').trim();
|
|
3217
|
+
// Re-export / barrel lines carry no single declared name.
|
|
3218
|
+
if (/^(?:module\.)?exports\b/.test(t) || /^export\s*\{/.test(t) || /^export\s+\*/.test(t)) return '';
|
|
3219
|
+
// Strip leading modifiers so the keyword/name is at the start.
|
|
3220
|
+
t = t.replace(/^export\s+/, '')
|
|
3221
|
+
.replace(/^default\s+/, '')
|
|
3222
|
+
.replace(/^(?:public|private|protected|static|abstract|final|override|readonly)\s+/g, '')
|
|
3223
|
+
.replace(/^async\s+/, '');
|
|
3224
|
+
let m;
|
|
3225
|
+
// Declared forms: function/def/func/fn/class/interface/trait/struct/enum/record/type <name>
|
|
3226
|
+
if ((m = t.match(/^(?:def|function|func|fn|class|interface|trait|struct|enum|record|type)\s+([A-Za-z_$][\w$]*)/))) return m[1];
|
|
3227
|
+
// const/let/var/val <name> = … (arrow functions, assigned values)
|
|
3228
|
+
if ((m = t.match(/^(?:const|let|var|val)\s+([A-Za-z_$][\w$]*)/))) return m[1];
|
|
3229
|
+
// Call / method form: <name>(…)
|
|
3230
|
+
if ((m = t.match(/^([A-Za-z_$][\w$]*)\s*\(/))) return m[1];
|
|
3231
|
+
// Lone identifier (e.g. a collapsed `symbol` after the anchor was stripped).
|
|
3232
|
+
if ((m = t.match(/^([A-Za-z_$][\w$]*)$/))) return m[1];
|
|
3233
|
+
return '';
|
|
3234
|
+
}
|
|
3211
3235
|
|
|
3212
|
-
|
|
3236
|
+
module.exports = { diffSignatures, extractName };
|
|
3213
3237
|
|
|
3214
3238
|
};
|
|
3215
|
-
|
|
3216
3239
|
// ── ./src/extractors/r ──
|
|
3217
3240
|
__factories["./src/extractors/r"] = function(module, exports) {
|
|
3218
3241
|
|
|
@@ -6238,7 +6261,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
|
|
|
6238
6261
|
|
|
6239
6262
|
const SERVER_INFO = {
|
|
6240
6263
|
name: 'sigmap',
|
|
6241
|
-
version: '
|
|
6264
|
+
version: '7.0.0',
|
|
6242
6265
|
description: 'SigMap MCP server — code signatures on demand',
|
|
6243
6266
|
};
|
|
6244
6267
|
|
|
@@ -7988,341 +8011,648 @@ __factories["./src/eval/usefulness-scorer"] = function(module, exports) {
|
|
|
7988
8011
|
|
|
7989
8012
|
// ── ./packages/adapters/copilot (bundled) ──
|
|
7990
8013
|
__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
|
-
|
|
8014
|
+
'use strict';
|
|
8015
|
+
|
|
8016
|
+
/**
|
|
8017
|
+
* Copilot adapter — writes to .github/copilot-instructions.md
|
|
8018
|
+
* GitHub Copilot reads this file automatically in every workspace.
|
|
8019
|
+
*
|
|
8020
|
+
* Contract:
|
|
8021
|
+
* format(context, opts?) → string
|
|
8022
|
+
* outputPath(cwd) → string
|
|
8023
|
+
*/
|
|
8024
|
+
|
|
8025
|
+
const path = require('path');
|
|
8026
|
+
const fs = require('fs');
|
|
8027
|
+
|
|
8028
|
+
const name = 'copilot';
|
|
8029
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8030
|
+
|
|
8031
|
+
/**
|
|
8032
|
+
* Format context for GitHub Copilot instructions.
|
|
8033
|
+
* @param {string} context - Raw signature context string
|
|
8034
|
+
* @param {object} [opts]
|
|
8035
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8036
|
+
* @returns {string}
|
|
8037
|
+
*/
|
|
8038
|
+
function format(context, opts = {}) {
|
|
8039
|
+
if (!context || typeof context !== 'string') return '';
|
|
8040
|
+
const version = opts.version || 'unknown';
|
|
8041
|
+
const timestamp = new Date().toISOString();
|
|
8042
|
+
const meta = _confidenceMeta(opts);
|
|
8043
|
+
const header = [
|
|
8044
|
+
`<!-- Generated by SigMap gen-context.js v${version} -->`,
|
|
8045
|
+
`<!-- Updated: ${timestamp} -->`,
|
|
8046
|
+
meta,
|
|
8047
|
+
`<!-- Do not edit below — regenerate with: node gen-context.js -->`,
|
|
8048
|
+
'',
|
|
8049
|
+
'# Code signatures',
|
|
8050
|
+
'',
|
|
8051
|
+
].join('\n');
|
|
8052
|
+
return header + context;
|
|
8053
|
+
}
|
|
8054
|
+
|
|
8055
|
+
function _confidenceMeta(opts) {
|
|
8056
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8057
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8058
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8059
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8060
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8061
|
+
return `<!-- sigmap: ${parts.join(' ')} -->`;
|
|
8062
|
+
}
|
|
8063
|
+
|
|
8064
|
+
/**
|
|
8065
|
+
* Return the output file path for this adapter.
|
|
8066
|
+
* @param {string} cwd - Project root
|
|
8067
|
+
* @returns {string}
|
|
8068
|
+
*/
|
|
8069
|
+
function outputPath(cwd) {
|
|
8070
|
+
return path.join(cwd, '.github', 'copilot-instructions.md');
|
|
8071
|
+
}
|
|
8072
|
+
|
|
8073
|
+
/**
|
|
8074
|
+
* Write signatures into copilot-instructions.md using append-under-marker.
|
|
8075
|
+
* If marker exists, content above marker is preserved.
|
|
8076
|
+
* If legacy generated content exists without marker, replace it cleanly.
|
|
8077
|
+
* @param {string} context - Raw signature context string
|
|
8078
|
+
* @param {string} cwd - Project root
|
|
8079
|
+
* @param {object} [opts]
|
|
8080
|
+
*/
|
|
8081
|
+
function write(context, cwd, opts = {}) {
|
|
8082
|
+
const filePath = outputPath(cwd);
|
|
8083
|
+
let existing = '';
|
|
8084
|
+
if (fs.existsSync(filePath)) {
|
|
8085
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8016
8086
|
}
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
const
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8087
|
+
|
|
8088
|
+
const formatted = format(context, opts);
|
|
8089
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8090
|
+
|
|
8091
|
+
let newContent;
|
|
8092
|
+
if (markerIdx !== -1) {
|
|
8093
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8094
|
+
} else {
|
|
8095
|
+
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js')
|
|
8096
|
+
|| existing.includes('# Code signatures');
|
|
8097
|
+
newContent = isLegacyGenerated
|
|
8098
|
+
? MARKER.trimStart() + formatted
|
|
8099
|
+
: existing + MARKER + formatted;
|
|
8030
8100
|
}
|
|
8031
|
-
module.exports = { name, format, outputPath, write };
|
|
8032
|
-
};
|
|
8033
8101
|
|
|
8102
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8103
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8104
|
+
}
|
|
8105
|
+
|
|
8106
|
+
module.exports = { name, format, outputPath, write };
|
|
8107
|
+
|
|
8108
|
+
};
|
|
8034
8109
|
// ── ./packages/adapters/claude (bundled) ──
|
|
8035
8110
|
__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
|
-
|
|
8111
|
+
'use strict';
|
|
8112
|
+
|
|
8113
|
+
/**
|
|
8114
|
+
* Claude adapter — appends to CLAUDE.md under a marker line.
|
|
8115
|
+
* Never overwrites human-written content above the marker.
|
|
8116
|
+
*
|
|
8117
|
+
* Contract:
|
|
8118
|
+
* format(context, opts?) → string
|
|
8119
|
+
* outputPath(cwd) → string
|
|
8120
|
+
* write(context, cwd, opts?) → void (handles append logic)
|
|
8121
|
+
*/
|
|
8122
|
+
|
|
8123
|
+
const path = require('path');
|
|
8124
|
+
const fs = require('fs');
|
|
8125
|
+
|
|
8126
|
+
const name = 'claude';
|
|
8127
|
+
|
|
8128
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8129
|
+
const ALLOWLIST_MARKER = '<!-- sigmap-bash-allowlist -->';
|
|
8130
|
+
|
|
8131
|
+
const ALLOWLIST_BLOCK = [
|
|
8132
|
+
'## Bash allowlist',
|
|
8133
|
+
'',
|
|
8134
|
+
ALLOWLIST_MARKER,
|
|
8135
|
+
'',
|
|
8136
|
+
'The following sigmap commands are pre-approved — Claude Code will not prompt for confirmation:',
|
|
8137
|
+
'',
|
|
8138
|
+
'```json',
|
|
8139
|
+
JSON.stringify({
|
|
8140
|
+
permissions: {
|
|
8141
|
+
allow: [
|
|
8142
|
+
'Bash(sigmap ask*)',
|
|
8143
|
+
'Bash(sigmap validate*)',
|
|
8144
|
+
'Bash(sigmap judge*)',
|
|
8145
|
+
'Bash(sigmap weights*)',
|
|
8146
|
+
'Bash(sigmap history*)',
|
|
8147
|
+
'Bash(sigmap --query*)',
|
|
8148
|
+
'Bash(sigmap --diff*)',
|
|
8149
|
+
'Bash(sigmap --health*)',
|
|
8150
|
+
'Bash(sigmap --coverage*)',
|
|
8151
|
+
'Bash(node gen-context.js*)',
|
|
8152
|
+
],
|
|
8153
|
+
},
|
|
8154
|
+
}, null, 2),
|
|
8155
|
+
'```',
|
|
8156
|
+
'',
|
|
8157
|
+
'Add the `permissions.allow` array above to `.claude/settings.json` to activate.',
|
|
8158
|
+
'',
|
|
8159
|
+
].join('\n');
|
|
8160
|
+
|
|
8161
|
+
/**
|
|
8162
|
+
* Format context suited for CLAUDE.md.
|
|
8163
|
+
* @param {string} context - Raw signature context string
|
|
8164
|
+
* @param {object} [opts]
|
|
8165
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8166
|
+
* @returns {string}
|
|
8167
|
+
*/
|
|
8168
|
+
function format(context, opts = {}) {
|
|
8169
|
+
if (!context || typeof context !== 'string') return '';
|
|
8170
|
+
const version = opts.version || 'unknown';
|
|
8171
|
+
const timestamp = new Date().toISOString();
|
|
8172
|
+
const meta = _confidenceMeta(opts);
|
|
8173
|
+
return [
|
|
8174
|
+
`<!-- Generated by SigMap v${version} — ${timestamp} -->`,
|
|
8175
|
+
meta,
|
|
8068
8176
|
'',
|
|
8177
|
+
context,
|
|
8069
8178
|
].join('\n');
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8179
|
+
}
|
|
8180
|
+
|
|
8181
|
+
function _confidenceMeta(opts) {
|
|
8182
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8183
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8184
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8185
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8186
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8187
|
+
return `<!-- sigmap: ${parts.join(' ')} -->`;
|
|
8188
|
+
}
|
|
8189
|
+
|
|
8190
|
+
/**
|
|
8191
|
+
* Return the output file path for this adapter.
|
|
8192
|
+
* @param {string} cwd - Project root
|
|
8193
|
+
* @returns {string}
|
|
8194
|
+
*/
|
|
8195
|
+
function outputPath(cwd) {
|
|
8196
|
+
return path.join(cwd, 'CLAUDE.md');
|
|
8197
|
+
}
|
|
8198
|
+
|
|
8199
|
+
/**
|
|
8200
|
+
* Write signatures into CLAUDE.md using the append-under-marker strategy.
|
|
8201
|
+
* Human content above the marker is never touched.
|
|
8202
|
+
* @param {string} context - Raw signature context string
|
|
8203
|
+
* @param {string} cwd - Project root
|
|
8204
|
+
* @param {object} [opts]
|
|
8205
|
+
*/
|
|
8206
|
+
function write(context, cwd, opts = {}) {
|
|
8207
|
+
const filePath = outputPath(cwd);
|
|
8208
|
+
let existing = '';
|
|
8209
|
+
if (fs.existsSync(filePath)) {
|
|
8210
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8085
8211
|
}
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
}
|
|
8212
|
+
const formatted = format(context, opts);
|
|
8213
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8214
|
+
let newContent;
|
|
8215
|
+
if (markerIdx !== -1) {
|
|
8216
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8217
|
+
} else {
|
|
8218
|
+
newContent = existing + MARKER + formatted;
|
|
8219
|
+
}
|
|
8220
|
+
|
|
8221
|
+
// Inject ## Bash allowlist above the sig marker if not already present
|
|
8222
|
+
if (!newContent.includes(ALLOWLIST_MARKER)) {
|
|
8223
|
+
const sigMarkerPos = newContent.indexOf('## Auto-generated signatures');
|
|
8224
|
+
if (sigMarkerPos !== -1) {
|
|
8225
|
+
newContent = newContent.slice(0, sigMarkerPos) + ALLOWLIST_BLOCK + '\n' + newContent.slice(sigMarkerPos);
|
|
8226
|
+
} else {
|
|
8227
|
+
newContent = ALLOWLIST_BLOCK + '\n' + newContent;
|
|
8103
8228
|
}
|
|
8104
|
-
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8105
8229
|
}
|
|
8106
|
-
module.exports = { name, format, outputPath, write };
|
|
8107
|
-
};
|
|
8108
8230
|
|
|
8231
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8232
|
+
}
|
|
8233
|
+
|
|
8234
|
+
module.exports = { name, format, outputPath, write };
|
|
8235
|
+
|
|
8236
|
+
};
|
|
8109
8237
|
// ── ./packages/adapters/cursor (bundled) ──
|
|
8110
8238
|
__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
|
-
}
|
|
8239
|
+
'use strict';
|
|
8240
|
+
|
|
8241
|
+
/**
|
|
8242
|
+
* Cursor adapter — writes to .cursorrules
|
|
8243
|
+
* Cursor reads .cursorrules automatically in every workspace.
|
|
8244
|
+
*
|
|
8245
|
+
* Contract:
|
|
8246
|
+
* format(context, opts?) → string
|
|
8247
|
+
* outputPath(cwd) → string
|
|
8248
|
+
*/
|
|
8249
|
+
|
|
8250
|
+
const path = require('path');
|
|
8251
|
+
|
|
8252
|
+
const name = 'cursor';
|
|
8253
|
+
|
|
8254
|
+
/**
|
|
8255
|
+
* Format context for Cursor rules file.
|
|
8256
|
+
* @param {string} context - Raw signature context string
|
|
8257
|
+
* @param {object} [opts]
|
|
8258
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8259
|
+
* @returns {string}
|
|
8260
|
+
*/
|
|
8261
|
+
function format(context, opts = {}) {
|
|
8262
|
+
if (!context || typeof context !== 'string') return '';
|
|
8263
|
+
const version = opts.version || 'unknown';
|
|
8264
|
+
const timestamp = new Date().toISOString();
|
|
8265
|
+
const meta = _confidenceMeta(opts);
|
|
8266
|
+
const header = [
|
|
8267
|
+
`# Code signatures — generated by SigMap v${version}`,
|
|
8268
|
+
`# Updated: ${timestamp}`,
|
|
8269
|
+
`# ${meta}`,
|
|
8270
|
+
`# Regenerate: node gen-context.js`,
|
|
8271
|
+
'',
|
|
8272
|
+
].join('\n');
|
|
8273
|
+
return header + context;
|
|
8274
|
+
}
|
|
8275
|
+
|
|
8276
|
+
function _confidenceMeta(opts) {
|
|
8277
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8278
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8279
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8280
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8281
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8282
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8283
|
+
}
|
|
8284
|
+
|
|
8285
|
+
/**
|
|
8286
|
+
* Return the output file path for this adapter.
|
|
8287
|
+
* @param {string} cwd - Project root
|
|
8288
|
+
* @returns {string}
|
|
8289
|
+
*/
|
|
8290
|
+
function outputPath(cwd) {
|
|
8291
|
+
return path.join(cwd, '.cursorrules');
|
|
8292
|
+
}
|
|
8293
|
+
|
|
8294
|
+
module.exports = { name, format, outputPath };
|
|
8132
8295
|
|
|
8296
|
+
};
|
|
8133
8297
|
// ── ./packages/adapters/windsurf (bundled) ──
|
|
8134
8298
|
__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
|
-
};
|
|
8299
|
+
'use strict';
|
|
8156
8300
|
|
|
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
|
-
};
|
|
8301
|
+
/**
|
|
8302
|
+
* Windsurf adapter — writes to .windsurfrules
|
|
8303
|
+
* Windsurf reads .windsurfrules automatically in every workspace.
|
|
8304
|
+
*
|
|
8305
|
+
* Contract:
|
|
8306
|
+
* format(context, opts?) → string
|
|
8307
|
+
* outputPath(cwd) → string
|
|
8308
|
+
*/
|
|
8182
8309
|
|
|
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
|
-
};
|
|
8310
|
+
const path = require('path');
|
|
8223
8311
|
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
], null, 2),
|
|
8244
|
-
'```',
|
|
8312
|
+
const name = 'windsurf';
|
|
8313
|
+
|
|
8314
|
+
/**
|
|
8315
|
+
* Format context for Windsurf rules file.
|
|
8316
|
+
* @param {string} context - Raw signature context string
|
|
8317
|
+
* @param {object} [opts]
|
|
8318
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8319
|
+
* @returns {string}
|
|
8320
|
+
*/
|
|
8321
|
+
function format(context, opts = {}) {
|
|
8322
|
+
if (!context || typeof context !== 'string') return '';
|
|
8323
|
+
const version = opts.version || 'unknown';
|
|
8324
|
+
const timestamp = new Date().toISOString();
|
|
8325
|
+
const meta = _confidenceMeta(opts);
|
|
8326
|
+
const header = [
|
|
8327
|
+
`# Code signatures — generated by SigMap v${version}`,
|
|
8328
|
+
`# Updated: ${timestamp}`,
|
|
8329
|
+
`# ${meta}`,
|
|
8330
|
+
`# Regenerate: node gen-context.js`,
|
|
8245
8331
|
'',
|
|
8246
8332
|
].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
|
-
};
|
|
8333
|
+
return header + context;
|
|
8334
|
+
}
|
|
8335
|
+
|
|
8336
|
+
function _confidenceMeta(opts) {
|
|
8337
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8338
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8339
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8340
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8341
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8342
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8343
|
+
}
|
|
8344
|
+
|
|
8345
|
+
/**
|
|
8346
|
+
* Return the output file path for this adapter.
|
|
8347
|
+
* @param {string} cwd - Project root
|
|
8348
|
+
* @returns {string}
|
|
8349
|
+
*/
|
|
8350
|
+
function outputPath(cwd) {
|
|
8351
|
+
return path.join(cwd, '.windsurfrules');
|
|
8352
|
+
}
|
|
8353
|
+
|
|
8354
|
+
module.exports = { name, format, outputPath };
|
|
8286
8355
|
|
|
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
8356
|
};
|
|
8357
|
+
// ── ./packages/adapters/openai (bundled) ──
|
|
8358
|
+
__factories["./packages/adapters/openai"] = function(module, exports) {
|
|
8359
|
+
'use strict';
|
|
8313
8360
|
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8361
|
+
/**
|
|
8362
|
+
* OpenAI adapter — formats context as an OpenAI system message.
|
|
8363
|
+
* Use the output as the `content` field of a system role message.
|
|
8364
|
+
*
|
|
8365
|
+
* Example usage in code:
|
|
8366
|
+
* const { format } = require('sigmap/adapters/openai');
|
|
8367
|
+
* const systemPrompt = format(context);
|
|
8368
|
+
* // Pass to: openai.chat.completions.create({ messages: [{ role: 'system', content: systemPrompt }] })
|
|
8369
|
+
*
|
|
8370
|
+
* Contract:
|
|
8371
|
+
* format(context, opts?) → string
|
|
8372
|
+
* outputPath(cwd) → string
|
|
8373
|
+
*/
|
|
8374
|
+
|
|
8375
|
+
const path = require('path');
|
|
8376
|
+
|
|
8377
|
+
const name = 'openai';
|
|
8378
|
+
|
|
8379
|
+
/**
|
|
8380
|
+
* Format context as an OpenAI system prompt.
|
|
8381
|
+
* @param {string} context - Raw signature context string
|
|
8382
|
+
* @param {object} [opts]
|
|
8383
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8384
|
+
* @param {string} [opts.projectName] - Optional project name
|
|
8385
|
+
* @returns {string}
|
|
8386
|
+
*/
|
|
8387
|
+
function format(context, opts = {}) {
|
|
8388
|
+
if (!context || typeof context !== 'string') return '';
|
|
8389
|
+
const version = opts.version || 'unknown';
|
|
8390
|
+
const timestamp = new Date().toISOString();
|
|
8391
|
+
const projectLine = opts.projectName
|
|
8392
|
+
? `Project: ${opts.projectName}\n`
|
|
8393
|
+
: '';
|
|
8394
|
+
|
|
8395
|
+
const meta = _confidenceMeta(opts);
|
|
8396
|
+
return [
|
|
8397
|
+
`You are a coding assistant with full knowledge of this codebase.`,
|
|
8398
|
+
`Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
|
|
8399
|
+
`<!-- ${meta} -->`,
|
|
8400
|
+
projectLine,
|
|
8401
|
+
`Use these signatures to answer questions about the code accurately.`,
|
|
8402
|
+
`When the user asks about a specific file or function, refer to the signatures below.`,
|
|
8403
|
+
`## Code Signatures`,
|
|
8404
|
+
``,
|
|
8405
|
+
context,
|
|
8406
|
+
].join('\n');
|
|
8407
|
+
}
|
|
8408
|
+
|
|
8409
|
+
/**
|
|
8410
|
+
* Return the output file path for this adapter.
|
|
8411
|
+
* Writes a .openai-context.md file that can be loaded at runtime.
|
|
8412
|
+
* @param {string} cwd - Project root
|
|
8413
|
+
* @returns {string}
|
|
8414
|
+
*/
|
|
8415
|
+
function outputPath(cwd) {
|
|
8416
|
+
return path.join(cwd, '.github', 'openai-context.md');
|
|
8417
|
+
}
|
|
8418
|
+
|
|
8419
|
+
function _confidenceMeta(opts) {
|
|
8420
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8421
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8422
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8423
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8424
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8425
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8426
|
+
}
|
|
8427
|
+
|
|
8428
|
+
module.exports = { name, format, outputPath };
|
|
8429
|
+
|
|
8430
|
+
};
|
|
8431
|
+
// ── ./packages/adapters/gemini (bundled) ──
|
|
8432
|
+
__factories["./packages/adapters/gemini"] = function(module, exports) {
|
|
8433
|
+
'use strict';
|
|
8434
|
+
|
|
8435
|
+
/**
|
|
8436
|
+
* Gemini adapter — formats context as a Gemini system instruction.
|
|
8437
|
+
* Use the output as the `system_instruction` field in a Gemini API request.
|
|
8438
|
+
*
|
|
8439
|
+
* Example usage:
|
|
8440
|
+
* const { format } = require('sigmap/adapters/gemini');
|
|
8441
|
+
* const instruction = format(context);
|
|
8442
|
+
* // Pass to: genAI.getGenerativeModel({ model: 'gemini-pro', systemInstruction: instruction })
|
|
8443
|
+
*
|
|
8444
|
+
* Contract:
|
|
8445
|
+
* format(context, opts?) → string
|
|
8446
|
+
* outputPath(cwd) → string
|
|
8447
|
+
*/
|
|
8448
|
+
|
|
8449
|
+
const path = require('path');
|
|
8450
|
+
const fs = require('fs');
|
|
8451
|
+
|
|
8452
|
+
const name = 'gemini';
|
|
8453
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8454
|
+
|
|
8455
|
+
/**
|
|
8456
|
+
* Format context as a Gemini system instruction.
|
|
8457
|
+
* @param {string} context - Raw signature context string
|
|
8458
|
+
* @param {object} [opts]
|
|
8459
|
+
* @param {string} [opts.version] - SigMap version string
|
|
8460
|
+
* @param {string} [opts.projectName] - Optional project name
|
|
8461
|
+
* @returns {string}
|
|
8462
|
+
*/
|
|
8463
|
+
function format(context, opts = {}) {
|
|
8464
|
+
if (!context || typeof context !== 'string') return '';
|
|
8465
|
+
const version = opts.version || 'unknown';
|
|
8466
|
+
const timestamp = new Date().toISOString();
|
|
8467
|
+
const projectLine = opts.projectName
|
|
8468
|
+
? `Project: ${opts.projectName}\n`
|
|
8469
|
+
: '';
|
|
8470
|
+
|
|
8471
|
+
const meta = _confidenceMeta(opts);
|
|
8472
|
+
return [
|
|
8473
|
+
`You are a coding assistant with complete knowledge of this codebase.`,
|
|
8474
|
+
`The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
|
|
8475
|
+
`<!-- ${meta} -->`,
|
|
8476
|
+
projectLine,
|
|
8477
|
+
`These signatures represent every public function, class, and type in the project.`,
|
|
8478
|
+
`Refer to them when answering questions about code structure, APIs, and implementation.`,
|
|
8479
|
+
`## Code Signatures`,
|
|
8480
|
+
``,
|
|
8481
|
+
context,
|
|
8482
|
+
].join('\n');
|
|
8483
|
+
}
|
|
8484
|
+
|
|
8485
|
+
/**
|
|
8486
|
+
* Return the output file path for this adapter.
|
|
8487
|
+
* @param {string} cwd - Project root
|
|
8488
|
+
* @returns {string}
|
|
8489
|
+
*/
|
|
8490
|
+
function outputPath(cwd) {
|
|
8491
|
+
return path.join(cwd, '.github', 'gemini-context.md');
|
|
8492
|
+
}
|
|
8493
|
+
|
|
8494
|
+
/**
|
|
8495
|
+
* Write signatures into gemini-context.md using append-under-marker.
|
|
8496
|
+
* If marker exists, content above marker is preserved.
|
|
8497
|
+
* If legacy generated content exists without marker, replace it cleanly.
|
|
8498
|
+
* @param {string} context - Raw signature context string
|
|
8499
|
+
* @param {string} cwd - Project root
|
|
8500
|
+
* @param {object} [opts]
|
|
8501
|
+
*/
|
|
8502
|
+
function write(context, cwd, opts = {}) {
|
|
8503
|
+
const filePath = outputPath(cwd);
|
|
8504
|
+
let existing = '';
|
|
8505
|
+
if (fs.existsSync(filePath)) {
|
|
8506
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8507
|
+
}
|
|
8508
|
+
|
|
8509
|
+
const formatted = format(context, opts);
|
|
8510
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8511
|
+
|
|
8512
|
+
let newContent;
|
|
8513
|
+
if (markerIdx !== -1) {
|
|
8514
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8515
|
+
} else {
|
|
8516
|
+
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js')
|
|
8517
|
+
|| existing.includes('## Code Signatures');
|
|
8518
|
+
newContent = isLegacyGenerated
|
|
8519
|
+
? MARKER.trimStart() + formatted
|
|
8520
|
+
: existing + MARKER + formatted;
|
|
8521
|
+
}
|
|
8522
|
+
|
|
8523
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8524
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8525
|
+
}
|
|
8526
|
+
|
|
8527
|
+
function _confidenceMeta(opts) {
|
|
8528
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
8529
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
8530
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
8531
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
8532
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
8533
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
8534
|
+
}
|
|
8535
|
+
|
|
8536
|
+
module.exports = { name, format, outputPath, write };
|
|
8537
|
+
|
|
8538
|
+
};
|
|
8539
|
+
// ── ./packages/adapters/codex (bundled) ──
|
|
8540
|
+
__factories["./packages/adapters/codex"] = function(module, exports) {
|
|
8541
|
+
'use strict';
|
|
8542
|
+
|
|
8543
|
+
/**
|
|
8544
|
+
* Codex adapter — writes OpenAI-style context to AGENTS.md.
|
|
8545
|
+
*
|
|
8546
|
+
* This adapter reuses the same prompt format as the OpenAI adapter,
|
|
8547
|
+
* but targets AGENTS.md so Codex-style agents can read repository guidance.
|
|
8548
|
+
*
|
|
8549
|
+
* Contract:
|
|
8550
|
+
* format(context, opts?) → string
|
|
8551
|
+
* outputPath(cwd) → string
|
|
8552
|
+
* write(context, cwd, opts?) → void
|
|
8553
|
+
*/
|
|
8554
|
+
|
|
8555
|
+
const path = require('path');
|
|
8556
|
+
const fs = require('fs');
|
|
8557
|
+
|
|
8558
|
+
const name = 'codex';
|
|
8559
|
+
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8560
|
+
|
|
8561
|
+
/**
|
|
8562
|
+
* Format context for AGENTS.md — clean markdown, no LLM preamble.
|
|
8563
|
+
* @param {string} context - Raw signature context string
|
|
8564
|
+
* @param {object} [opts]
|
|
8565
|
+
* @returns {string}
|
|
8566
|
+
*/
|
|
8567
|
+
function format(context, opts = {}) {
|
|
8568
|
+
if (!context || typeof context !== 'string' || !context.trim()) return '';
|
|
8569
|
+
return `# Code signatures\n\n${context}`;
|
|
8570
|
+
}
|
|
8571
|
+
|
|
8572
|
+
/**
|
|
8573
|
+
* Return the output file path for this adapter.
|
|
8574
|
+
* @param {string} cwd - Project root
|
|
8575
|
+
* @returns {string}
|
|
8576
|
+
*/
|
|
8577
|
+
function outputPath(cwd) {
|
|
8578
|
+
return path.join(cwd, 'AGENTS.md');
|
|
8579
|
+
}
|
|
8580
|
+
|
|
8581
|
+
/**
|
|
8582
|
+
* Write signatures into AGENTS.md using append-under-marker.
|
|
8583
|
+
* If marker exists, content above marker is preserved.
|
|
8584
|
+
* If legacy generated content exists without marker, replace it cleanly.
|
|
8585
|
+
* @param {string} context - Raw signature context string
|
|
8586
|
+
* @param {string} cwd - Project root
|
|
8587
|
+
* @param {object} [opts]
|
|
8588
|
+
*/
|
|
8589
|
+
function write(context, cwd, opts = {}) {
|
|
8590
|
+
const filePath = outputPath(cwd);
|
|
8591
|
+
let existing = '';
|
|
8592
|
+
if (fs.existsSync(filePath)) {
|
|
8593
|
+
existing = fs.readFileSync(filePath, 'utf8');
|
|
8594
|
+
}
|
|
8595
|
+
|
|
8596
|
+
const formatted = format(context, opts);
|
|
8597
|
+
const markerIdx = existing.indexOf('## Auto-generated signatures');
|
|
8598
|
+
|
|
8599
|
+
let newContent;
|
|
8600
|
+
if (markerIdx !== -1) {
|
|
8601
|
+
newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
|
|
8602
|
+
} else {
|
|
8603
|
+
const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js')
|
|
8604
|
+
|| existing.includes('## Code Signatures')
|
|
8605
|
+
|| existing.includes('# Code signatures');
|
|
8606
|
+
newContent = isLegacyGenerated
|
|
8607
|
+
? MARKER.trimStart() + formatted
|
|
8608
|
+
: existing + MARKER + formatted;
|
|
8609
|
+
}
|
|
8610
|
+
|
|
8611
|
+
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
8612
|
+
}
|
|
8613
|
+
|
|
8614
|
+
module.exports = { name, format, outputPath, write };
|
|
8615
|
+
|
|
8616
|
+
};
|
|
8617
|
+
// ── ./packages/adapters/index (bundled) ──
|
|
8618
|
+
__factories["./packages/adapters/index"] = function(module, exports) {
|
|
8619
|
+
const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
|
|
8620
|
+
const _adapterCache = {};
|
|
8621
|
+
function getAdapter(name) {
|
|
8622
|
+
if (!name || typeof name !== 'string') return null;
|
|
8623
|
+
const key = name.toLowerCase();
|
|
8624
|
+
if (!ADAPTER_NAMES.includes(key)) return null;
|
|
8625
|
+
if (!_adapterCache[key]) {
|
|
8626
|
+
try { _adapterCache[key] = __require('./packages/adapters/' + key); } catch (_) { return null; }
|
|
8627
|
+
}
|
|
8628
|
+
return _adapterCache[key];
|
|
8629
|
+
}
|
|
8630
|
+
function listAdapters() { return ADAPTER_NAMES.slice(); }
|
|
8631
|
+
function adapt(context, adapterName, opts = {}) {
|
|
8632
|
+
if (!context || typeof context !== 'string') return '';
|
|
8633
|
+
const adapter = getAdapter(adapterName);
|
|
8634
|
+
if (!adapter || typeof adapter.format !== 'function') return '';
|
|
8635
|
+
try { return adapter.format(context, opts); } catch (_) { return ''; }
|
|
8636
|
+
}
|
|
8637
|
+
function outputsToAdapters(outputs) {
|
|
8638
|
+
if (!Array.isArray(outputs)) return ['copilot'];
|
|
8639
|
+
return outputs.map((o) => ADAPTER_NAMES.includes(o) ? o : o);
|
|
8640
|
+
}
|
|
8641
|
+
module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
|
|
8642
|
+
};
|
|
8643
|
+
|
|
8644
|
+
// ── ./src/extractors/generic ──
|
|
8645
|
+
__factories["./src/extractors/generic"] = function(module, exports) {
|
|
8646
|
+
'use strict';
|
|
8647
|
+
const PATTERNS = [
|
|
8648
|
+
/^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
|
|
8649
|
+
/^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
|
|
8650
|
+
/^def\s+\w+[\s(|:]/,
|
|
8651
|
+
/^(?:pub\s+)?func\s+\w+\s*\(/,
|
|
8652
|
+
/^(?:let|let\s+rec)\s+\w+\s*[=(]/,
|
|
8653
|
+
/^class\s+\w+/,
|
|
8654
|
+
/^(?:proc|sub|method)\s+\w+\s*\(/,
|
|
8655
|
+
];
|
|
8326
8656
|
function extract(src) {
|
|
8327
8657
|
if (!src || typeof src !== 'string') return [];
|
|
8328
8658
|
const results = [];
|
|
@@ -9180,6 +9510,610 @@ __factories["./src/workspace/detector"] = function(module, exports) {
|
|
|
9180
9510
|
* No npm install required. Node 18+ built-ins only.
|
|
9181
9511
|
*/
|
|
9182
9512
|
|
|
9513
|
+
// ── ./src/format/usage-guidance ──
|
|
9514
|
+
__factories["./src/format/usage-guidance"] = function(module, exports) {
|
|
9515
|
+
'use strict';
|
|
9516
|
+
|
|
9517
|
+
/**
|
|
9518
|
+
* Canonical "how to use SigMap" guidance block (v6.16/v7.0).
|
|
9519
|
+
*
|
|
9520
|
+
* Every adapter emits this one identical block so all generated context files
|
|
9521
|
+
* (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md, GEMINI.md, .cursorrules,
|
|
9522
|
+
* …) carry the same, single usage section — instead of each adapter inventing
|
|
9523
|
+
* its own wording (and codex emitting a redundant second JSON block).
|
|
9524
|
+
*/
|
|
9525
|
+
|
|
9526
|
+
function usageBlock() {
|
|
9527
|
+
return [
|
|
9528
|
+
'## SigMap commands',
|
|
9529
|
+
'',
|
|
9530
|
+
'| When | Command |',
|
|
9531
|
+
'|------|---------|',
|
|
9532
|
+
'| Before answering a question about code | `sigmap ask "<your question>"` |',
|
|
9533
|
+
'| To rank files by topic | `sigmap --query "<topic>"` |',
|
|
9534
|
+
'| After changing config or source dirs | `sigmap validate` |',
|
|
9535
|
+
'| To verify an AI answer is grounded | `sigmap judge --response <file>` |',
|
|
9536
|
+
'',
|
|
9537
|
+
'Always run `sigmap ask` (or `sigmap --query`) before searching for files relevant to a task.',
|
|
9538
|
+
'',
|
|
9539
|
+
].join('\n');
|
|
9540
|
+
}
|
|
9541
|
+
|
|
9542
|
+
module.exports = { usageBlock };
|
|
9543
|
+
|
|
9544
|
+
};
|
|
9545
|
+
|
|
9546
|
+
// ── ./src/squeeze/classify ──
|
|
9547
|
+
__factories["./src/squeeze/classify"] = function(module, exports) {
|
|
9548
|
+
'use strict';
|
|
9549
|
+
|
|
9550
|
+
/**
|
|
9551
|
+
* Squeeze input classifier (v7.0.0).
|
|
9552
|
+
*
|
|
9553
|
+
* Deterministic, zero-dep detector that labels a pasted blob as a
|
|
9554
|
+
* `stacktrace`, `cilog`, or `json` payload — or `null` (pass through). Pure
|
|
9555
|
+
* regex/heuristics; runs in well under 10ms even on large input.
|
|
9556
|
+
*
|
|
9557
|
+
* Order matters: stack traces are highest value and a CI log often *contains*
|
|
9558
|
+
* a trace, so stacktrace is checked first, then cilog, then json.
|
|
9559
|
+
*/
|
|
9560
|
+
|
|
9561
|
+
const FRAME_RE = [
|
|
9562
|
+
/^\s*at\s+.+\(.+:\d+:\d+\)\s*$/, // JS: at fn (file:line:col)
|
|
9563
|
+
/^\s*at\s+.+:\d+:\d+\s*$/, // JS: at file:line:col
|
|
9564
|
+
/^\s*at\s+[\w$.<>]+\(.+\.\w+:\d+\)/, // Java/Kotlin: at pkg.Cls.m(File.java:42)
|
|
9565
|
+
/^\s*File\s+".+",\s+line\s+\d+/, // Python frame
|
|
9566
|
+
/^\s+\w+.*\([^)]*\.(go|rs):\d+\)/, // Go/Rust frame with file:line
|
|
9567
|
+
/^\s*#\d+\s+0x[0-9a-f]+/, // native/gdb frame
|
|
9568
|
+
];
|
|
9569
|
+
|
|
9570
|
+
const STACK_HEADER_RE = [
|
|
9571
|
+
/Traceback \(most recent call last\)/,
|
|
9572
|
+
/Exception in thread/,
|
|
9573
|
+
/\bat Object\.<anonymous>\b/,
|
|
9574
|
+
/thread '.*' panicked at/,
|
|
9575
|
+
/^panic:/m,
|
|
9576
|
+
/goroutine \d+ \[/,
|
|
9577
|
+
];
|
|
9578
|
+
|
|
9579
|
+
function countFrames(lines) {
|
|
9580
|
+
let n = 0;
|
|
9581
|
+
for (const line of lines) {
|
|
9582
|
+
if (FRAME_RE.some((re) => re.test(line))) n++;
|
|
9583
|
+
}
|
|
9584
|
+
return n;
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9587
|
+
function matchesStackTrace(input, lines) {
|
|
9588
|
+
const frames = countFrames(lines);
|
|
9589
|
+
const header = STACK_HEADER_RE.some((re) => re.test(input));
|
|
9590
|
+
// A single explicit header (Traceback/panic) counts as strong evidence even
|
|
9591
|
+
// with few parsed frames; otherwise require 2+ frame-like lines.
|
|
9592
|
+
if (!header && frames < 2) return { match: false, confidence: 0, frames };
|
|
9593
|
+
// Confidence scales with frame count; a header alone floors it at 0.6.
|
|
9594
|
+
let confidence = Math.min(0.97, 0.15 * frames);
|
|
9595
|
+
if (header) confidence = Math.max(confidence, 0.6 + Math.min(0.3, 0.05 * frames));
|
|
9596
|
+
return { match: true, confidence: Number(confidence.toFixed(2)), frames };
|
|
9597
|
+
}
|
|
9598
|
+
|
|
9599
|
+
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})/;
|
|
9600
|
+
const PROGRESS_RE = /(\d{1,3}%)|(\bDownloading\b)|(\bnpm (WARN|notice|http)\b)|(##\[(group|endgroup|command)\])|(\[\d+\/\d+\])|(▕|█|━|⠿)|(\bETA\b)|(\r$)/;
|
|
9601
|
+
|
|
9602
|
+
function matchesCiLog(input, lines) {
|
|
9603
|
+
if (lines.length < 8) return { match: false, confidence: 0 };
|
|
9604
|
+
let logish = 0;
|
|
9605
|
+
const seen = new Map();
|
|
9606
|
+
let repeats = 0;
|
|
9607
|
+
for (const line of lines) {
|
|
9608
|
+
if (TS_RE.test(line) || PROGRESS_RE.test(line)) logish++;
|
|
9609
|
+
const norm = line.replace(/\d+/g, '#').trim();
|
|
9610
|
+
if (norm) {
|
|
9611
|
+
const c = (seen.get(norm) || 0) + 1;
|
|
9612
|
+
seen.set(norm, c);
|
|
9613
|
+
if (c > 1) repeats++;
|
|
9614
|
+
}
|
|
9615
|
+
}
|
|
9616
|
+
const density = logish / lines.length;
|
|
9617
|
+
const repeatRatio = repeats / lines.length;
|
|
9618
|
+
if (density < 0.4 && repeatRatio < 0.4) return { match: false, confidence: 0 };
|
|
9619
|
+
const confidence = Math.min(0.95, Math.max(density, repeatRatio) + 0.15);
|
|
9620
|
+
return { match: true, confidence: Number(confidence.toFixed(2)) };
|
|
9621
|
+
}
|
|
9622
|
+
|
|
9623
|
+
function matchesJsonPayload(input) {
|
|
9624
|
+
const trimmed = input.trim();
|
|
9625
|
+
if (/^[[{]/.test(trimmed)) {
|
|
9626
|
+
try {
|
|
9627
|
+
JSON.parse(trimmed);
|
|
9628
|
+
return { match: true, confidence: 0.95 };
|
|
9629
|
+
} catch (_) { /* not strict JSON — fall through to heuristic */ }
|
|
9630
|
+
}
|
|
9631
|
+
const lines = trimmed.split('\n');
|
|
9632
|
+
if (lines.length < 4) return { match: false, confidence: 0 };
|
|
9633
|
+
let kv = 0;
|
|
9634
|
+
for (const line of lines) {
|
|
9635
|
+
if (/^\s*"[^"]+"\s*:\s*.+/.test(line) || /^\s*[}\]],?\s*$/.test(line)) kv++;
|
|
9636
|
+
}
|
|
9637
|
+
const ratio = kv / lines.length;
|
|
9638
|
+
if (ratio < 0.6) return { match: false, confidence: 0 };
|
|
9639
|
+
return { match: true, confidence: Number(Math.min(0.9, ratio).toFixed(2)) };
|
|
9640
|
+
}
|
|
9641
|
+
|
|
9642
|
+
/**
|
|
9643
|
+
* @param {string} input
|
|
9644
|
+
* @returns {{ category: 'stacktrace'|'cilog'|'json'|null, confidence: number }}
|
|
9645
|
+
*/
|
|
9646
|
+
function classify(input) {
|
|
9647
|
+
if (typeof input !== 'string' || !input.trim()) return { category: null, confidence: 0 };
|
|
9648
|
+
const lines = input.split('\n');
|
|
9649
|
+
|
|
9650
|
+
const st = matchesStackTrace(input, lines);
|
|
9651
|
+
if (st.match) return { category: 'stacktrace', confidence: st.confidence };
|
|
9652
|
+
|
|
9653
|
+
const ci = matchesCiLog(input, lines);
|
|
9654
|
+
if (ci.match) return { category: 'cilog', confidence: ci.confidence };
|
|
9655
|
+
|
|
9656
|
+
const js = matchesJsonPayload(input);
|
|
9657
|
+
if (js.match) return { category: 'json', confidence: js.confidence };
|
|
9658
|
+
|
|
9659
|
+
return { category: null, confidence: 0 };
|
|
9660
|
+
}
|
|
9661
|
+
|
|
9662
|
+
module.exports = { classify, countFrames };
|
|
9663
|
+
|
|
9664
|
+
};
|
|
9665
|
+
|
|
9666
|
+
// ── ./src/squeeze/cilog ──
|
|
9667
|
+
__factories["./src/squeeze/cilog"] = function(module, exports) {
|
|
9668
|
+
'use strict';
|
|
9669
|
+
|
|
9670
|
+
/**
|
|
9671
|
+
* CI / build-log squeeze (v7.0.0).
|
|
9672
|
+
*
|
|
9673
|
+
* Strips timestamps, progress bars, and repeated noise; keeps every error line
|
|
9674
|
+
* plus a small context window around it. Never returns empty — when there are
|
|
9675
|
+
* no errors it falls back to a head/tail summary. Also reused by the stacktrace
|
|
9676
|
+
* squeezer to clean noise surrounding a trace.
|
|
9677
|
+
*/
|
|
9678
|
+
|
|
9679
|
+
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*)+/;
|
|
9680
|
+
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[: ]|█|━|▕|⣿)/;
|
|
9681
|
+
const ERROR_RE = /\b(?:error|err!|fail(?:ed|ure)?|exception|panic|fatal|traceback|ERR_|E[A-Z]{3,})\b|✗|❌/i;
|
|
9682
|
+
|
|
9683
|
+
/** Remove a leading timestamp prefix from a single line. */
|
|
9684
|
+
function stripTimestamp(line) {
|
|
9685
|
+
return line.replace(TS_PREFIX_RE, '');
|
|
9686
|
+
}
|
|
9687
|
+
|
|
9688
|
+
/**
|
|
9689
|
+
* @param {string} input
|
|
9690
|
+
* @param {object} [opts]
|
|
9691
|
+
* @param {number} [opts.context=2] context lines kept around each error
|
|
9692
|
+
* @returns {{ squeezed: string, kept: string[], stripped: string[] }}
|
|
9693
|
+
*/
|
|
9694
|
+
function squeezeCiLog(input, opts = {}) {
|
|
9695
|
+
const ctx = opts.context != null ? opts.context : 2;
|
|
9696
|
+
const lines = input.split('\n');
|
|
9697
|
+
const keep = new Set();
|
|
9698
|
+
const errorIdx = [];
|
|
9699
|
+
|
|
9700
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9701
|
+
if (ERROR_RE.test(lines[i])) {
|
|
9702
|
+
errorIdx.push(i);
|
|
9703
|
+
for (let j = Math.max(0, i - ctx); j <= Math.min(lines.length - 1, i + ctx); j++) keep.add(j);
|
|
9704
|
+
}
|
|
9705
|
+
}
|
|
9706
|
+
|
|
9707
|
+
let body;
|
|
9708
|
+
let keptDesc;
|
|
9709
|
+
if (errorIdx.length === 0) {
|
|
9710
|
+
const head = lines.slice(0, 10).map(stripTimestamp);
|
|
9711
|
+
const tail = lines.length > 20 ? lines.slice(-10).map(stripTimestamp) : [];
|
|
9712
|
+
body = head.slice();
|
|
9713
|
+
if (tail.length) body.push(`… (${lines.length - 20} lines omitted) …`, ...tail);
|
|
9714
|
+
keptDesc = ['head/tail summary (no error lines found)'];
|
|
9715
|
+
} else {
|
|
9716
|
+
const sorted = [...keep].sort((a, b) => a - b);
|
|
9717
|
+
body = [];
|
|
9718
|
+
let prev = -2;
|
|
9719
|
+
for (const idx of sorted) {
|
|
9720
|
+
// Drop pure progress/noise lines unless they are themselves errors.
|
|
9721
|
+
const line = stripTimestamp(lines[idx]);
|
|
9722
|
+
if (PROGRESS_LINE_RE.test(line) && !ERROR_RE.test(line)) continue;
|
|
9723
|
+
if (idx > prev + 1) body.push(`… (${idx - prev - 1} lines) …`);
|
|
9724
|
+
body.push(line);
|
|
9725
|
+
prev = idx;
|
|
9726
|
+
}
|
|
9727
|
+
if (body.length === 0) body = errorIdx.map((i) => stripTimestamp(lines[i])); // safety net
|
|
9728
|
+
keptDesc = [`${errorIdx.length} error line(s) + ${ctx}-line context`];
|
|
9729
|
+
}
|
|
9730
|
+
|
|
9731
|
+
return {
|
|
9732
|
+
squeezed: body.join('\n'),
|
|
9733
|
+
kept: keptDesc,
|
|
9734
|
+
stripped: [`${Math.max(0, lines.length - body.length)} timestamp/progress/noise line(s)`],
|
|
9735
|
+
};
|
|
9736
|
+
}
|
|
9737
|
+
|
|
9738
|
+
module.exports = { squeezeCiLog, stripTimestamp, ERROR_RE };
|
|
9739
|
+
|
|
9740
|
+
};
|
|
9741
|
+
|
|
9742
|
+
// ── ./src/squeeze/jsonpayload ──
|
|
9743
|
+
__factories["./src/squeeze/jsonpayload"] = function(module, exports) {
|
|
9744
|
+
'use strict';
|
|
9745
|
+
|
|
9746
|
+
/**
|
|
9747
|
+
* JSON-payload squeeze (v7.0.0).
|
|
9748
|
+
*
|
|
9749
|
+
* Collapses repeated array elements, truncates long string values, and
|
|
9750
|
+
* preserves the schema shape at every depth — so an LLM still sees the
|
|
9751
|
+
* structure of an API/GraphQL/validation error without the bulk.
|
|
9752
|
+
*/
|
|
9753
|
+
|
|
9754
|
+
const MAX_STR = 500;
|
|
9755
|
+
const ARRAY_KEEP = 2;
|
|
9756
|
+
|
|
9757
|
+
function squeezeValue(v, opts) {
|
|
9758
|
+
const maxStr = opts.maxStr;
|
|
9759
|
+
const keep = opts.arrayKeep;
|
|
9760
|
+
if (Array.isArray(v)) {
|
|
9761
|
+
if (v.length <= keep + 1) return v.map((x) => squeezeValue(x, opts));
|
|
9762
|
+
const head = v.slice(0, keep).map((x) => squeezeValue(x, opts));
|
|
9763
|
+
head.push(`…${v.length - keep} more similar items`);
|
|
9764
|
+
return head;
|
|
9765
|
+
}
|
|
9766
|
+
if (v && typeof v === 'object') {
|
|
9767
|
+
const o = {};
|
|
9768
|
+
for (const k of Object.keys(v)) o[k] = squeezeValue(v[k], opts);
|
|
9769
|
+
return o;
|
|
9770
|
+
}
|
|
9771
|
+
if (typeof v === 'string' && v.length > maxStr) {
|
|
9772
|
+
return v.slice(0, maxStr) + `…(${v.length} chars)`;
|
|
9773
|
+
}
|
|
9774
|
+
return v;
|
|
9775
|
+
}
|
|
9776
|
+
|
|
9777
|
+
/**
|
|
9778
|
+
* @param {string} input
|
|
9779
|
+
* @param {object} [opts]
|
|
9780
|
+
* @param {number} [opts.maxStr=500] truncate strings longer than this
|
|
9781
|
+
* @param {number} [opts.arrayKeep=2] array items kept before collapsing
|
|
9782
|
+
* @returns {{ squeezed, kept, stripped }}
|
|
9783
|
+
*/
|
|
9784
|
+
function squeezeJsonPayload(input, opts = {}) {
|
|
9785
|
+
let parsed;
|
|
9786
|
+
try { parsed = JSON.parse(input); }
|
|
9787
|
+
catch (_) { return { squeezed: input, kept: ['(not valid JSON — unchanged)'], stripped: [] }; }
|
|
9788
|
+
const cfg = { maxStr: opts.maxStr != null ? opts.maxStr : MAX_STR, arrayKeep: opts.arrayKeep != null ? opts.arrayKeep : ARRAY_KEEP };
|
|
9789
|
+
const squeezed = JSON.stringify(squeezeValue(parsed, cfg), null, 2);
|
|
9790
|
+
return {
|
|
9791
|
+
squeezed,
|
|
9792
|
+
kept: ['schema shape preserved at all depths'],
|
|
9793
|
+
stripped: ['collapsed repeated array items; truncated long string values'],
|
|
9794
|
+
};
|
|
9795
|
+
}
|
|
9796
|
+
|
|
9797
|
+
module.exports = { squeezeJsonPayload, squeezeValue };
|
|
9798
|
+
|
|
9799
|
+
};
|
|
9800
|
+
|
|
9801
|
+
// ── ./src/squeeze/stacktrace ──
|
|
9802
|
+
__factories["./src/squeeze/stacktrace"] = function(module, exports) {
|
|
9803
|
+
'use strict';
|
|
9804
|
+
|
|
9805
|
+
/**
|
|
9806
|
+
* Stack-trace squeeze (v7.0.0) — the highest-value squeeze module.
|
|
9807
|
+
*
|
|
9808
|
+
* Dedupes repeated exceptions, strips vendor frames, keeps frames in the user's
|
|
9809
|
+
* own source dirs, and — the differentiator — **enriches the top kept frame**
|
|
9810
|
+
* with its real signature from the SigMap symbol index (`buildSigIndex`).
|
|
9811
|
+
* Generic log summarizers can't do this; SigMap has the repo's symbol map.
|
|
9812
|
+
*
|
|
9813
|
+
* Pure/deterministic. The symbol index is injected via `opts.symbolIndex` so
|
|
9814
|
+
* the module is unit-testable without touching the filesystem.
|
|
9815
|
+
*/
|
|
9816
|
+
|
|
9817
|
+
const path = require('path');
|
|
9818
|
+
|
|
9819
|
+
const VENDOR_RE = /(?:^|[\\/])(?:node_modules|vendor|site-packages|dist|build|\.venv|venv|third_party|external|\.cargo|go\/pkg\/mod)[\\/]/;
|
|
9820
|
+
|
|
9821
|
+
/** Parse a frame line across JS/TS, Python, Java/Kotlin, Go, Rust, native. */
|
|
9822
|
+
function parseFrame(line) {
|
|
9823
|
+
let m;
|
|
9824
|
+
if ((m = line.match(/^\s*at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/))) return { fn: m[1], file: m[2], line: +m[3], raw: line };
|
|
9825
|
+
if ((m = line.match(/^\s*at\s+(.+?):(\d+):(\d+)\s*$/))) return { fn: '', file: m[1], line: +m[2], raw: line };
|
|
9826
|
+
if ((m = line.match(/^\s*at\s+([\w$.<>]+)\((.+?):(\d+)\)/))) return { fn: m[1], file: m[2], line: +m[3], raw: line };
|
|
9827
|
+
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 };
|
|
9828
|
+
if ((m = line.match(/^\s*(.+\.(?:go|rs)):(\d+)/))) return { fn: '', file: m[1], line: +m[2], raw: line };
|
|
9829
|
+
return null;
|
|
9830
|
+
}
|
|
9831
|
+
|
|
9832
|
+
function isVendor(file) { return VENDOR_RE.test(String(file).replace(/\\/g, '/')); }
|
|
9833
|
+
|
|
9834
|
+
function inSrcDirs(file, srcDirs) {
|
|
9835
|
+
const f = String(file).replace(/\\/g, '/');
|
|
9836
|
+
return srcDirs.some((d) => {
|
|
9837
|
+
const dd = String(d).replace(/^\.\//, '').replace(/\/$/, '');
|
|
9838
|
+
return dd && (f === dd || f.startsWith(dd + '/') || f.includes('/' + dd + '/'));
|
|
9839
|
+
});
|
|
9840
|
+
}
|
|
9841
|
+
|
|
9842
|
+
/** Look up the real signature for a frame in the SigMap symbol index. */
|
|
9843
|
+
function enrichFrame(frame, symbolIndex) {
|
|
9844
|
+
if (!symbolIndex || !frame) return null;
|
|
9845
|
+
const want = String(frame.file).replace(/\\/g, '/');
|
|
9846
|
+
const base = path.basename(want);
|
|
9847
|
+
let key = null;
|
|
9848
|
+
for (const k0 of symbolIndex.keys()) {
|
|
9849
|
+
const k = String(k0).replace(/\\/g, '/');
|
|
9850
|
+
if (k === want || want.endsWith('/' + k) || k.endsWith('/' + want)) { key = k0; break; }
|
|
9851
|
+
if (!key && path.basename(k) === base) key = k0;
|
|
9852
|
+
}
|
|
9853
|
+
if (!key) return null;
|
|
9854
|
+
const sigs = symbolIndex.get(key) || [];
|
|
9855
|
+
const wantFn = frame.fn ? frame.fn.split('.').pop() : '';
|
|
9856
|
+
let byLine = null, byName = null;
|
|
9857
|
+
for (const sig of sigs) {
|
|
9858
|
+
const s = String(sig);
|
|
9859
|
+
const mm = s.match(/:(\d+)(?:-(\d+))?\s*$/);
|
|
9860
|
+
if (mm) {
|
|
9861
|
+
const a = +mm[1], b = mm[2] ? +mm[2] : a;
|
|
9862
|
+
if (frame.line >= a && frame.line <= b) byLine = s;
|
|
9863
|
+
}
|
|
9864
|
+
if (wantFn && new RegExp('\\b' + wantFn.replace(/[^\w$]/g, '') + '\\b').test(s)) byName = byName || s;
|
|
9865
|
+
}
|
|
9866
|
+
const sig = byLine || byName;
|
|
9867
|
+
return sig ? { file: key, sig: sig.replace(/\s*:\d+(?:-\d+)?\s*$/, '').trim() } : null;
|
|
9868
|
+
}
|
|
9869
|
+
|
|
9870
|
+
/**
|
|
9871
|
+
* @param {string} input
|
|
9872
|
+
* @param {object} [opts]
|
|
9873
|
+
* @param {string[]} [opts.srcDirs] user source dirs (default ['src'])
|
|
9874
|
+
* @param {Map} [opts.symbolIndex] SigMap signature index for enrichment
|
|
9875
|
+
* @param {number} [opts.maxFrames=8] cap on kept source frames
|
|
9876
|
+
* @returns {{ squeezed, kept, stripped, enriched }}
|
|
9877
|
+
*/
|
|
9878
|
+
function squeezeStackTrace(input, opts = {}) {
|
|
9879
|
+
const srcDirs = (opts.srcDirs && opts.srcDirs.length) ? opts.srcDirs : ['src'];
|
|
9880
|
+
const maxFrames = opts.maxFrames != null ? opts.maxFrames : 8;
|
|
9881
|
+
const lines = input.split('\n');
|
|
9882
|
+
|
|
9883
|
+
const headerCount = new Map();
|
|
9884
|
+
const headerOrder = [];
|
|
9885
|
+
const frames = [];
|
|
9886
|
+
for (const line of lines) {
|
|
9887
|
+
const f = parseFrame(line);
|
|
9888
|
+
if (f) { frames.push(f); continue; }
|
|
9889
|
+
const t = line.trim();
|
|
9890
|
+
if (!t) continue;
|
|
9891
|
+
if (!headerCount.has(t)) headerOrder.push(t);
|
|
9892
|
+
headerCount.set(t, (headerCount.get(t) || 0) + 1);
|
|
9893
|
+
}
|
|
9894
|
+
|
|
9895
|
+
const seen = new Set();
|
|
9896
|
+
let dupFrames = 0;
|
|
9897
|
+
const nonVendor = [];
|
|
9898
|
+
const sourceFrames = [];
|
|
9899
|
+
let vendorCount = 0;
|
|
9900
|
+
for (const f of frames) {
|
|
9901
|
+
const k = f.file + ':' + f.line;
|
|
9902
|
+
if (seen.has(k)) { dupFrames++; continue; }
|
|
9903
|
+
seen.add(k);
|
|
9904
|
+
if (isVendor(f.file)) { vendorCount++; continue; }
|
|
9905
|
+
nonVendor.push(f);
|
|
9906
|
+
if (inSrcDirs(f.file, srcDirs)) sourceFrames.push(f);
|
|
9907
|
+
}
|
|
9908
|
+
|
|
9909
|
+
// Prefer source frames; never return empty (fall back to top non-vendor, then raw).
|
|
9910
|
+
const shown = sourceFrames.length ? sourceFrames.slice(0, maxFrames)
|
|
9911
|
+
: (nonVendor.length ? nonVendor.slice(0, 3) : frames.slice(0, 3));
|
|
9912
|
+
|
|
9913
|
+
const enrichment = shown.length ? enrichFrame(shown[0], opts.symbolIndex) : null;
|
|
9914
|
+
|
|
9915
|
+
const out = [];
|
|
9916
|
+
for (const h of headerOrder) {
|
|
9917
|
+
const n = headerCount.get(h);
|
|
9918
|
+
out.push(n > 1 ? `${h} (occurred ×${n})` : h);
|
|
9919
|
+
}
|
|
9920
|
+
for (let i = 0; i < shown.length; i++) {
|
|
9921
|
+
out.push(' ' + shown[i].raw.trim());
|
|
9922
|
+
if (i === 0 && enrichment) out.push(` ↳ ${enrichment.sig} [${enrichment.file}]`);
|
|
9923
|
+
}
|
|
9924
|
+
|
|
9925
|
+
return {
|
|
9926
|
+
squeezed: out.join('\n'),
|
|
9927
|
+
kept: [
|
|
9928
|
+
`${headerOrder.length} unique exception(s)`,
|
|
9929
|
+
`top ${shown.length} ${sourceFrames.length ? 'source ' : ''}frame(s)`,
|
|
9930
|
+
...(enrichment ? [`enriched ${path.basename(shown[0].file)}:${shown[0].line}`] : []),
|
|
9931
|
+
],
|
|
9932
|
+
stripped: [`${vendorCount} vendor frame(s)`, `${dupFrames} duplicate frame(s)`],
|
|
9933
|
+
enriched: !!enrichment,
|
|
9934
|
+
};
|
|
9935
|
+
}
|
|
9936
|
+
|
|
9937
|
+
module.exports = { squeezeStackTrace, parseFrame, isVendor, inSrcDirs, enrichFrame };
|
|
9938
|
+
|
|
9939
|
+
};
|
|
9940
|
+
|
|
9941
|
+
// ── ./src/squeeze/index ──
|
|
9942
|
+
__factories["./src/squeeze/index"] = function(module, exports) {
|
|
9943
|
+
'use strict';
|
|
9944
|
+
|
|
9945
|
+
/**
|
|
9946
|
+
* Squeeze orchestrator (v7.0.0): classify → squeeze → reduction → decision.
|
|
9947
|
+
*
|
|
9948
|
+
* Always-on and silent: callers run `squeeze()` on pasted input, then use
|
|
9949
|
+
* `shouldPrompt()` to decide whether the reduction is worth interrupting for.
|
|
9950
|
+
* Everything is deterministic and offline; the symbol index for stack-trace
|
|
9951
|
+
* enrichment is passed through via `opts.symbolIndex`.
|
|
9952
|
+
*/
|
|
9953
|
+
|
|
9954
|
+
const { classify } = __require('./src/squeeze/classify');
|
|
9955
|
+
const { squeezeStackTrace } = __require('./src/squeeze/stacktrace');
|
|
9956
|
+
const { squeezeCiLog } = __require('./src/squeeze/cilog');
|
|
9957
|
+
const { squeezeJsonPayload } = __require('./src/squeeze/jsonpayload');
|
|
9958
|
+
|
|
9959
|
+
function estimateTokens(s) { return Math.ceil(String(s || '').length / 4); }
|
|
9960
|
+
|
|
9961
|
+
/**
|
|
9962
|
+
* @param {string} input
|
|
9963
|
+
* @param {object} [opts] forwarded to the category squeezer (srcDirs, symbolIndex, …)
|
|
9964
|
+
* @returns {{ category, confidence, original, squeezed, rawTokens, squeezedTokens, reduction, kept, stripped, enriched, applies }}
|
|
9965
|
+
*/
|
|
9966
|
+
function squeeze(input, opts = {}) {
|
|
9967
|
+
const { category, confidence } = classify(input);
|
|
9968
|
+
const rawTokens = estimateTokens(input);
|
|
9969
|
+
const base = {
|
|
9970
|
+
category, confidence, original: input, squeezed: input,
|
|
9971
|
+
rawTokens, squeezedTokens: rawTokens, reduction: 0,
|
|
9972
|
+
kept: [], stripped: [], enriched: false, applies: false,
|
|
9973
|
+
};
|
|
9974
|
+
if (!category) return base;
|
|
9975
|
+
|
|
9976
|
+
let r;
|
|
9977
|
+
if (category === 'stacktrace') r = squeezeStackTrace(input, opts);
|
|
9978
|
+
else if (category === 'cilog') r = squeezeCiLog(input, opts);
|
|
9979
|
+
else r = squeezeJsonPayload(input, opts);
|
|
9980
|
+
|
|
9981
|
+
const squeezedTokens = estimateTokens(r.squeezed);
|
|
9982
|
+
const reduction = rawTokens > 0 ? (rawTokens - squeezedTokens) / rawTokens : 0;
|
|
9983
|
+
return {
|
|
9984
|
+
category, confidence,
|
|
9985
|
+
original: input, squeezed: r.squeezed,
|
|
9986
|
+
rawTokens, squeezedTokens,
|
|
9987
|
+
reduction: Math.max(0, Number(reduction.toFixed(4))),
|
|
9988
|
+
kept: r.kept || [], stripped: r.stripped || [], enriched: !!r.enriched,
|
|
9989
|
+
applies: squeezedTokens < rawTokens,
|
|
9990
|
+
};
|
|
9991
|
+
}
|
|
9992
|
+
|
|
9993
|
+
/** True when the reduction clears the threshold (accepts 0–1 or 0–100). */
|
|
9994
|
+
function shouldPrompt(reduction, threshold) {
|
|
9995
|
+
const t = threshold > 1 ? threshold / 100 : threshold;
|
|
9996
|
+
return reduction >= t;
|
|
9997
|
+
}
|
|
9998
|
+
|
|
9999
|
+
/** A compact human summary of what squeeze would do (for the prompt). */
|
|
10000
|
+
function formatSummary(result) {
|
|
10001
|
+
const pct = Math.round(result.reduction * 100);
|
|
10002
|
+
const lines = [
|
|
10003
|
+
`Input: ${result.rawTokens.toLocaleString()} tokens`,
|
|
10004
|
+
`Can reduce to ${result.squeezedTokens.toLocaleString()} tokens (${pct}% smaller):`,
|
|
10005
|
+
];
|
|
10006
|
+
for (const k of result.kept) lines.push(` ✓ Kept: ${k}`);
|
|
10007
|
+
for (const s of result.stripped) lines.push(` ✗ Stripped: ${s}`);
|
|
10008
|
+
return lines.join('\n');
|
|
10009
|
+
}
|
|
10010
|
+
|
|
10011
|
+
module.exports = { squeeze, shouldPrompt, formatSummary, estimateTokens };
|
|
10012
|
+
|
|
10013
|
+
};
|
|
10014
|
+
|
|
10015
|
+
// ── ./src/nudge ──
|
|
10016
|
+
__factories["./src/nudge"] = function(module, exports) {
|
|
10017
|
+
'use strict';
|
|
10018
|
+
|
|
10019
|
+
/**
|
|
10020
|
+
* Star nudge + usage tracking (v7.0.0).
|
|
10021
|
+
*
|
|
10022
|
+
* Records run counts in `.context/usage.json` and shows a one-time GitHub-star
|
|
10023
|
+
* message after the tool has been genuinely useful (≥10 runs, ≥8 successes).
|
|
10024
|
+
* Shown exactly once per machine — even under concurrent runs (an `wx` lock
|
|
10025
|
+
* file makes the show race-safe). Wired into `ask` (and the `squeeze` path).
|
|
10026
|
+
*/
|
|
10027
|
+
|
|
10028
|
+
const fs = require('fs');
|
|
10029
|
+
const path = require('path');
|
|
10030
|
+
const os = require('os');
|
|
10031
|
+
const crypto = require('crypto');
|
|
10032
|
+
|
|
10033
|
+
const RUN_THRESHOLD = 10;
|
|
10034
|
+
const SUCCESS_THRESHOLD = 8;
|
|
10035
|
+
|
|
10036
|
+
function usagePath(cwd) { return path.join(cwd, '.context', 'usage.json'); }
|
|
10037
|
+
|
|
10038
|
+
function defaultUsage() {
|
|
10039
|
+
return {
|
|
10040
|
+
totalRuns: 0, successfulRuns: 0, squeezeOffered: 0, squeezeAccepted: 0,
|
|
10041
|
+
starNudgeShown: false, machineId: '', firstRunDate: null, lastRunDate: null,
|
|
10042
|
+
};
|
|
10043
|
+
}
|
|
10044
|
+
|
|
10045
|
+
function readUsage(cwd) {
|
|
10046
|
+
try { return { ...defaultUsage(), ...JSON.parse(fs.readFileSync(usagePath(cwd), 'utf8')) }; }
|
|
10047
|
+
catch (_) { return defaultUsage(); }
|
|
10048
|
+
}
|
|
10049
|
+
|
|
10050
|
+
function writeUsageAtomic(cwd, usage) {
|
|
10051
|
+
const p = usagePath(cwd);
|
|
10052
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
10053
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
10054
|
+
fs.writeFileSync(tmp, JSON.stringify(usage, null, 2));
|
|
10055
|
+
fs.renameSync(tmp, p); // atomic on POSIX
|
|
10056
|
+
}
|
|
10057
|
+
|
|
10058
|
+
const STAR_MESSAGE = [
|
|
10059
|
+
'─────────────────────────────────────────────────────────',
|
|
10060
|
+
' SigMap has helped you 10 times now.',
|
|
10061
|
+
'',
|
|
10062
|
+
" If it's been useful, a GitHub star takes 5 seconds and",
|
|
10063
|
+
' helps other developers find it:',
|
|
10064
|
+
' → github.com/manojmallick/sigmap',
|
|
10065
|
+
'',
|
|
10066
|
+
" (Won't ask again. Press Enter to continue.)",
|
|
10067
|
+
'─────────────────────────────────────────────────────────',
|
|
10068
|
+
].join('\n');
|
|
10069
|
+
|
|
10070
|
+
function showStarNudge(write) {
|
|
10071
|
+
(write || ((s) => process.stderr.write(s)))('\n' + STAR_MESSAGE + '\n');
|
|
10072
|
+
}
|
|
10073
|
+
|
|
10074
|
+
/**
|
|
10075
|
+
* Record one run and, when the thresholds are first met, show the star nudge.
|
|
10076
|
+
* @param {string} cwd
|
|
10077
|
+
* @param {boolean} runSuccess
|
|
10078
|
+
* @param {object} [opts]
|
|
10079
|
+
* @param {boolean} [opts.silent] record only — never print
|
|
10080
|
+
* @param {function} [opts.write] sink for the message (default stderr)
|
|
10081
|
+
* @param {string} [opts.today] override date (testing)
|
|
10082
|
+
* @param {object} [opts.bump] counter deltas to merge (e.g. { squeezeAccepted: 1 })
|
|
10083
|
+
* @returns {{ usage, nudged }}
|
|
10084
|
+
*/
|
|
10085
|
+
function checkStarNudge(cwd, runSuccess, opts = {}) {
|
|
10086
|
+
const usage = readUsage(cwd);
|
|
10087
|
+
usage.totalRuns += 1;
|
|
10088
|
+
if (runSuccess) usage.successfulRuns += 1;
|
|
10089
|
+
if (opts.bump) for (const k of Object.keys(opts.bump)) usage[k] = (usage[k] || 0) + opts.bump[k];
|
|
10090
|
+
|
|
10091
|
+
const today = opts.today || new Date().toISOString().slice(0, 10);
|
|
10092
|
+
if (!usage.firstRunDate) usage.firstRunDate = today;
|
|
10093
|
+
usage.lastRunDate = today;
|
|
10094
|
+
if (!usage.machineId) {
|
|
10095
|
+
try { usage.machineId = 'sha256-' + crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 16); } catch (_) {}
|
|
10096
|
+
}
|
|
10097
|
+
|
|
10098
|
+
let nudged = false;
|
|
10099
|
+
if (!usage.starNudgeShown && usage.totalRuns >= RUN_THRESHOLD && usage.successfulRuns >= SUCCESS_THRESHOLD) {
|
|
10100
|
+
// Race-safe single-show: only the process that creates the lock prints.
|
|
10101
|
+
let won = false;
|
|
10102
|
+
try { fs.closeSync(fs.openSync(usagePath(cwd) + '.nudge.lock', 'wx')); won = true; }
|
|
10103
|
+
catch (_) { won = false; }
|
|
10104
|
+
if (won && !opts.silent) showStarNudge(opts.write);
|
|
10105
|
+
nudged = won;
|
|
10106
|
+
usage.starNudgeShown = true;
|
|
10107
|
+
}
|
|
10108
|
+
|
|
10109
|
+
writeUsageAtomic(cwd, usage);
|
|
10110
|
+
return { usage, nudged };
|
|
10111
|
+
}
|
|
10112
|
+
|
|
10113
|
+
module.exports = { checkStarNudge, readUsage, usagePath, showStarNudge, RUN_THRESHOLD, SUCCESS_THRESHOLD };
|
|
10114
|
+
|
|
10115
|
+
};
|
|
10116
|
+
|
|
9183
10117
|
// ── ./src/verify/parsers ──
|
|
9184
10118
|
__factories["./src/verify/parsers"] = function(module, exports) {
|
|
9185
10119
|
'use strict';
|
|
@@ -10024,7 +10958,7 @@ const path = require('path');
|
|
|
10024
10958
|
const os = require('os');
|
|
10025
10959
|
const { execSync } = require('child_process');
|
|
10026
10960
|
|
|
10027
|
-
const VERSION = '
|
|
10961
|
+
const VERSION = '7.0.0';
|
|
10028
10962
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
10029
10963
|
|
|
10030
10964
|
function requireSourceOrBundled(key) {
|
|
@@ -10324,29 +11258,17 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
10324
11258
|
let total = renderedTotal(fileEntries);
|
|
10325
11259
|
if (total <= budgetForEntries) return fileEntries;
|
|
10326
11260
|
|
|
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) => {
|
|
11261
|
+
// Over budget — degrade gracefully, best file first:
|
|
11262
|
+
// 1. keep FULL signatures (params + return type) while they fit;
|
|
11263
|
+
// 2. when a file no longer fits with full sigs, collapse just THAT file to
|
|
11264
|
+
// its line-anchor pointers (still discoverable via buildSigIndex / the
|
|
11265
|
+
// ranker — it parses the context file, so an anchored file stays findable);
|
|
11266
|
+
// 3. drop a file only when even the anchor form won't fit.
|
|
11267
|
+
// The important, high-priority files therefore keep their real signatures —
|
|
11268
|
+
// the user-visible value — while only low-priority overflow degrades to
|
|
11269
|
+
// anchors instead of vanishing. (Earlier versions collapsed EVERY file to an
|
|
11270
|
+
// anchor the moment you went 1 token over budget, gutting all signatures.)
|
|
11271
|
+
const withPriority = fileEntries.map((e) => {
|
|
10350
11272
|
let priority = 0;
|
|
10351
11273
|
let dropReason = 'budget: low recency';
|
|
10352
11274
|
if (isGeneratedFile(e.filePath)) { priority = 10; dropReason = 'budget: generated file'; }
|
|
@@ -10354,41 +11276,59 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
10354
11276
|
else if (isTestFile(e.filePath)) { priority = 8; dropReason = 'budget: test file'; }
|
|
10355
11277
|
else if (isConfigFile(e.filePath)) { priority = 6; dropReason = 'budget: config file'; }
|
|
10356
11278
|
else priority = 4;
|
|
10357
|
-
// v4.0: signal quality = sigs per line-of-code (higher = more informative)
|
|
10358
11279
|
const loc = e.content ? e.content.split('\n').length : 1;
|
|
10359
11280
|
const signalQuality = loc > 0 ? (e.sigs ? e.sigs.length : 0) / loc : 0;
|
|
10360
11281
|
return { ...e, priority, dropReason, signalQuality };
|
|
10361
11282
|
});
|
|
10362
11283
|
|
|
10363
|
-
//
|
|
10364
|
-
withPriority.sort((a, b) => {
|
|
10365
|
-
if (
|
|
10366
|
-
if ((
|
|
10367
|
-
return (
|
|
11284
|
+
// Best-first order: lowest drop-priority, then most-recent, then most-informative.
|
|
11285
|
+
const bestFirst = withPriority.slice().sort((a, b) => {
|
|
11286
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
11287
|
+
if ((b.mtime || 0) !== (a.mtime || 0)) return (b.mtime || 0) - (a.mtime || 0);
|
|
11288
|
+
return (b.signalQuality || 0) - (a.signalQuality || 0);
|
|
10368
11289
|
});
|
|
10369
11290
|
|
|
10370
|
-
const
|
|
11291
|
+
const entryCost = (e) => estimateTokens(e.sigs.join('\n')) + sectionOverhead(e);
|
|
11292
|
+
const collapseEntry = (e) => ({
|
|
11293
|
+
...e,
|
|
11294
|
+
sigs: (e.sigs || []).map((s) => {
|
|
11295
|
+
const line = toIndexLine(s);
|
|
11296
|
+
return line && /:\d+-\d+/.test(line) ? line : s;
|
|
11297
|
+
}),
|
|
11298
|
+
});
|
|
11299
|
+
// Keep FULL signatures for the best files while they fit (signatures are the
|
|
11300
|
+
// value the user reads). When a file's full form no longer fits, fall back to
|
|
11301
|
+
// its anchor form (still discoverable via the ranker, which parses this file);
|
|
11302
|
+
// drop only when even the anchor won't fit. So the important files keep real
|
|
11303
|
+
// signatures and only the lowest-priority overflow degrades — instead of every
|
|
11304
|
+
// signature being gutted to an anchor the moment you go over budget.
|
|
11305
|
+
const finalByPath = new Map();
|
|
10371
11306
|
const verboseDropped = [];
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
kept.push(entry);
|
|
11307
|
+
let collapsedCount = 0;
|
|
11308
|
+
let used = 0;
|
|
11309
|
+
for (const entry of bestFirst) { // best first
|
|
11310
|
+
if (used + entryCost(entry) <= budgetForEntries) {
|
|
11311
|
+
finalByPath.set(entry.filePath, entry); used += entryCost(entry); continue;
|
|
11312
|
+
}
|
|
11313
|
+
const slim = collapseEntry(entry);
|
|
11314
|
+
if (used + entryCost(slim) <= budgetForEntries) {
|
|
11315
|
+
finalByPath.set(entry.filePath, slim); used += entryCost(slim); collapsedCount++; continue;
|
|
10382
11316
|
}
|
|
11317
|
+
verboseDropped.push({ filePath: entry.filePath, reason: entry.dropReason });
|
|
10383
11318
|
}
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
11319
|
+
// Restore the original file order for stable output.
|
|
11320
|
+
const kept = withPriority.filter((e) => finalByPath.has(e.filePath)).map((e) => finalByPath.get(e.filePath));
|
|
11321
|
+
|
|
11322
|
+
if (verboseDropped.length > 0 || collapsedCount > 0) {
|
|
11323
|
+
const parts = [];
|
|
11324
|
+
if (verboseDropped.length) parts.push(`dropped ${verboseDropped.length} file(s)`);
|
|
11325
|
+
if (collapsedCount) parts.push(`collapsed ${collapsedCount} low-priority file(s) to anchors`);
|
|
11326
|
+
console.warn(`[sigmap] budget: ${parts.join(', ')} to stay under ${maxTokens} tokens`);
|
|
10387
11327
|
if (process.argv.includes('--verbose')) {
|
|
10388
11328
|
for (const { filePath, reason } of verboseDropped) {
|
|
10389
11329
|
console.warn(`[sigmap] dropped: ${path.relative(process.cwd(), filePath)} — ${reason}`);
|
|
10390
11330
|
}
|
|
10391
|
-
console.warn(`[sigmap] included: ${kept.length} files, dropped: ${verboseDropped.length}`);
|
|
11331
|
+
console.warn(`[sigmap] included: ${kept.length} files (${collapsedCount} collapsed), dropped: ${verboseDropped.length}`);
|
|
10392
11332
|
}
|
|
10393
11333
|
}
|
|
10394
11334
|
return kept;
|
|
@@ -10610,12 +11550,17 @@ function resolveImpactRadius(fileEntries, cwd, config) {
|
|
|
10610
11550
|
}
|
|
10611
11551
|
|
|
10612
11552
|
function formatOutput(fileEntries, cwd, routingEnabled, config, extras) {
|
|
11553
|
+
// One canonical usage block for every context file (CLAUDE.md, AGENTS.md,
|
|
11554
|
+
// copilot-instructions.md, …). Lives here — the single source all writers
|
|
11555
|
+
// consume — so adapters don't each invent their own (now-removed) variant.
|
|
11556
|
+
const { usageBlock } = requireSourceOrBundled('./src/format/usage-guidance');
|
|
10613
11557
|
const lines = [
|
|
10614
11558
|
'<!-- Generated by SigMap gen-context.js v' + VERSION + ' -->',
|
|
10615
11559
|
'<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->',
|
|
10616
11560
|
'',
|
|
10617
11561
|
'# Code signatures',
|
|
10618
11562
|
'',
|
|
11563
|
+
usageBlock(),
|
|
10619
11564
|
];
|
|
10620
11565
|
|
|
10621
11566
|
// Compact dependency map section (shows import relationships, ~50-100 tokens)
|
|
@@ -11786,6 +12731,10 @@ Usage:
|
|
|
11786
12731
|
${cmd} verify-ai-output <answer.md> Flag fake files/tests/imports/symbols/npm-scripts in an AI answer
|
|
11787
12732
|
${cmd} verify-ai-output <answer.md> --json Hallucination report as JSON (exits 1 if issues)
|
|
11788
12733
|
${cmd} verify-ai-output <answer.md> --report Write a standalone HTML report (red/amber/green)
|
|
12734
|
+
${cmd} squeeze <file|-> Minimize a pasted stacktrace/CI-log/JSON blob (--json for stats)
|
|
12735
|
+
${cmd} ask "<query>" --squeeze Auto-accept input minimization (no prompt; for scripts/CI)
|
|
12736
|
+
${cmd} ask "<query>" --no-squeeze Disable input minimization entirely
|
|
12737
|
+
${cmd} ask "<query>" --squeeze-threshold N Min reduction %% to prompt (default 30)
|
|
11789
12738
|
${cmd} note "<text>" Append a note to the cross-session decision log
|
|
11790
12739
|
${cmd} note List recent notes (also: note --list <N>)
|
|
11791
12740
|
${cmd} status Show repo state — branch, dirty files, index freshness, notes
|
|
@@ -12174,8 +13123,8 @@ function main() {
|
|
|
12174
13123
|
const { loadSession, saveSession, mergeSessionContext } = requireSourceOrBundled('./src/session/memory');
|
|
12175
13124
|
const { detectWorkspaces, inferPackage, scopeToPackage } = requireSourceOrBundled('./src/workspace/detector');
|
|
12176
13125
|
|
|
12177
|
-
|
|
12178
|
-
|
|
13126
|
+
let intent = detectIntent(query);
|
|
13127
|
+
let intentWeights = getIntentWeights(intent);
|
|
12179
13128
|
|
|
12180
13129
|
const sigIndex = buildSigIndex(cwd);
|
|
12181
13130
|
if (sigIndex.size === 0) {
|
|
@@ -12183,6 +13132,44 @@ function main() {
|
|
|
12183
13132
|
process.exit(1);
|
|
12184
13133
|
}
|
|
12185
13134
|
|
|
13135
|
+
// v7.0.0: Squeeze — classify and minimize pasted stacktrace/CI-log/JSON
|
|
13136
|
+
// input before ranking. Always runs silently; only prompts (interactive
|
|
13137
|
+
// TTY) when the reduction clears the threshold. Never blocks pipes/CI.
|
|
13138
|
+
let squeezeOffered = false;
|
|
13139
|
+
let squeezeAccepted = false;
|
|
13140
|
+
if (!args.includes('--no-squeeze')) {
|
|
13141
|
+
try {
|
|
13142
|
+
const { squeeze: runSqueeze, shouldPrompt, formatSummary } = requireSourceOrBundled('./src/squeeze/index');
|
|
13143
|
+
const sq = runSqueeze(query, { srcDirs: config.srcDirs, symbolIndex: sigIndex });
|
|
13144
|
+
if (sq.applies && sq.reduction > 0) {
|
|
13145
|
+
squeezeOffered = true;
|
|
13146
|
+
const thrIdx = args.indexOf('--squeeze-threshold');
|
|
13147
|
+
const threshold = thrIdx !== -1 ? parseFloat(args[thrIdx + 1]) : 30;
|
|
13148
|
+
const auto = args.includes('--squeeze');
|
|
13149
|
+
const interactive = !!(process.stdin.isTTY && process.stderr.isTTY) && !args.includes('--json');
|
|
13150
|
+
let accept = false;
|
|
13151
|
+
if (auto) {
|
|
13152
|
+
accept = true;
|
|
13153
|
+
} else if (interactive && shouldPrompt(sq.reduction, threshold)) {
|
|
13154
|
+
process.stderr.write('\n' + formatSummary(sq) + '\n\n');
|
|
13155
|
+
process.stderr.write('Proceed with minimized input? [Y/n] ');
|
|
13156
|
+
const buf = Buffer.alloc(8);
|
|
13157
|
+
try {
|
|
13158
|
+
const n = fs.readSync(0, buf, 0, 8, null);
|
|
13159
|
+
const ans = buf.toString('utf8', 0, n).trim().toLowerCase();
|
|
13160
|
+
accept = ans === '' || ans === 'y' || ans === 'yes';
|
|
13161
|
+
} catch (_) { accept = false; }
|
|
13162
|
+
}
|
|
13163
|
+
if (accept) {
|
|
13164
|
+
query = sq.squeezed;
|
|
13165
|
+
intent = detectIntent(query);
|
|
13166
|
+
intentWeights = getIntentWeights(intent);
|
|
13167
|
+
squeezeAccepted = true;
|
|
13168
|
+
}
|
|
13169
|
+
}
|
|
13170
|
+
} catch (_) { /* squeeze is best-effort — never break ask */ }
|
|
13171
|
+
}
|
|
13172
|
+
|
|
12186
13173
|
let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });
|
|
12187
13174
|
|
|
12188
13175
|
// v6.10: Workspace scoping — infer package from query and apply boost
|
|
@@ -12282,6 +13269,15 @@ function main() {
|
|
|
12282
13269
|
bar,
|
|
12283
13270
|
].join('\n'));
|
|
12284
13271
|
}
|
|
13272
|
+
// v7.0.0: record the run and show the one-time star nudge (interactive only).
|
|
13273
|
+
try {
|
|
13274
|
+
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
13275
|
+
const showNudge = !!process.stderr.isTTY && !args.includes('--json');
|
|
13276
|
+
checkStarNudge(cwd, true, {
|
|
13277
|
+
silent: !showNudge,
|
|
13278
|
+
bump: { squeezeOffered: squeezeOffered ? 1 : 0, squeezeAccepted: squeezeAccepted ? 1 : 0 },
|
|
13279
|
+
});
|
|
13280
|
+
} catch (_) {}
|
|
12285
13281
|
process.exit(0);
|
|
12286
13282
|
}
|
|
12287
13283
|
|
|
@@ -13037,6 +14033,49 @@ function main() {
|
|
|
13037
14033
|
process.exit(0);
|
|
13038
14034
|
}
|
|
13039
14035
|
|
|
14036
|
+
// v7.0.0: `sigmap squeeze <file|->` — minimize a pasted stacktrace / CI-log / JSON blob
|
|
14037
|
+
if (args[0] === 'squeeze') {
|
|
14038
|
+
const jsonOut = args.includes('--json');
|
|
14039
|
+
const target = args[1] && !args[1].startsWith('--') ? args[1] : '-';
|
|
14040
|
+
let input = '';
|
|
14041
|
+
try {
|
|
14042
|
+
input = fs.readFileSync(target === '-' ? 0 : path.resolve(cwd, target), 'utf8');
|
|
14043
|
+
} catch (e) {
|
|
14044
|
+
console.error(`[sigmap] cannot read input: ${e.message}`);
|
|
14045
|
+
process.exit(1);
|
|
14046
|
+
}
|
|
14047
|
+
|
|
14048
|
+
const { squeeze: runSqueeze, formatSummary } = requireSourceOrBundled('./src/squeeze/index');
|
|
14049
|
+
let symbolIndex = null;
|
|
14050
|
+
try {
|
|
14051
|
+
const { buildSigIndex } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
14052
|
+
symbolIndex = buildSigIndex(cwd);
|
|
14053
|
+
} catch (_) {}
|
|
14054
|
+
const sq = runSqueeze(input, { srcDirs: config.srcDirs, symbolIndex });
|
|
14055
|
+
|
|
14056
|
+
try {
|
|
14057
|
+
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
14058
|
+
checkStarNudge(cwd, true, { silent: !process.stderr.isTTY || jsonOut, bump: { squeezeOffered: sq.applies ? 1 : 0 } });
|
|
14059
|
+
} catch (_) {}
|
|
14060
|
+
|
|
14061
|
+
if (jsonOut) {
|
|
14062
|
+
process.stdout.write(JSON.stringify({
|
|
14063
|
+
category: sq.category, confidence: sq.confidence,
|
|
14064
|
+
rawTokens: sq.rawTokens, squeezedTokens: sq.squeezedTokens,
|
|
14065
|
+
reduction: sq.reduction, enriched: sq.enriched, squeezed: sq.squeezed,
|
|
14066
|
+
}) + '\n');
|
|
14067
|
+
process.exit(0);
|
|
14068
|
+
}
|
|
14069
|
+
if (!sq.category) {
|
|
14070
|
+
process.stderr.write('[sigmap] no squeezable structure detected — input unchanged\n');
|
|
14071
|
+
process.stdout.write(input);
|
|
14072
|
+
process.exit(0);
|
|
14073
|
+
}
|
|
14074
|
+
process.stderr.write(formatSummary(sq) + '\n\n');
|
|
14075
|
+
process.stdout.write(sq.squeezed + '\n');
|
|
14076
|
+
process.exit(0);
|
|
14077
|
+
}
|
|
14078
|
+
|
|
13040
14079
|
// `sigmap verify-ai-output <answer.md>` — Hallucination Guard (deterministic core)
|
|
13041
14080
|
if (args[0] === 'verify-ai-output') {
|
|
13042
14081
|
const target = args[1];
|