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 +9 -0
- package/gen-context.js +168 -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/mcp/server.js +1 -1
- package/src/scaffold/propose.js +112 -0
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.
|
|
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.
|
|
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.
|
|
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": {
|
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 };
|