sigmap 8.29.0 → 8.31.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/CHANGELOG.md +24 -0
- package/README.md +2 -2
- package/gen-context.js +418 -30
- package/llms-full.txt +4 -2
- package/llms.txt +2 -2
- package/package.json +3 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/extractors/java.js +20 -4
- package/src/graph/builder.js +39 -3
- package/src/graph/call-graph.js +216 -13
- package/src/mcp/install.js +29 -2
- package/src/mcp/server.js +1 -1
- package/src/retrieval/ranker.js +35 -4
- package/src/skills/skills.js +26 -2
package/src/retrieval/ranker.js
CHANGED
|
@@ -72,26 +72,52 @@ const PENALTY_SIGNALS = {
|
|
|
72
72
|
generatedCode: 0.3, // dist/build/.next in path
|
|
73
73
|
docsFile: 0.2, // docs/doc/README in path
|
|
74
74
|
nodeModules: 0.0, // node_modules (zero score)
|
|
75
|
+
dataHolder: 0.3, // generated POJO/entity: almost entirely accessors
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
// A file whose members are overwhelmingly trivial accessors is a data holder,
|
|
79
|
+
// not logic. Path-based detection cannot see these: generated JPA/MyBatis
|
|
80
|
+
// entities live in ordinary source trees. They match a query on any column
|
|
81
|
+
// name they happen to carry (`getNote`/`setNote` matches "note" as strongly as
|
|
82
|
+
// the service that actually implements order notes), so on an entity-heavy
|
|
83
|
+
// repo they crowd real code out of the top results.
|
|
84
|
+
const ACCESSOR_RE = /^\s*(get|set|is)[A-Z]\w*\s*\(/;
|
|
85
|
+
const DATA_HOLDER_RATIO = 0.8;
|
|
86
|
+
const DATA_HOLDER_MIN_MEMBERS = 6;
|
|
87
|
+
|
|
77
88
|
// Query terms that mean the penalised category IS the target. Read from the
|
|
78
89
|
// query tokens directly, NOT via detectIntent: that classifier is first-match-
|
|
79
90
|
// wins over its pattern object, and `debug` precedes `test`, so "fix the failing
|
|
80
91
|
// test" classifies as debug and never reaches the test branch.
|
|
81
92
|
const WANTS_TESTS = new Set(['test', 'tests', 'spec', 'specs', 'unit', 'integration', 'e2e', 'assertion', 'assert', 'mock', 'fixture', 'coverage', 'testing']);
|
|
82
93
|
const WANTS_DOCS = new Set(['doc', 'docs', 'documentation', 'readme', 'changelog', 'guide', 'tutorial']);
|
|
94
|
+
const WANTS_MODELS = new Set(['entity', 'entities', 'model', 'models', 'pojo', 'dto', 'bean', 'getter', 'getters', 'setter', 'setters', 'accessor', 'accessors', 'field', 'fields', 'column', 'columns', 'schema']);
|
|
83
95
|
|
|
84
96
|
/** Which penalised categories the query is explicitly asking for. */
|
|
85
97
|
function _queryWants(queryTokens) {
|
|
86
|
-
const wants = { tests: false, docs: false };
|
|
98
|
+
const wants = { tests: false, docs: false, models: false };
|
|
87
99
|
for (const t of queryTokens || []) {
|
|
88
100
|
if (WANTS_TESTS.has(t)) wants.tests = true;
|
|
89
101
|
if (WANTS_DOCS.has(t)) wants.docs = true;
|
|
102
|
+
if (WANTS_MODELS.has(t)) wants.models = true;
|
|
90
103
|
}
|
|
91
104
|
return wants;
|
|
92
105
|
}
|
|
93
106
|
|
|
94
|
-
|
|
107
|
+
/**
|
|
108
|
+
* True when a file's members are overwhelmingly trivial accessors — a generated
|
|
109
|
+
* entity or POJO rather than logic. Type declarations are excluded from the
|
|
110
|
+
* ratio so a small class is not misjudged by its own `class X` line.
|
|
111
|
+
*/
|
|
112
|
+
function _isDataHolder(sigs) {
|
|
113
|
+
if (!Array.isArray(sigs)) return false;
|
|
114
|
+
const members = sigs.filter((line) => /^\s/.test(line) || !/^(class|interface|enum|struct|function|module\.exports)\b/.test(line));
|
|
115
|
+
if (members.length < DATA_HOLDER_MIN_MEMBERS) return false;
|
|
116
|
+
const accessors = members.filter((line) => ACCESSOR_RE.test(line)).length;
|
|
117
|
+
return accessors / members.length >= DATA_HOLDER_RATIO;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _computePenalty(filePath, wants, sigs) {
|
|
95
121
|
const pathLower = filePath.toLowerCase();
|
|
96
122
|
if (pathLower.includes('node_modules')) return PENALTY_SIGNALS.nodeModules;
|
|
97
123
|
// A penalty must never fire on the very thing the user asked for. Before
|
|
@@ -104,6 +130,11 @@ function _computePenalty(filePath, wants) {
|
|
|
104
130
|
if (/(^|\/)(docs|doc|readme|changelog)($|\/)/.test(pathLower)) {
|
|
105
131
|
return (wants && wants.docs) ? 1.0 : PENALTY_SIGNALS.docsFile;
|
|
106
132
|
}
|
|
133
|
+
// Content-based, and last: a data holder is still a real source file, so it
|
|
134
|
+
// is only demoted once the path-based categories have had their say.
|
|
135
|
+
if (_isDataHolder(sigs)) {
|
|
136
|
+
return (wants && wants.models) ? 1.0 : PENALTY_SIGNALS.dataHolder;
|
|
137
|
+
}
|
|
107
138
|
return 1.0;
|
|
108
139
|
}
|
|
109
140
|
|
|
@@ -161,7 +192,7 @@ function scoreFile(filePath, sigs, queryTokens, weights, wants) {
|
|
|
161
192
|
if (!sigs || sigs.length === 0) return { score: 0, signals: { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: 1.0 } };
|
|
162
193
|
|
|
163
194
|
const w = weights || DEFAULT_WEIGHTS;
|
|
164
|
-
const signals = { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: _computePenalty(filePath, wants) };
|
|
195
|
+
const signals = { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: _computePenalty(filePath, wants, sigs) };
|
|
165
196
|
|
|
166
197
|
// Module-doc prose is excluded here on purpose. This signal measures overlap
|
|
167
198
|
// with DECLARED IDENTIFIERS; prose relevance is BM25's job, where it is scored
|
|
@@ -757,4 +788,4 @@ function detectIntent(query) {
|
|
|
757
788
|
return detectIntents(query)[0];
|
|
758
789
|
}
|
|
759
790
|
|
|
760
|
-
module.exports = { rank, buildSigIndex, scoreFile, _queryWants, detectIntents, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, CENTRALITY_BLEND_WEIGHT, detectIntent };
|
|
791
|
+
module.exports = { rank, buildSigIndex, scoreFile, _queryWants, _isDataHolder, detectIntents, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, CENTRALITY_BLEND_WEIGHT, detectIntent };
|
package/src/skills/skills.js
CHANGED
|
@@ -27,13 +27,31 @@ const SKILLS = {
|
|
|
27
27
|
'Follow this loop before any file exploration in a repo with SigMap installed.',
|
|
28
28
|
'',
|
|
29
29
|
'1. **Ask before reading.** `sigmap ask "<task>"` (or the `query_context` MCP tool) ranks the relevant files as ~hundreds of tokens of signatures instead of thousands of raw-file tokens. Never open files to "look around".',
|
|
30
|
-
'2. **Read ranges, not files.** Use the `get_lines` MCP tool with the `:start-end` line anchors carried on every signature to pull only the lines you need.',
|
|
30
|
+
'2. **Read ranges, not files.** Use the `get_lines` MCP tool — or `sigmap lines <file> :<line> --context <n>` where MCP is unavailable — with the `:start-end` line anchors carried on every signature to pull only the lines you need.',
|
|
31
31
|
'3. **Ground before trusting.** Run the `verify_suggestion` MCP tool (or `sigmap verify-ai-output`) on generated code before applying it — it flags fabricated files, imports, symbols, and npm scripts against the live index.',
|
|
32
32
|
'4. **Squeeze big pastes.** Any stack trace, CI/build log, or JSON blob goes through `sigmap squeeze` (or the `squeeze_output` MCP tool) before it enters context — the signal survives, the noise does not.',
|
|
33
33
|
'5. **Checkpoint progress.** Use the `create_checkpoint` MCP tool or `sigmap note "<decision>"` so a follow-up session resumes without re-deriving state.',
|
|
34
34
|
'6. **Watch the budget.** Check the `get_budget` MCP tool or `sigmap budget` (estimates from SigMap\'s local ledger — no LLM calls). Near the budget: summarize-then-drop older context instead of accumulating, and prefer terse output.',
|
|
35
35
|
].join('\n'),
|
|
36
36
|
},
|
|
37
|
+
'sigmap-task': {
|
|
38
|
+
title: 'SigMap task loop',
|
|
39
|
+
kind: 'prompt',
|
|
40
|
+
description: 'Do a coding task grounded in SigMap: look up before reading, edit by line anchor, verify before reporting.',
|
|
41
|
+
argumentHint: 'the change you want, in plain words',
|
|
42
|
+
body: [
|
|
43
|
+
'Work through these steps **in order**. Do not open any file before step 2.',
|
|
44
|
+
'Every command runs from the integrated terminal — do not ask the user to run them for you.',
|
|
45
|
+
'',
|
|
46
|
+
'1. **Look up, do not search.** `npx sigmap ask "<the task>"` — this writes `.context/query-context.md`.',
|
|
47
|
+
'2. **Read the map.** `cat .context/query-context.md`. It ranks the relevant files and lists their signatures with `:start-end` line anchors — a few hundred tokens where the same files read whole are tens of thousands. Say which files it surfaced before continuing. If nothing relevant appears, re-run step 1 with different wording; fall back to search only after two attempts, and say so.',
|
|
48
|
+
'3. **Read the anchored range, by command.** A signature ending `:425-425` means line 425, not the 547-line file. Run `npx sigmap lines <file> :425 --context 10` — paste the anchor straight off the signature. Never `cat` a whole file when you hold an anchor for it: on a real repo a 220-line span costs ~2,700 tokens where the anchored window costs ~220.',
|
|
49
|
+
'4. **Make the change.** Follow the conventions visible in the signatures — same layering, same response wrapper, same annotation style. Add no dependencies.',
|
|
50
|
+
'5. **Verify before reporting.** Write what you changed to `.sigmap-notes.md`, naming every file by its **full repository-relative path** (a bare filename is reported as fake), then run `npx sigmap verify-ai-output .sigmap-notes.md`. It checks every name against the real index, offline, with no model call. Fix anything it flags and re-run before you reply.',
|
|
51
|
+
'6. **Refresh the map.** `npx sigmap` — your edits made it stale.',
|
|
52
|
+
'7. **Report.** The files you changed, the ranges you actually read, the step-1 token count, and the step-5 verify result. Say so if you fell back to searching or if verify flagged something.',
|
|
53
|
+
].join('\n'),
|
|
54
|
+
},
|
|
37
55
|
'sigmap-config-optimizer': {
|
|
38
56
|
title: 'SigMap config optimizer',
|
|
39
57
|
description: 'Playbook for getting a correct SigMap config on any repo: detect with sigmap tune, review the per-change reasons, apply, validate.',
|
|
@@ -58,7 +76,9 @@ const SKILL_CLIENTS = {
|
|
|
58
76
|
windsurf: { label: 'Windsurf', parent: ['.windsurf'],
|
|
59
77
|
target: (cwd, skill) => path.join(cwd, '.windsurf', 'rules', `${skill}.md`) },
|
|
60
78
|
copilot: { label: 'GitHub Copilot', parent: ['.github'],
|
|
61
|
-
target: (cwd, skill) =>
|
|
79
|
+
target: (cwd, skill) => (SKILLS[skill] && SKILLS[skill].kind === 'prompt'
|
|
80
|
+
? path.join(cwd, '.github', 'prompts', `${skill}.prompt.md`)
|
|
81
|
+
: path.join(cwd, '.github', 'instructions', `${skill}.instructions.md`)) },
|
|
62
82
|
codex: { label: 'Codex CLI (AGENTS.md)', parent: ['AGENTS.md'],
|
|
63
83
|
target: (cwd) => path.join(cwd, 'AGENTS.md'), inject: true },
|
|
64
84
|
};
|
|
@@ -79,6 +99,10 @@ function renderSkill(client, skillName, version) {
|
|
|
79
99
|
return `---\ndescription: ${skill.description}\nalwaysApply: false\n---\n\n${body}`;
|
|
80
100
|
}
|
|
81
101
|
if (client === 'copilot') {
|
|
102
|
+
if (skill.kind === 'prompt') {
|
|
103
|
+
return `---\nname: ${skillName}\nagent: 'agent'\ndescription: ${skill.description}\n`
|
|
104
|
+
+ `argument-hint: ${skill.argumentHint}\n---\n\n${body}`;
|
|
105
|
+
}
|
|
82
106
|
return `---\napplyTo: "**"\n---\n\n${body}`;
|
|
83
107
|
}
|
|
84
108
|
return body; // windsurf: plain markdown
|