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.
@@ -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
- function _computePenalty(filePath, wants) {
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 };
@@ -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) => path.join(cwd, '.github', 'instructions', `${skill}.instructions.md`) },
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