sigmap 7.8.0 → 7.10.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 +18 -0
- package/gen-context.js +287 -2
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/conventions/inject.js +98 -0
- package/src/mcp/server.js +1 -1
- package/src/scaffold/propose.js +112 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,24 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [7.10.0] — 2026-06-17
|
|
14
|
+
|
|
15
|
+
Minor release — `sigmap scaffold` with a confidence floor (grounded codegen, Layer 4).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`sigmap scaffold <name>` — convention-matched proposal with a confidence floor (#307):** the first slice of Layer 4 (Cause 3 — guessing structure for new code). Proposes a convention-matched structure for a new module — filename in the repo's dominant naming style, the export style to use, and a matching test file — but **only when the conventions are consistent enough**. New zero-dependency, bundle-safe `src/scaffold/propose.js` (`proposeScaffold`): the governing confidence is file-naming consistency, with a soft threshold (default 0.70, overridable via `--threshold`) and a **non-overridable hard floor of 0.50**. Below the threshold it refuses and surfaces the conflict (reusing `analyzeConflicts`); `--force` allows a proposal between the floor and the threshold (flagged with a warning), but never below the floor — a wrong proposal systematizes bad code. CLI supports `--ext`, `--threshold`, `--force`, and `--json` (refusal exits 1). Scaffold persistence (`.sigmap/scaffold/latest.md`), `--naming-pattern` override, and the `verify-plan` → `create` → `review-pr` pipeline remain follow-ups.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [7.9.0] — 2026-06-17
|
|
23
|
+
|
|
24
|
+
Minor release — `sigmap conventions --inject` (grounded codegen, Layer 3).
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **`sigmap conventions --inject` — write the conventions block into CLAUDE.md (#304):** the next slice of Layer 3, completing the "agent sees the house style" link in the grounded-creation loop. Renders the detected conventions (file naming, export style, test framework — each with its dominant pattern and consistency tier) into a marker-delimited block and injects it into `CLAUDE.md`, creating the file if absent. New zero-dependency, bundle-safe `src/conventions/inject.js` (`renderConventionsBlock`, `injectConventions`): the injection is idempotent and marker-scoped (`<!-- sigmap-conventions:start -->` … `:end -->`), preserving all human content and coexisting with the existing `## Auto-generated signatures` block. `--report`, `--fix`, `--update`, `--ci`, and Layer 4 scaffold remain follow-ups.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
13
31
|
## [7.8.0] — 2026-06-17
|
|
14
32
|
|
|
15
33
|
Minor release — `sigmap conventions --conflicts` (grounded codegen, Layer 3).
|
package/gen-context.js
CHANGED
|
@@ -23,6 +23,224 @@ function __require(key) {
|
|
|
23
23
|
// ── ./src/cache/freshen ──
|
|
24
24
|
// ── ./src/conventions/extract ──
|
|
25
25
|
// ── ./src/conventions/conflicts ──
|
|
26
|
+
// ── ./src/conventions/inject ──
|
|
27
|
+
// ── ./src/scaffold/propose ──
|
|
28
|
+
__factories["./src/scaffold/propose"] = function(module, exports) {
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Scaffold proposal with a confidence floor (IMPL.md §5 — Cause 3).
|
|
32
|
+
*
|
|
33
|
+
* Proposes a convention-matched structure for a new module — filename in the
|
|
34
|
+
* repo's dominant naming style, the export style to use, and a matching test
|
|
35
|
+
* file — but only when the conventions are consistent enough. Below a hard
|
|
36
|
+
* floor it refuses and surfaces the conflict, because a wrong proposal
|
|
37
|
+
* systematizes bad code. Pure, zero-dependency, bundle-safe; reuses the
|
|
38
|
+
* conventions primitives.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const { toNamingStyle, analyzeConflicts } = __require('./src/conventions/conflicts');
|
|
42
|
+
|
|
43
|
+
// Soft threshold is configurable; the hard floor is not (IMPL.md §5.1).
|
|
44
|
+
const DEFAULT_THRESHOLD = 0.7;
|
|
45
|
+
const HARD_FLOOR = 0.5;
|
|
46
|
+
|
|
47
|
+
/** Tier for a consistency score (matches the conventions tiers). */
|
|
48
|
+
function _tier(pct) {
|
|
49
|
+
if (pct >= 0.9) return 'consistent';
|
|
50
|
+
if (pct >= 0.7) return 'mostly';
|
|
51
|
+
return 'inconsistent';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Strip any extension/compound suffix from a requested name → bare stem. */
|
|
55
|
+
function _stem(name) {
|
|
56
|
+
const s = String(name || '').trim();
|
|
57
|
+
const slash = s.lastIndexOf('/');
|
|
58
|
+
const base = slash >= 0 ? s.slice(slash + 1) : s;
|
|
59
|
+
const dot = base.indexOf('.');
|
|
60
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Test file path for a styled stem given the detected framework + ext. */
|
|
64
|
+
function _testFile(styledStem, framework, ext) {
|
|
65
|
+
if (framework === 'pytest' || framework === 'unittest') {
|
|
66
|
+
return `test_${toNamingStyle(styledStem, 'snake_case')}.py`;
|
|
67
|
+
}
|
|
68
|
+
return `${styledStem}.test.${ext}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Propose a convention-matched scaffold, gated by a confidence floor.
|
|
73
|
+
* @param {string} name desired module name (any casing; extension ignored)
|
|
74
|
+
* @param {object} conventions an `extractConventions` result
|
|
75
|
+
* @param {object} [opts]
|
|
76
|
+
* @param {number} [opts.threshold=0.7] soft threshold (clamped to ≥ hard floor)
|
|
77
|
+
* @param {boolean} [opts.force=false] allow proposing below the soft threshold
|
|
78
|
+
* (never below the hard floor)
|
|
79
|
+
* @param {string} [opts.ext='js'] file extension for the proposed files
|
|
80
|
+
* @returns {{ ok:boolean, refused:boolean, name:string, tier:string,
|
|
81
|
+
* confidence:number, threshold:number, hardFloor:number, forced:boolean,
|
|
82
|
+
* warning:string|null, reason:string, proposal:object|null, conflicts:object }}
|
|
83
|
+
*/
|
|
84
|
+
function proposeScaffold(name, conventions, opts = {}) {
|
|
85
|
+
const threshold = Math.max(HARD_FLOOR, opts.threshold != null ? opts.threshold : DEFAULT_THRESHOLD);
|
|
86
|
+
const force = !!opts.force;
|
|
87
|
+
const ext = opts.ext || 'js';
|
|
88
|
+
const fileNaming = (conventions && conventions.fileNaming) || { dominant: null, dominantPct: 0, total: 0 };
|
|
89
|
+
const exportStyle = (conventions && conventions.exportStyle) || { dominant: null };
|
|
90
|
+
const confidence = fileNaming.dominantPct || 0;
|
|
91
|
+
const tier = fileNaming.total > 0 ? _tier(confidence) : 'unknown';
|
|
92
|
+
const conflicts = analyzeConflicts(conventions || {});
|
|
93
|
+
|
|
94
|
+
const base = {
|
|
95
|
+
ok: false, refused: true, name: String(name || ''), tier, confidence,
|
|
96
|
+
threshold, hardFloor: HARD_FLOOR, forced: false, warning: null,
|
|
97
|
+
reason: '', proposal: null, conflicts,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (!fileNaming.dominant || fileNaming.total === 0) {
|
|
101
|
+
return { ...base, reason: 'no file-naming convention detected — cannot propose a name' };
|
|
102
|
+
}
|
|
103
|
+
if (confidence < HARD_FLOOR) {
|
|
104
|
+
return {
|
|
105
|
+
...base,
|
|
106
|
+
reason: `file-naming consistency ${(confidence * 100).toFixed(0)}% is below the hard floor ${(HARD_FLOOR * 100).toFixed(0)}% — refusing (not overridable)`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (confidence < threshold && !force) {
|
|
110
|
+
return {
|
|
111
|
+
...base,
|
|
112
|
+
reason: `file-naming consistency ${(confidence * 100).toFixed(0)}% is below the threshold ${(threshold * 100).toFixed(0)}% — refusing (use --force to override above the ${(HARD_FLOOR * 100).toFixed(0)}% floor)`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const styledStem = toNamingStyle(_stem(name), fileNaming.dominant);
|
|
117
|
+
const forced = confidence < threshold && force;
|
|
118
|
+
const proposal = {
|
|
119
|
+
filename: `${styledStem}.${ext}`,
|
|
120
|
+
namingStyle: fileNaming.dominant,
|
|
121
|
+
exportStyle: exportStyle.dominant || 'named',
|
|
122
|
+
testFile: _testFile(styledStem, conventions.testFramework, ext),
|
|
123
|
+
testFramework: conventions.testFramework || null,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...base,
|
|
128
|
+
ok: true,
|
|
129
|
+
refused: false,
|
|
130
|
+
forced,
|
|
131
|
+
warning: forced
|
|
132
|
+
? `proposed below the ${(threshold * 100).toFixed(0)}% threshold (--force); conventions are only ${tier}`
|
|
133
|
+
: null,
|
|
134
|
+
reason: forced ? 'forced proposal above the hard floor' : `conventions are ${tier} — proposing`,
|
|
135
|
+
proposal,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { proposeScaffold, DEFAULT_THRESHOLD, HARD_FLOOR };
|
|
140
|
+
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
__factories["./src/conventions/inject"] = function(module, exports) {
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* CLAUDE.md convention injection (IMPL.md §7 Phase 2 / §8 step 5).
|
|
147
|
+
*
|
|
148
|
+
* Renders the conventions detected by `extractConventions` into a
|
|
149
|
+
* marker-delimited markdown block and injects it into CLAUDE.md so an agent
|
|
150
|
+
* reading the file plans grounded in the repo's house style. Idempotent and
|
|
151
|
+
* marker-scoped — it never touches human content or the `## Auto-generated
|
|
152
|
+
* signatures` block. Pure string transforms; zero-dependency, bundle-safe.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
const START = '<!-- sigmap-conventions:start -->';
|
|
156
|
+
const END = '<!-- sigmap-conventions:end -->';
|
|
157
|
+
|
|
158
|
+
const TIER_NOTE = {
|
|
159
|
+
consistent: 'consistent — match it',
|
|
160
|
+
mostly: 'dominant, with some drift',
|
|
161
|
+
inconsistent: 'no clear convention — check neighboring files',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const NAMES = {
|
|
165
|
+
fileNaming: 'File naming',
|
|
166
|
+
exportStyle: 'Export style',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const _pct = (n) => `${Math.round(n * 100)}%`;
|
|
170
|
+
|
|
171
|
+
function _conventionLine(label, conv) {
|
|
172
|
+
if (!conv || conv.total === 0 || !conv.dominant) return null;
|
|
173
|
+
const note = TIER_NOTE[conv.tier] || conv.tier;
|
|
174
|
+
let line = `- **${label}:** ${conv.dominant} (${_pct(conv.dominantPct)} — ${note}).`;
|
|
175
|
+
const others = conv.variants.slice(1).filter((v) => v.pct > 0);
|
|
176
|
+
if (others.length) {
|
|
177
|
+
line += ` Variants: ${others.map((v) => `${v.label} ${_pct(v.pct)}`).join(', ')}.`;
|
|
178
|
+
}
|
|
179
|
+
return line;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Render the conventions block (including its start/end markers).
|
|
184
|
+
* @param {object} result an `extractConventions` result
|
|
185
|
+
* @param {string} [version] SigMap version for the footer
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
function renderConventionsBlock(result, version) {
|
|
189
|
+
const lines = [];
|
|
190
|
+
for (const key of ['fileNaming', 'exportStyle']) {
|
|
191
|
+
const l = _conventionLine(NAMES[key], result && result[key]);
|
|
192
|
+
if (l) lines.push(l);
|
|
193
|
+
}
|
|
194
|
+
if (result && result.testFramework) {
|
|
195
|
+
lines.push(`- **Test framework:** ${result.testFramework}.`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const body = lines.length
|
|
199
|
+
? lines
|
|
200
|
+
: ['- No conventions detected yet (run `sigmap conventions` on a TS/JS/Python repo).'];
|
|
201
|
+
|
|
202
|
+
const ver = version ? ` v${version}` : '';
|
|
203
|
+
return [
|
|
204
|
+
START,
|
|
205
|
+
'## Conventions (auto-detected by SigMap)',
|
|
206
|
+
'',
|
|
207
|
+
'Match these when writing or editing code (TS/JS/Python):',
|
|
208
|
+
'',
|
|
209
|
+
...body,
|
|
210
|
+
'',
|
|
211
|
+
`<sub>Generated by SigMap${ver} · run \`sigmap conventions --inject\` to refresh.</sub>`,
|
|
212
|
+
END,
|
|
213
|
+
].join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Inject (or replace) the conventions block in existing CLAUDE.md content.
|
|
218
|
+
* Replaces an existing marked block in place; appends one when absent.
|
|
219
|
+
* Idempotent — never touches content outside the markers.
|
|
220
|
+
* @param {string} existing current file content ('' if the file is new)
|
|
221
|
+
* @param {string} block the block returned by `renderConventionsBlock`
|
|
222
|
+
* @returns {string}
|
|
223
|
+
*/
|
|
224
|
+
function injectConventions(existing, block) {
|
|
225
|
+
const src = String(existing || '');
|
|
226
|
+
const startIdx = src.indexOf(START);
|
|
227
|
+
if (startIdx !== -1) {
|
|
228
|
+
const endIdx = src.indexOf(END, startIdx);
|
|
229
|
+
if (endIdx !== -1) {
|
|
230
|
+
const before = src.slice(0, startIdx);
|
|
231
|
+
const after = src.slice(endIdx + END.length);
|
|
232
|
+
return before + block + after;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (src.trim() === '') return block + '\n';
|
|
236
|
+
const sep = src.endsWith('\n') ? '\n' : '\n\n';
|
|
237
|
+
return src + sep + block + '\n';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = { renderConventionsBlock, injectConventions, START, END };
|
|
241
|
+
|
|
242
|
+
};
|
|
243
|
+
|
|
26
244
|
__factories["./src/conventions/conflicts"] = function(module, exports) {
|
|
27
245
|
|
|
28
246
|
/**
|
|
@@ -6901,7 +7119,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
6901
7119
|
|
|
6902
7120
|
const SERVER_INFO = {
|
|
6903
7121
|
name: 'sigmap',
|
|
6904
|
-
version: '7.
|
|
7122
|
+
version: '7.10.0',
|
|
6905
7123
|
description: 'SigMap MCP server — code signatures on demand',
|
|
6906
7124
|
};
|
|
6907
7125
|
|
|
@@ -12579,7 +12797,7 @@ function __tryGit(args, opts = {}) {
|
|
|
12579
12797
|
catch (_) { return ''; }
|
|
12580
12798
|
}
|
|
12581
12799
|
|
|
12582
|
-
const VERSION = '7.
|
|
12800
|
+
const VERSION = '7.10.0';
|
|
12583
12801
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
12584
12802
|
|
|
12585
12803
|
function requireSourceOrBundled(key) {
|
|
@@ -15757,6 +15975,23 @@ function main() {
|
|
|
15757
15975
|
const files = buildFileList(cwd, config);
|
|
15758
15976
|
const result = extractConventions(cwd, files);
|
|
15759
15977
|
|
|
15978
|
+
// `--inject`: write/update the conventions block in CLAUDE.md (idempotent).
|
|
15979
|
+
if (args.includes('--inject')) {
|
|
15980
|
+
const { renderConventionsBlock, injectConventions } = requireSourceOrBundled('./src/conventions/inject');
|
|
15981
|
+
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
15982
|
+
let existing = '';
|
|
15983
|
+
try { if (fs.existsSync(claudePath)) existing = fs.readFileSync(claudePath, 'utf8'); } catch (_) {}
|
|
15984
|
+
const block = renderConventionsBlock(result, VERSION);
|
|
15985
|
+
try {
|
|
15986
|
+
fs.writeFileSync(claudePath, injectConventions(existing, block));
|
|
15987
|
+
} catch (e) {
|
|
15988
|
+
console.error(`[sigmap] cannot write ${claudePath}: ${e.message}`);
|
|
15989
|
+
process.exit(1);
|
|
15990
|
+
}
|
|
15991
|
+
console.log(`[sigmap] conventions → injected block into ${path.relative(cwd, claudePath) || 'CLAUDE.md'}`);
|
|
15992
|
+
process.exit(0);
|
|
15993
|
+
}
|
|
15994
|
+
|
|
15760
15995
|
// `--conflicts`: surface why a convention is mixed (breakdown + rename suggestions).
|
|
15761
15996
|
if (args.includes('--conflicts')) {
|
|
15762
15997
|
const { analyzeConflicts } = requireSourceOrBundled('./src/conventions/conflicts');
|
|
@@ -15822,6 +16057,56 @@ function main() {
|
|
|
15822
16057
|
process.exit(0);
|
|
15823
16058
|
}
|
|
15824
16059
|
|
|
16060
|
+
// Layer 4: `sigmap scaffold <name>` — propose a convention-matched structure
|
|
16061
|
+
// for a new module, gated by a confidence floor (refuses if conventions are
|
|
16062
|
+
// too inconsistent; a wrong proposal systematizes bad code).
|
|
16063
|
+
if (args[0] === 'scaffold') {
|
|
16064
|
+
const jsonOut = args.includes('--json');
|
|
16065
|
+
const name = args[1] && !args[1].startsWith('--') ? args[1] : null;
|
|
16066
|
+
if (!name) {
|
|
16067
|
+
console.error('[sigmap] usage: sigmap scaffold <name> [--ext <e>] [--threshold <n>] [--force] [--json]');
|
|
16068
|
+
process.exit(1);
|
|
16069
|
+
}
|
|
16070
|
+
const thIdx = args.indexOf('--threshold');
|
|
16071
|
+
const threshold = thIdx !== -1 && args[thIdx + 1] ? parseFloat(args[thIdx + 1]) : undefined;
|
|
16072
|
+
const extIdx = args.indexOf('--ext');
|
|
16073
|
+
const ext = extIdx !== -1 && args[extIdx + 1] ? args[extIdx + 1].replace(/^\./, '') : undefined;
|
|
16074
|
+
const force = args.includes('--force');
|
|
16075
|
+
|
|
16076
|
+
const { extractConventions } = requireSourceOrBundled('./src/conventions/extract');
|
|
16077
|
+
const { proposeScaffold } = requireSourceOrBundled('./src/scaffold/propose');
|
|
16078
|
+
const conventions = extractConventions(cwd, buildFileList(cwd, config));
|
|
16079
|
+
const decision = proposeScaffold(name, conventions, { threshold, ext, force });
|
|
16080
|
+
|
|
16081
|
+
if (jsonOut) {
|
|
16082
|
+
process.stdout.write(JSON.stringify(decision) + '\n');
|
|
16083
|
+
process.exit(decision.ok ? 0 : 1);
|
|
16084
|
+
}
|
|
16085
|
+
|
|
16086
|
+
const pctS = (n) => `${(n * 100).toFixed(0)}%`;
|
|
16087
|
+
if (decision.ok) {
|
|
16088
|
+
console.log(`[sigmap] scaffold "${decision.name}" — conventions ${decision.tier} (${pctS(decision.confidence)})`);
|
|
16089
|
+
if (decision.warning) console.log(` ⚠ ${decision.warning}`);
|
|
16090
|
+
const p = decision.proposal;
|
|
16091
|
+
console.log(` file: ${p.filename} (${p.namingStyle})`);
|
|
16092
|
+
console.log(` export style: ${p.exportStyle}`);
|
|
16093
|
+
console.log(` test file: ${p.testFile}${p.testFramework ? ` (${p.testFramework})` : ''}`);
|
|
16094
|
+
process.exit(0);
|
|
16095
|
+
}
|
|
16096
|
+
|
|
16097
|
+
console.log(`[sigmap] scaffold "${decision.name}" — REFUSED`);
|
|
16098
|
+
console.log(` ${decision.reason}`);
|
|
16099
|
+
if (decision.conflicts && decision.conflicts.hasConflicts) {
|
|
16100
|
+
for (const c of decision.conflicts.conventions) {
|
|
16101
|
+
console.log(`\n ${c.name} — dominant: ${c.dominant} ${pctS(c.dominantPct)} [${c.tier}]`);
|
|
16102
|
+
for (const v of c.variants) {
|
|
16103
|
+
console.log(` ${v.pattern.padEnd(12)} ${pctS(v.pct).padStart(4)}${v.examples.length ? ` e.g. ${v.examples.join(', ')}` : ''}`);
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
}
|
|
16107
|
+
process.exit(1);
|
|
16108
|
+
}
|
|
16109
|
+
|
|
15825
16110
|
// v7.0.0: `sigmap squeeze <file|->` — minimize a pasted stacktrace / CI-log / JSON blob
|
|
15826
16111
|
if (args[0] === 'squeeze') {
|
|
15827
16112
|
const jsonOut = args.includes('--json');
|
package/llms-full.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.10.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
package/llms.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.10.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.10.0",
|
|
4
4
|
"description": "97% token reduction for AI coding. Extracts function & class signatures with TF-IDF ranking to feed only the right files to Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP. Zero dependencies, runs offline via npx.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLAUDE.md convention injection (IMPL.md §7 Phase 2 / §8 step 5).
|
|
5
|
+
*
|
|
6
|
+
* Renders the conventions detected by `extractConventions` into a
|
|
7
|
+
* marker-delimited markdown block and injects it into CLAUDE.md so an agent
|
|
8
|
+
* reading the file plans grounded in the repo's house style. Idempotent and
|
|
9
|
+
* marker-scoped — it never touches human content or the `## Auto-generated
|
|
10
|
+
* signatures` block. Pure string transforms; zero-dependency, bundle-safe.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const START = '<!-- sigmap-conventions:start -->';
|
|
14
|
+
const END = '<!-- sigmap-conventions:end -->';
|
|
15
|
+
|
|
16
|
+
const TIER_NOTE = {
|
|
17
|
+
consistent: 'consistent — match it',
|
|
18
|
+
mostly: 'dominant, with some drift',
|
|
19
|
+
inconsistent: 'no clear convention — check neighboring files',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const NAMES = {
|
|
23
|
+
fileNaming: 'File naming',
|
|
24
|
+
exportStyle: 'Export style',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const _pct = (n) => `${Math.round(n * 100)}%`;
|
|
28
|
+
|
|
29
|
+
function _conventionLine(label, conv) {
|
|
30
|
+
if (!conv || conv.total === 0 || !conv.dominant) return null;
|
|
31
|
+
const note = TIER_NOTE[conv.tier] || conv.tier;
|
|
32
|
+
let line = `- **${label}:** ${conv.dominant} (${_pct(conv.dominantPct)} — ${note}).`;
|
|
33
|
+
const others = conv.variants.slice(1).filter((v) => v.pct > 0);
|
|
34
|
+
if (others.length) {
|
|
35
|
+
line += ` Variants: ${others.map((v) => `${v.label} ${_pct(v.pct)}`).join(', ')}.`;
|
|
36
|
+
}
|
|
37
|
+
return line;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Render the conventions block (including its start/end markers).
|
|
42
|
+
* @param {object} result an `extractConventions` result
|
|
43
|
+
* @param {string} [version] SigMap version for the footer
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function renderConventionsBlock(result, version) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
for (const key of ['fileNaming', 'exportStyle']) {
|
|
49
|
+
const l = _conventionLine(NAMES[key], result && result[key]);
|
|
50
|
+
if (l) lines.push(l);
|
|
51
|
+
}
|
|
52
|
+
if (result && result.testFramework) {
|
|
53
|
+
lines.push(`- **Test framework:** ${result.testFramework}.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const body = lines.length
|
|
57
|
+
? lines
|
|
58
|
+
: ['- No conventions detected yet (run `sigmap conventions` on a TS/JS/Python repo).'];
|
|
59
|
+
|
|
60
|
+
const ver = version ? ` v${version}` : '';
|
|
61
|
+
return [
|
|
62
|
+
START,
|
|
63
|
+
'## Conventions (auto-detected by SigMap)',
|
|
64
|
+
'',
|
|
65
|
+
'Match these when writing or editing code (TS/JS/Python):',
|
|
66
|
+
'',
|
|
67
|
+
...body,
|
|
68
|
+
'',
|
|
69
|
+
`<sub>Generated by SigMap${ver} · run \`sigmap conventions --inject\` to refresh.</sub>`,
|
|
70
|
+
END,
|
|
71
|
+
].join('\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Inject (or replace) the conventions block in existing CLAUDE.md content.
|
|
76
|
+
* Replaces an existing marked block in place; appends one when absent.
|
|
77
|
+
* Idempotent — never touches content outside the markers.
|
|
78
|
+
* @param {string} existing current file content ('' if the file is new)
|
|
79
|
+
* @param {string} block the block returned by `renderConventionsBlock`
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function injectConventions(existing, block) {
|
|
83
|
+
const src = String(existing || '');
|
|
84
|
+
const startIdx = src.indexOf(START);
|
|
85
|
+
if (startIdx !== -1) {
|
|
86
|
+
const endIdx = src.indexOf(END, startIdx);
|
|
87
|
+
if (endIdx !== -1) {
|
|
88
|
+
const before = src.slice(0, startIdx);
|
|
89
|
+
const after = src.slice(endIdx + END.length);
|
|
90
|
+
return before + block + after;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (src.trim() === '') return block + '\n';
|
|
94
|
+
const sep = src.endsWith('\n') ? '\n' : '\n\n';
|
|
95
|
+
return src + sep + block + '\n';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { renderConventionsBlock, injectConventions, START, END };
|
package/src/mcp/server.js
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scaffold proposal with a confidence floor (IMPL.md §5 — Cause 3).
|
|
5
|
+
*
|
|
6
|
+
* Proposes a convention-matched structure for a new module — filename in the
|
|
7
|
+
* repo's dominant naming style, the export style to use, and a matching test
|
|
8
|
+
* file — but only when the conventions are consistent enough. Below a hard
|
|
9
|
+
* floor it refuses and surfaces the conflict, because a wrong proposal
|
|
10
|
+
* systematizes bad code. Pure, zero-dependency, bundle-safe; reuses the
|
|
11
|
+
* conventions primitives.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { toNamingStyle, analyzeConflicts } = require('../conventions/conflicts');
|
|
15
|
+
|
|
16
|
+
// Soft threshold is configurable; the hard floor is not (IMPL.md §5.1).
|
|
17
|
+
const DEFAULT_THRESHOLD = 0.7;
|
|
18
|
+
const HARD_FLOOR = 0.5;
|
|
19
|
+
|
|
20
|
+
/** Tier for a consistency score (matches the conventions tiers). */
|
|
21
|
+
function _tier(pct) {
|
|
22
|
+
if (pct >= 0.9) return 'consistent';
|
|
23
|
+
if (pct >= 0.7) return 'mostly';
|
|
24
|
+
return 'inconsistent';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Strip any extension/compound suffix from a requested name → bare stem. */
|
|
28
|
+
function _stem(name) {
|
|
29
|
+
const s = String(name || '').trim();
|
|
30
|
+
const slash = s.lastIndexOf('/');
|
|
31
|
+
const base = slash >= 0 ? s.slice(slash + 1) : s;
|
|
32
|
+
const dot = base.indexOf('.');
|
|
33
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Test file path for a styled stem given the detected framework + ext. */
|
|
37
|
+
function _testFile(styledStem, framework, ext) {
|
|
38
|
+
if (framework === 'pytest' || framework === 'unittest') {
|
|
39
|
+
return `test_${toNamingStyle(styledStem, 'snake_case')}.py`;
|
|
40
|
+
}
|
|
41
|
+
return `${styledStem}.test.${ext}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Propose a convention-matched scaffold, gated by a confidence floor.
|
|
46
|
+
* @param {string} name desired module name (any casing; extension ignored)
|
|
47
|
+
* @param {object} conventions an `extractConventions` result
|
|
48
|
+
* @param {object} [opts]
|
|
49
|
+
* @param {number} [opts.threshold=0.7] soft threshold (clamped to ≥ hard floor)
|
|
50
|
+
* @param {boolean} [opts.force=false] allow proposing below the soft threshold
|
|
51
|
+
* (never below the hard floor)
|
|
52
|
+
* @param {string} [opts.ext='js'] file extension for the proposed files
|
|
53
|
+
* @returns {{ ok:boolean, refused:boolean, name:string, tier:string,
|
|
54
|
+
* confidence:number, threshold:number, hardFloor:number, forced:boolean,
|
|
55
|
+
* warning:string|null, reason:string, proposal:object|null, conflicts:object }}
|
|
56
|
+
*/
|
|
57
|
+
function proposeScaffold(name, conventions, opts = {}) {
|
|
58
|
+
const threshold = Math.max(HARD_FLOOR, opts.threshold != null ? opts.threshold : DEFAULT_THRESHOLD);
|
|
59
|
+
const force = !!opts.force;
|
|
60
|
+
const ext = opts.ext || 'js';
|
|
61
|
+
const fileNaming = (conventions && conventions.fileNaming) || { dominant: null, dominantPct: 0, total: 0 };
|
|
62
|
+
const exportStyle = (conventions && conventions.exportStyle) || { dominant: null };
|
|
63
|
+
const confidence = fileNaming.dominantPct || 0;
|
|
64
|
+
const tier = fileNaming.total > 0 ? _tier(confidence) : 'unknown';
|
|
65
|
+
const conflicts = analyzeConflicts(conventions || {});
|
|
66
|
+
|
|
67
|
+
const base = {
|
|
68
|
+
ok: false, refused: true, name: String(name || ''), tier, confidence,
|
|
69
|
+
threshold, hardFloor: HARD_FLOOR, forced: false, warning: null,
|
|
70
|
+
reason: '', proposal: null, conflicts,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!fileNaming.dominant || fileNaming.total === 0) {
|
|
74
|
+
return { ...base, reason: 'no file-naming convention detected — cannot propose a name' };
|
|
75
|
+
}
|
|
76
|
+
if (confidence < HARD_FLOOR) {
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
reason: `file-naming consistency ${(confidence * 100).toFixed(0)}% is below the hard floor ${(HARD_FLOOR * 100).toFixed(0)}% — refusing (not overridable)`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (confidence < threshold && !force) {
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
reason: `file-naming consistency ${(confidence * 100).toFixed(0)}% is below the threshold ${(threshold * 100).toFixed(0)}% — refusing (use --force to override above the ${(HARD_FLOOR * 100).toFixed(0)}% floor)`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const styledStem = toNamingStyle(_stem(name), fileNaming.dominant);
|
|
90
|
+
const forced = confidence < threshold && force;
|
|
91
|
+
const proposal = {
|
|
92
|
+
filename: `${styledStem}.${ext}`,
|
|
93
|
+
namingStyle: fileNaming.dominant,
|
|
94
|
+
exportStyle: exportStyle.dominant || 'named',
|
|
95
|
+
testFile: _testFile(styledStem, conventions.testFramework, ext),
|
|
96
|
+
testFramework: conventions.testFramework || null,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
...base,
|
|
101
|
+
ok: true,
|
|
102
|
+
refused: false,
|
|
103
|
+
forced,
|
|
104
|
+
warning: forced
|
|
105
|
+
? `proposed below the ${(threshold * 100).toFixed(0)}% threshold (--force); conventions are only ${tier}`
|
|
106
|
+
: null,
|
|
107
|
+
reason: forced ? 'forced proposal above the hard floor' : `conventions are ${tier} — proposing`,
|
|
108
|
+
proposal,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { proposeScaffold, DEFAULT_THRESHOLD, HARD_FLOOR };
|