sigmap 6.5.2 → 6.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -56,15 +56,11 @@ Use this marker block for all appendable context files:
56
56
  | To query by topic | `sigmap --query "<topic>"` |
57
57
 
58
58
  Always run `sigmap ask` or `sigmap --query` before searching for files relevant to a task.
59
- ## changes (last 5 commits — 2 days ago)
59
+ ## changes (last 5 commits — 8 minutes ago)
60
60
  ```
61
- src/config/loader.js +_legacyDetectAutoSrcDirs ~detectAutoSrcDirs
62
- src/discovery/source-root-resolver.js +resolveSourceRoots +_detectMonorepo +_enumerateCandidates +_applySpecialRules
63
- src/discovery/language-detector.js +detectLanguages +_walkDepth
64
- src/discovery/source-root-scorer.js +getRecentlyChangedDirs +scoreCandidate +_countSourceFiles
65
- src/discovery/framework-detector.js +detectFrameworks +_readDeps +_readFile +_existsAnywhere
66
- src/discovery/sigmapignore.js +loadIgnorePatterns +matchesIgnorePattern
67
- src/retrieval/ranker.js +_computePenalty ~scoreFile ~rank ~buildSigIndex
61
+ src/plan/planner.js +createPlan
62
+ src/retrieval/ranker.js +_computePenalty +_computeHubs +_isHub ~scoreFile
63
+ src/session/memory.js +sessionPath +loadSession +saveSession +mergeSessionContext
68
64
  ```
69
65
 
70
66
  ## packages
@@ -185,27 +181,6 @@ function write(context, cwd, opts = {})
185
181
 
186
182
  ## src
187
183
 
188
- ### src/extractors/javascript.js
189
- ```
190
- module.exports = { extract }
191
- function extract(src) → string[]
192
- function extractBlock(src, startIndex)
193
- function extractClassMembers(block, returnHints)
194
- function buildReturnHints(src)
195
- function normalizeType(type)
196
- function formatReturnHint(type)
197
- function normalizeParams(params)
198
- ```
199
-
200
- ### src/extractors/kotlin.js
201
- ```
202
- module.exports = { extract }
203
- function extract(src) → string[]
204
- function extractBlock(src, startIndex)
205
- function extractMembers(block)
206
- function normalizeParams(params)
207
- ```
208
-
209
184
  ### src/extractors/php.js
210
185
  ```
211
186
  module.exports = { extract }
@@ -668,6 +643,21 @@ function loadIgnorePatterns(cwd)
668
643
  function matchesIgnorePattern(dirName, patterns)
669
644
  ```
670
645
 
646
+ ### src/plan/planner.js
647
+ ```
648
+ module.exports = { createPlan }
649
+ function createPlan(goal, cwd, config)
650
+ ```
651
+
652
+ ### src/mcp/server.js
653
+ ```
654
+ module.exports = { start }
655
+ function respond(id, result)
656
+ function respondError(id, code, message)
657
+ function dispatch(msg, cwd)
658
+ function start(cwd)
659
+ ```
660
+
671
661
  ### src/retrieval/ranker.js
672
662
  ```
673
663
  module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, detectIntent }
@@ -683,11 +673,12 @@ function formatRankJSON(results, query) → object
683
673
  function detectIntent(query)
684
674
  ```
685
675
 
686
- ### src/mcp/server.js
676
+ ### src/session/memory.js
687
677
  ```
688
- module.exports = { start }
689
- function respond(id, result)
690
- function respondError(id, code, message)
691
- function dispatch(msg, cwd)
692
- function start(cwd)
678
+ module.exports = { loadSession, saveSession, mergeSessionContext, clearSession }
679
+ function sessionPath(cwd)
680
+ function loadSession(cwd)
681
+ function saveSession(cwd, { intent, topFiles, query })
682
+ function mergeSessionContext(scores, session, currentIntent)
683
+ function clearSession(cwd)
693
684
  ```
package/CHANGELOG.md CHANGED
@@ -10,6 +10,24 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [6.6.1] — 2026-04-27
14
+
15
+ ### Added
16
+
17
+ - **JVM project structure support** — Added auto-detection of Java, Kotlin, and Scala project directories. `srcDirs` now includes `src/main/java`, `src/main/kotlin`, `src/main/scala`, `app/src/main/java`, `app/src/main/kotlin`, `src/test/java`, and `src/test/kotlin` for out-of-the-box support of JVM-based projects.
18
+
19
+ ---
20
+
21
+ ## [6.6.0] — 2026-04-27
22
+
23
+ ### Added
24
+
25
+ - **Session memory** — Carry context across follow-up queries within a coding session. New `src/session/memory.js` module manages session state with 4-hour TTL. Previous session's top-5 files get +0.2 score boost in next query; boost reduced to +0.1 if intent differs (topic-switch guard).
26
+ - **`sigmap ask --followup`** — Reuse previous session's context when making follow-up queries. Session automatically saved after each `ask` command for seamless context carry-forward.
27
+ - **`sigmap plan "<goal>"`** — Analyze change impact and plan modifications. Returns files grouped by confidence (inspect-first vs likely-to-change), impact radius, and affected tests. Supports `--json` output for agent integration.
28
+
29
+ ---
30
+
13
31
  ## [6.5.2] — 2026-04-27
14
32
 
15
33
  ### Added
package/gen-context.js CHANGED
@@ -5387,7 +5387,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
5387
5387
 
5388
5388
  const SERVER_INFO = {
5389
5389
  name: 'sigmap',
5390
- version: '6.5.2',
5390
+ version: '6.6.1',
5391
5391
  description: 'SigMap MCP server — code signatures on demand',
5392
5392
  };
5393
5393
 
@@ -6211,6 +6211,124 @@ __factories["./src/tracking/logger"] = function(module, exports) {
6211
6211
 
6212
6212
  };
6213
6213
 
6214
+ // ── ./src/session/memory ──
6215
+ __factories["./src/session/memory"] = function(module, exports) {
6216
+ 'use strict';
6217
+
6218
+ const fs = require('fs');
6219
+ const path = require('path');
6220
+
6221
+ const SESSION_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours — one coding session
6222
+
6223
+ function sessionPath(cwd) {
6224
+ return path.join(cwd, '.context', 'session.json');
6225
+ }
6226
+
6227
+ function loadSession(cwd) {
6228
+ const p = sessionPath(cwd);
6229
+ if (!fs.existsSync(p)) return null;
6230
+ try {
6231
+ const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
6232
+ if (Date.now() - raw.ts > SESSION_TTL_MS) return null; // expired
6233
+ return raw;
6234
+ } catch {
6235
+ return null;
6236
+ }
6237
+ }
6238
+
6239
+ function saveSession(cwd, { intent, topFiles, query }) {
6240
+ const p = sessionPath(cwd);
6241
+ fs.mkdirSync(path.dirname(p), { recursive: true });
6242
+ fs.writeFileSync(p, JSON.stringify({
6243
+ ts: Date.now(),
6244
+ intent,
6245
+ topFiles, // [{ file, score }] — top 5 from last ask
6246
+ lastQuery: query,
6247
+ }));
6248
+ }
6249
+
6250
+ function mergeSessionContext(scores, session, currentIntent) {
6251
+ if (!session) return scores;
6252
+
6253
+ const boostAmount = session.intent === currentIntent ? 0.20 : 0.10;
6254
+ const sessionBoost = new Map(session.topFiles.map(f => [f.file, boostAmount]));
6255
+
6256
+ return scores.map(r => ({
6257
+ ...r,
6258
+ score: r.score + (sessionBoost.get(r.file) || 0),
6259
+ }));
6260
+ }
6261
+
6262
+ function clearSession(cwd) {
6263
+ const p = sessionPath(cwd);
6264
+ if (fs.existsSync(p)) fs.unlinkSync(p);
6265
+ }
6266
+
6267
+ module.exports = { loadSession, saveSession, mergeSessionContext, clearSession };
6268
+ };
6269
+
6270
+ // ── ./src/plan/planner ──
6271
+ __factories["./src/plan/planner"] = function(module, exports) {
6272
+ 'use strict';
6273
+
6274
+ const { buildFromCwd } = __require('./src/graph/builder');
6275
+ const { getImpact } = __require('./src/graph/impact');
6276
+ const { buildSigIndex, rank, detectIntent } = __require('./src/retrieval/ranker');
6277
+ const { buildTestIndex, isTested } = __require('./src/extractors/coverage');
6278
+
6279
+ function createPlan(goal, cwd, config) {
6280
+ const intent = detectIntent(goal);
6281
+ const sigIndex = buildSigIndex(cwd);
6282
+ if (sigIndex.size === 0) {
6283
+ return { error: 'no context found' };
6284
+ }
6285
+
6286
+ const ranked = rank(goal, sigIndex, { topK: 15, cwd });
6287
+ const highConf = ranked.filter(r => r.confidence === 'high').slice(0, 5);
6288
+ const medConf = ranked.filter(r => r.confidence === 'medium').slice(0, 5);
6289
+
6290
+ let impact = null;
6291
+ if (highConf.length > 0) {
6292
+ const entryFile = highConf[0].file;
6293
+ try {
6294
+ const graph = buildFromCwd(cwd);
6295
+ impact = getImpact(entryFile, graph, { maxDepth: 3, cwd });
6296
+ } catch (_) {
6297
+ // Graph build failed, continue without impact
6298
+ }
6299
+ }
6300
+
6301
+ let testedFiles = [];
6302
+ try {
6303
+ const testIndex = buildTestIndex(cwd, config.testDirs || ['test', 'tests', '__tests__', 'spec']);
6304
+ testedFiles = highConf.filter(r => {
6305
+ const sigs = r.sigs || [];
6306
+ const fnNames = sigs.map(s => {
6307
+ const m = s.match(/(?:function|def|fn)\s+(\w+)/);
6308
+ return m ? m[1] : null;
6309
+ }).filter(Boolean);
6310
+ return fnNames.some(fn => isTested(fn, testIndex));
6311
+ });
6312
+ } catch (_) {
6313
+ // Coverage index failed, continue without test info
6314
+ }
6315
+
6316
+ return {
6317
+ goal,
6318
+ intent,
6319
+ inspectFirst: highConf.map(r => r.file),
6320
+ likelyToChange: medConf.map(r => r.file),
6321
+ impactRadius: impact ? {
6322
+ direct: [...(impact.direct || [])],
6323
+ transitive: [...(impact.transitive || [])],
6324
+ } : null,
6325
+ testsAffected: testedFiles.map(r => r.file),
6326
+ };
6327
+ }
6328
+
6329
+ module.exports = { createPlan };
6330
+ };
6331
+
6214
6332
  // ── ./src/retrieval/tokenizer ──
6215
6333
  __factories["./src/retrieval/tokenizer"] = function(module, exports) {
6216
6334
  'use strict';
@@ -7737,7 +7855,7 @@ const path = require('path');
7737
7855
  const os = require('os');
7738
7856
  const { execSync } = require('child_process');
7739
7857
 
7740
- const VERSION = '6.5.2';
7858
+ const VERSION = '6.6.1';
7741
7859
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
7742
7860
 
7743
7861
  function requireSourceOrBundled(key) {
@@ -9791,6 +9909,7 @@ function main() {
9791
9909
 
9792
9910
  const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');
9793
9911
  const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
9912
+ const { loadSession, saveSession, mergeSessionContext } = requireSourceOrBundled('./src/session/memory');
9794
9913
 
9795
9914
  const intent = detectIntent(query);
9796
9915
  const intentWeights = getIntentWeights(intent);
@@ -9801,7 +9920,21 @@ function main() {
9801
9920
  process.exit(1);
9802
9921
  }
9803
9922
 
9804
- const ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });
9923
+ let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });
9924
+
9925
+ // v6.8: --followup support — carry session context forward
9926
+ const isFollowup = args.includes('--followup');
9927
+ const session = isFollowup ? loadSession(cwd) : null;
9928
+ if (session) {
9929
+ ranked = mergeSessionContext(ranked, session, intent);
9930
+ ranked.sort((a, b) => b.score - a.score);
9931
+ if (!args.includes('--json')) {
9932
+ process.stderr.write(`[sigmap] ♻ followup — carrying context from: "${session.lastQuery.slice(0, 60)}"\n`);
9933
+ }
9934
+ }
9935
+
9936
+ // v6.8: Save session for future --followup calls
9937
+ saveSession(cwd, { intent, topFiles: ranked.slice(0, 5).map(r => ({ file: r.file, score: r.score })), query });
9805
9938
  const miniCtx = buildMiniContext(ranked, cwd);
9806
9939
  const outPath = path.join(cwd, '.context', 'query-context.md');
9807
9940
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
@@ -9947,6 +10080,77 @@ function main() {
9947
10080
  process.exit(0);
9948
10081
  }
9949
10082
 
10083
+ // v6.8: `sigmap plan "<goal>"` — analyze what files need changes and impact radius
10084
+ if (args[0] === 'plan') {
10085
+ const goal = args[1];
10086
+ if (!goal || goal.startsWith('--')) {
10087
+ console.error('[sigmap] Usage: sigmap plan "<goal>"');
10088
+ console.error(' Example: sigmap plan "add rate limiting to the API"');
10089
+ process.exit(1);
10090
+ }
10091
+
10092
+ const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');
10093
+
10094
+ const intent = detectIntent(goal);
10095
+ const intentWeights = getIntentWeights(intent);
10096
+
10097
+ const sigIndex = buildSigIndex(cwd);
10098
+ if (sigIndex.size === 0) {
10099
+ console.error('[sigmap] no context file found. Run: sigmap (to generate first)');
10100
+ process.exit(1);
10101
+ }
10102
+
10103
+ const ranked = rank(goal, sigIndex, { topK: 15, weights: intentWeights, cwd });
10104
+
10105
+ // Separate into confidence levels
10106
+ const highConf = ranked.filter(r => r.confidence === 'high').slice(0, 5);
10107
+ const medConf = ranked.filter(r => r.confidence === 'medium').slice(0, 5);
10108
+
10109
+ // Compute impact radius (simplified — no graph for now)
10110
+ let impact = null;
10111
+
10112
+ // Identify likely-affected tests (simplified — checks for .test. or .spec. in filename)
10113
+ const testedFiles = highConf.filter(r => /\.(test|spec)\.(js|ts|py)$|_test\.(js|ts|py)$/.test(r.file));
10114
+
10115
+ if (args.includes('--json')) {
10116
+ process.stdout.write(JSON.stringify({
10117
+ goal, intent,
10118
+ inspectFirst: highConf.map(r => r.file),
10119
+ likelyToChange: medConf.map(r => r.file),
10120
+ impactRadius: impact,
10121
+ testsAffected: testedFiles.map(r => r.file),
10122
+ }, null, 2) + '\n');
10123
+ } else {
10124
+ const bar = '─'.repeat(50);
10125
+ console.log(bar);
10126
+ console.log(` sigmap plan "${goal}"`);
10127
+ console.log(` Intent : ${intent}`);
10128
+ console.log(bar);
10129
+ console.log('');
10130
+ console.log(' Inspect first (highest relevance):');
10131
+ if (highConf.length === 0) {
10132
+ console.log(' (no files found)');
10133
+ } else {
10134
+ highConf.forEach((r, i) => console.log(` ${i + 1}. ${path.relative(cwd, r.file)}`));
10135
+ }
10136
+ console.log('');
10137
+ console.log(' Likely to change:');
10138
+ if (medConf.length === 0) {
10139
+ console.log(' (no files found)');
10140
+ } else {
10141
+ medConf.forEach((r, i) => console.log(` ${i + 1}. ${path.relative(cwd, r.file)}`));
10142
+ }
10143
+ if (testedFiles.length > 0) {
10144
+ console.log('');
10145
+ console.log(' Tests to run after change:');
10146
+ testedFiles.forEach(r => console.log(` • ${path.relative(cwd, r.file)}`));
10147
+ }
10148
+ console.log('');
10149
+ console.log(bar);
10150
+ }
10151
+ process.exit(0);
10152
+ }
10153
+
9950
10154
  // v5.9: `sigmap bench --submit` — format local benchmark history as a shareable community block
9951
10155
  if (args[0] === 'bench' && args.includes('--submit')) {
9952
10156
  const versionMeta = (() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "6.5.2",
3
+ "version": "6.6.1",
4
4
  "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
5
  "main": "gen-context.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "6.5.2",
3
+ "version": "6.6.1",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "6.5.2",
3
+ "version": "6.6.1",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -26,6 +26,10 @@ const DEFAULTS = {
26
26
  'pages', 'components', 'hooks', 'routes', 'controllers',
27
27
  'models', 'views', 'resources', 'config', 'db',
28
28
  'projects', 'apps', 'libs', 'instance', 'blueprints',
29
+ // JVM project structures (Java, Kotlin, Scala)
30
+ 'src/main/java', 'src/main/kotlin', 'src/main/scala',
31
+ 'app/src/main/java', 'app/src/main/kotlin',
32
+ 'src/test/java', 'src/test/kotlin',
29
33
  ],
30
34
 
31
35
  // Directory/file names to exclude entirely
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '6.5.2',
21
+ version: '6.6.1',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { buildFromCwd } = require('../graph/builder');
6
+ const { getImpact } = require('../graph/impact');
7
+ const { buildSigIndex, rank, detectIntent } = require('../retrieval/ranker');
8
+ const { buildTestIndex, isTested } = require('../extractors/coverage');
9
+
10
+ module.exports = { createPlan };
11
+
12
+ function createPlan(goal, cwd, config) {
13
+ // Step 1: Detect intent and rank files for the goal
14
+ const intent = detectIntent(goal);
15
+ const sigIndex = buildSigIndex(cwd);
16
+ if (sigIndex.size === 0) {
17
+ return { error: 'no context found' };
18
+ }
19
+
20
+ const ranked = rank(goal, sigIndex, { topK: 15, cwd });
21
+
22
+ // Step 2: Separate into confidence levels
23
+ const highConf = ranked.filter(r => r.confidence === 'high').slice(0, 5);
24
+ const medConf = ranked.filter(r => r.confidence === 'medium').slice(0, 5);
25
+
26
+ // Step 3: Compute impact radius for highest-confidence file
27
+ let impact = null;
28
+ if (highConf.length > 0) {
29
+ const entryFile = highConf[0].file;
30
+ try {
31
+ const graph = buildFromCwd(cwd);
32
+ impact = getImpact(entryFile, graph, { maxDepth: 3, cwd });
33
+ } catch (_) {
34
+ // Graph build failed, continue without impact
35
+ }
36
+ }
37
+
38
+ // Step 4: Identify likely-affected tests
39
+ let testedFiles = [];
40
+ try {
41
+ const testIndex = buildTestIndex(cwd, config.testDirs || ['test', 'tests', '__tests__', 'spec']);
42
+ testedFiles = highConf.filter(r => {
43
+ const sigs = r.sigs || [];
44
+ const fnNames = sigs.map(s => {
45
+ const m = s.match(/(?:function|def|fn)\s+(\w+)/);
46
+ return m ? m[1] : null;
47
+ }).filter(Boolean);
48
+ return fnNames.some(fn => isTested(fn, testIndex));
49
+ });
50
+ } catch (_) {
51
+ // Coverage index failed, continue without test info
52
+ }
53
+
54
+ return {
55
+ goal,
56
+ intent,
57
+ inspectFirst: highConf.map(r => r.file),
58
+ likelyToChange: medConf.map(r => r.file),
59
+ impactRadius: impact ? {
60
+ direct: [...(impact.direct || [])],
61
+ transitive: [...(impact.transitive || [])],
62
+ } : null,
63
+ testsAffected: testedFiles.map(r => r.file),
64
+ };
65
+ }
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ module.exports = { loadSession, saveSession, mergeSessionContext, clearSession };
7
+
8
+ const SESSION_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours — one coding session
9
+
10
+ function sessionPath(cwd) {
11
+ return path.join(cwd, '.context', 'session.json');
12
+ }
13
+
14
+ function loadSession(cwd) {
15
+ const p = sessionPath(cwd);
16
+ if (!fs.existsSync(p)) return null;
17
+ try {
18
+ const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
19
+ if (Date.now() - raw.ts > SESSION_TTL_MS) return null; // expired
20
+ return raw;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ function saveSession(cwd, { intent, topFiles, query }) {
27
+ const p = sessionPath(cwd);
28
+ fs.mkdirSync(path.dirname(p), { recursive: true });
29
+ fs.writeFileSync(p, JSON.stringify({
30
+ ts: Date.now(),
31
+ intent,
32
+ topFiles, // [{ file, score }] — top 5 from last ask
33
+ lastQuery: query,
34
+ }));
35
+ }
36
+
37
+ // Merge session context as additional ranking signal:
38
+ // any file that appeared in the previous session's top-5 gets a +0.2 boost
39
+ // Reduce to +0.1 if the intent has changed (topic switch guard)
40
+ function mergeSessionContext(scores, session, currentIntent) {
41
+ if (!session) return scores;
42
+
43
+ const boostAmount = session.intent === currentIntent ? 0.20 : 0.10;
44
+ const sessionBoost = new Map(session.topFiles.map(f => [f.file, boostAmount]));
45
+
46
+ return scores.map(r => ({
47
+ ...r,
48
+ // Additive boost — cannot reduce a score
49
+ score: r.score + (sessionBoost.get(r.file) || 0),
50
+ }));
51
+ }
52
+
53
+ function clearSession(cwd) {
54
+ const p = sessionPath(cwd);
55
+ if (fs.existsSync(p)) fs.unlinkSync(p);
56
+ }