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/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
- 'use strict';
3169
-
3170
- /**
3171
- * Compare signature arrays and produce compact diff markers.
3172
- * @param {string[]} baseSigs
3173
- * @param {string[]} currentSigs
3174
- * @returns {{added:string[], removed:string[], modified:string[]}}
3175
- */
3176
- function diffSignatures(baseSigs, currentSigs) {
3177
- const base = new Set(baseSigs || []);
3178
- const curr = new Set(currentSigs || []);
3179
-
3180
- const added = [...curr].filter((s) => !base.has(s));
3181
- const removed = [...base].filter((s) => !curr.has(s));
3182
-
3183
- const byName = (arr) => {
3184
- const m = new Map();
3185
- for (const s of arr) {
3186
- const n = extractName(s);
3187
- if (!n) continue;
3188
- if (!m.has(n)) m.set(n, []);
3189
- m.get(n).push(s);
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
- for (const [name] of aBy) {
3199
- if (rBy.has(name)) modified.push(name);
3200
- }
3193
+ const aBy = byName(added);
3194
+ const rBy = byName(removed);
3195
+ const modified = [];
3201
3196
 
3202
- return { added, removed, modified };
3197
+ for (const [name] of aBy) {
3198
+ if (rBy.has(name)) modified.push(name);
3203
3199
  }
3204
3200
 
3205
- function extractName(sig) {
3206
- if (!sig) return '';
3207
- const t = sig.trim();
3208
- const m = t.match(/(?:def|function|func|class|interface|trait|struct|enum|record)?\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(|$)/);
3209
- return m ? m[1] : '';
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
- module.exports = { diffSignatures, extractName };
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: '6.15.0',
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
- const path = require('path');
7992
- const fs = require('fs');
7993
- const name = 'copilot';
7994
- const COPILOT_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
7995
- function format(context, opts = {}) {
7996
- if (!context || typeof context !== 'string') return '';
7997
- const version = (opts && opts.version) || 'unknown';
7998
- const timestamp = new Date().toISOString();
7999
- return [
8000
- `<!-- Generated by SigMap gen-context.js v${version} -->`,
8001
- `<!-- Updated: ${timestamp} -->`,
8002
- `<!-- Do not edit below — regenerate with: node gen-context.js -->`,
8003
- '',
8004
- '## SigMap commands',
8005
- '',
8006
- '| When | Command |',
8007
- '|------|---------|',
8008
- '| Before answering a question about code | `sigmap ask "<your question>"` |',
8009
- '| After changing config or source dirs | `sigmap validate` |',
8010
- '| To verify an AI answer is grounded | `sigmap judge --response <file>` |',
8011
- '',
8012
- '# Code signatures',
8013
- '',
8014
- context,
8015
- ].join('\n');
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
- function outputPath(cwd) { return path.join(cwd, '.github', 'copilot-instructions.md'); }
8018
- function write(context, cwd, opts = {}) {
8019
- const filePath = outputPath(cwd);
8020
- let existing = '';
8021
- if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
8022
- const formatted = format(context, opts);
8023
- const markerIdx = existing.indexOf('## Auto-generated signatures');
8024
- const isLegacyGenerated = existing.includes('<!-- Generated by SigMap gen-context.js') || existing.includes('# Code signatures');
8025
- const newContent = markerIdx !== -1
8026
- ? existing.slice(0, markerIdx) + COPILOT_MARKER.trimStart() + formatted
8027
- : (isLegacyGenerated ? COPILOT_MARKER.trimStart() + formatted : existing + COPILOT_MARKER + formatted);
8028
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
8029
- fs.writeFileSync(filePath, newContent, 'utf8');
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
- const path = require('path');
8037
- const fs = require('fs');
8038
- const name = 'claude';
8039
- const CLAUDE_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
8040
- const CLAUDE_ALLOWLIST_MARKER = '<!-- sigmap-bash-allowlist -->';
8041
- const CLAUDE_ALLOWLIST_BLOCK = [
8042
- '## Bash allowlist',
8043
- '',
8044
- CLAUDE_ALLOWLIST_MARKER,
8045
- '',
8046
- 'The following sigmap commands are pre-approved — Claude Code will not prompt for confirmation:',
8047
- '',
8048
- '```json',
8049
- JSON.stringify({
8050
- permissions: {
8051
- allow: [
8052
- 'Bash(sigmap ask*)',
8053
- 'Bash(sigmap validate*)',
8054
- 'Bash(sigmap judge*)',
8055
- 'Bash(sigmap weights*)',
8056
- 'Bash(sigmap history*)',
8057
- 'Bash(sigmap --query*)',
8058
- 'Bash(sigmap --diff*)',
8059
- 'Bash(sigmap --health*)',
8060
- 'Bash(sigmap --coverage*)',
8061
- 'Bash(node gen-context.js*)',
8062
- ],
8063
- },
8064
- }, null, 2),
8065
- '```',
8066
- '',
8067
- 'Add the `permissions.allow` array above to `.claude/settings.json` to activate.',
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
- function format(context, opts = {}) {
8071
- if (!context || typeof context !== 'string') return '';
8072
- const version = (opts && opts.version) || 'unknown';
8073
- const timestamp = new Date().toISOString();
8074
- return [
8075
- '## SigMap',
8076
- '- Before searching for files, run `sigmap ask "<query>"` — ranks by TF-IDF relevance',
8077
- '- After config changes, run `sigmap validate` to confirm coverage',
8078
- '- To verify answer groundedness, run `sigmap judge --response <file> --context CLAUDE.md`',
8079
- '- For PR-scoped context, run `sigmap --diff`',
8080
- '',
8081
- `<!-- Generated by SigMap v${version} — ${timestamp} -->`,
8082
- '',
8083
- context,
8084
- ].join('\n');
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
- function outputPath(cwd) { return path.join(cwd, 'CLAUDE.md'); }
8087
- function write(context, cwd, opts = {}) {
8088
- const filePath = outputPath(cwd);
8089
- let existing = '';
8090
- if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
8091
- const formatted = format(context, opts);
8092
- const markerIdx = existing.indexOf('## Auto-generated signatures');
8093
- let newContent = markerIdx !== -1
8094
- ? existing.slice(0, markerIdx) + CLAUDE_MARKER.trimStart() + formatted
8095
- : existing + CLAUDE_MARKER + formatted;
8096
- if (!newContent.includes(CLAUDE_ALLOWLIST_MARKER)) {
8097
- const sigPos = newContent.indexOf('## Auto-generated signatures');
8098
- if (sigPos !== -1) {
8099
- newContent = newContent.slice(0, sigPos) + CLAUDE_ALLOWLIST_BLOCK + '\n' + newContent.slice(sigPos);
8100
- } else {
8101
- newContent = CLAUDE_ALLOWLIST_BLOCK + '\n' + newContent;
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
- const path = require('path');
8112
- const name = 'cursor';
8113
- function format(context, opts = {}) {
8114
- if (!context || typeof context !== 'string') return '';
8115
- const version = (opts && opts.version) || 'unknown';
8116
- const timestamp = new Date().toISOString();
8117
- return [
8118
- `# Code signatures generated by SigMap v${version}`,
8119
- `# Updated: ${timestamp}`,
8120
- `# Regenerate: node gen-context.js`,
8121
- '#',
8122
- '# SigMap: before answering, run: sigmap ask "<query>" — finds relevant files',
8123
- '# SigMap: after config changes, run: sigmap validate',
8124
- '# SigMap: to verify answers, run: sigmap judge --response <file>',
8125
- '',
8126
- context,
8127
- ].join('\n');
8128
- }
8129
- function outputPath(cwd) { return path.join(cwd, '.cursorrules'); }
8130
- module.exports = { name, format, outputPath };
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
- const path = require('path');
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
- // ── ./packages/adapters/openai (bundled) ──
8158
- __factories["./packages/adapters/openai"] = function(module, exports) {
8159
- const path = require('path');
8160
- const name = 'openai';
8161
- function format(context, opts = {}) {
8162
- if (!context || typeof context !== 'string') return '';
8163
- const version = (opts && opts.version) || 'unknown';
8164
- const timestamp = new Date().toISOString();
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
- // ── ./packages/adapters/gemini (bundled) ──
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
- // ── ./packages/adapters/codex (bundled) ──
8225
- __factories["./packages/adapters/codex"] = function(module, exports) {
8226
- const path = require('path');
8227
- const fs = require('fs');
8228
- const name = 'codex';
8229
- const CODEX_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
8230
- const CODEX_TOOLS_MARKER = '<!-- sigmap-tools -->';
8231
- const CODEX_TOOLS_BLOCK = [
8232
- '## Tools',
8233
- '',
8234
- CODEX_TOOLS_MARKER,
8235
- '',
8236
- '```json',
8237
- JSON.stringify([
8238
- { name: 'sigmap_ask', description: 'Rank source files by relevance to a natural-language query. Run before exploring the codebase.', command: 'sigmap ask "$QUERY"' },
8239
- { name: 'sigmap_validate', description: 'Validate SigMap config and measure context coverage. Run after changing config or source dirs.', command: 'sigmap validate' },
8240
- { name: 'sigmap_judge', description: 'Score an LLM response for groundedness against source context. Use to verify answer quality.', command: 'sigmap judge --response "$RESPONSE" --context "$CONTEXT"' },
8241
- { name: 'sigmap_query', description: 'Rank all files by relevance using TF-IDF and write a focused mini-context.', command: 'sigmap --query "$QUERY" --context' },
8242
- { name: 'sigmap_weights', description: 'Show learned file-ranking multipliers accumulated from past sessions.', command: 'sigmap weights' },
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
- function format(context, opts = {}) {
8248
- if (!context || typeof context !== 'string' || !context.trim()) return '';
8249
- const toolBlock = [
8250
- '## SigMap commands',
8251
- '',
8252
- '| When | Command |',
8253
- '|------|---------|',
8254
- '| Before answering a question | `sigmap ask "<your question>"` |',
8255
- '| After code changes | `sigmap validate` |',
8256
- '| To query by topic | `sigmap --query "<topic>"` |',
8257
- '',
8258
- 'Always run `sigmap ask` or `sigmap --query` before searching for files relevant to a task.',
8259
- '',
8260
- ].join('\n');
8261
- return '# Code signatures\n\n' + toolBlock + context;
8262
- }
8263
- function outputPath(cwd) { return path.join(cwd, 'AGENTS.md'); }
8264
- function write(context, cwd, opts = {}) {
8265
- const filePath = outputPath(cwd);
8266
- let existing = '';
8267
- if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
8268
- const formatted = format(context, opts);
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
- // ── ./src/extractors/generic ──
8315
- __factories["./src/extractors/generic"] = function(module, exports) {
8316
- 'use strict';
8317
- const PATTERNS = [
8318
- /^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
8319
- /^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
8320
- /^def\s+\w+[\s(|:]/,
8321
- /^(?:pub\s+)?func\s+\w+\s*\(/,
8322
- /^(?:let|let\s+rec)\s+\w+\s*[=(]/,
8323
- /^class\s+\w+/,
8324
- /^(?:proc|sub|method)\s+\w+\s*\(/,
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 = '6.15.0';
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
- // v6.12 Surgical Context progressive disclosure: before dropping whole files,
10328
- // collapse signature BODIES to their line-anchor pointers (keep `symbol :start-end`).
10329
- // Only sigs that actually carry an anchor shrink; the agent can re-fetch bodies via
10330
- // the get_lines MCP tool. This degrades gracefully instead of losing files outright.
10331
- let working = fileEntries;
10332
- const collapsed = fileEntries.map((e) => {
10333
- const slim = (e.sigs || []).map((s) => {
10334
- const line = toIndexLine(s);
10335
- return line && /:\d+-\d+/.test(line) ? line : s; // replace only when it yields an anchor
10336
- });
10337
- return { ...e, sigs: slim };
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 budgetdegrade 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
- // Within same priority: sort by mtime ascending (oldest first), then signalQuality ascending (least informative first)
10364
- withPriority.sort((a, b) => {
10365
- if (b.priority !== a.priority) return b.priority - a.priority;
10366
- if ((a.mtime || 0) !== (b.mtime || 0)) return (a.mtime || 0) - (b.mtime || 0);
10367
- return (a.signalQuality || 0) - (b.signalQuality || 0);
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 kept = [];
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
- // Iterate forward: highest drop-priority files (generated=10, mock=9, test=8) are at index 0.
10373
- // Drop those first until the RENDERED total (sigs + section overhead) fits maxTokens,
10374
- // then keep everything else.
10375
- let rendered = renderedTotal(withPriority);
10376
- for (const entry of withPriority) {
10377
- if (rendered > budgetForEntries) {
10378
- rendered -= estimateTokens(entry.sigs.join('\n')) + sectionOverhead(entry);
10379
- verboseDropped.push({ filePath: entry.filePath, reason: entry.dropReason });
10380
- } else {
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
- if (verboseDropped.length > 0) {
10385
- console.warn(`[sigmap] budget: dropped ${verboseDropped.length} files to stay under ${maxTokens} tokens`);
10386
- // Feature 7: --verbose — print per-file drop reason
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
- const intent = detectIntent(query);
12178
- const intentWeights = getIntentWeights(intent);
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];