sigmap 7.9.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 CHANGED
@@ -10,6 +10,15 @@ 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
+
13
22
  ## [7.9.0] — 2026-06-17
14
23
 
15
24
  Minor release — `sigmap conventions --inject` (grounded codegen, Layer 3).
package/gen-context.js CHANGED
@@ -24,6 +24,122 @@ function __require(key) {
24
24
  // ── ./src/conventions/extract ──
25
25
  // ── ./src/conventions/conflicts ──
26
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
+
27
143
  __factories["./src/conventions/inject"] = function(module, exports) {
28
144
 
29
145
  /**
@@ -7003,7 +7119,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
7003
7119
 
7004
7120
  const SERVER_INFO = {
7005
7121
  name: 'sigmap',
7006
- version: '7.9.0',
7122
+ version: '7.10.0',
7007
7123
  description: 'SigMap MCP server — code signatures on demand',
7008
7124
  };
7009
7125
 
@@ -12681,7 +12797,7 @@ function __tryGit(args, opts = {}) {
12681
12797
  catch (_) { return ''; }
12682
12798
  }
12683
12799
 
12684
- const VERSION = '7.9.0';
12800
+ const VERSION = '7.10.0';
12685
12801
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
12686
12802
 
12687
12803
  function requireSourceOrBundled(key) {
@@ -15941,6 +16057,56 @@ function main() {
15941
16057
  process.exit(0);
15942
16058
  }
15943
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
+
15944
16110
  // v7.0.0: `sigmap squeeze <file|->` — minimize a pasted stacktrace / CI-log / JSON blob
15945
16111
  if (args[0] === 'squeeze') {
15946
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.9.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
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.9.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
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.9.0",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "7.9.0",
3
+ "version": "7.10.0",
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": "7.9.0",
3
+ "version": "7.10.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
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: '7.9.0',
21
+ version: '7.10.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -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 };