flex-md 4.7.2 → 4.7.4
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/README.md +1 -1
- package/dist/__tests__/diagnostics.test.js +45 -47
- package/dist/__tests__/ofs.test.js +28 -30
- package/dist/__tests__/structural.test.js +19 -21
- package/dist/__tests__/validate.test.js +27 -29
- package/dist/cli/index.js +13 -15
- package/dist/detect/json/detectIntent.js +1 -4
- package/dist/detect/json/detectMarkdown.js +29 -35
- package/dist/detect/json/detectPresence.js +2 -6
- package/dist/detect/json/index.js +10 -31
- package/dist/detect/json/types.js +1 -2
- package/dist/extract/extract.js +11 -14
- package/dist/extract/types.js +1 -2
- package/dist/index.js +22 -69
- package/dist/logger.js +3 -6
- package/dist/md/match.js +1 -4
- package/dist/md/normalize.js +1 -4
- package/dist/md/outline.js +4 -8
- package/dist/md/parse.js +11 -20
- package/dist/ofs/adapter.js +45 -49
- package/dist/ofs/enricher.js +3 -8
- package/dist/ofs/infer.js +4 -7
- package/dist/ofs/issuesEnvelope.js +5 -10
- package/dist/ofs/memory.js +6 -10
- package/dist/ofs/parser.js +7 -13
- package/dist/ofs/stringify.js +1 -4
- package/dist/pipeline/enforce.js +12 -15
- package/dist/pipeline/kind.js +3 -6
- package/dist/pipeline/repair.js +8 -11
- package/dist/strictness/container.js +1 -4
- package/dist/strictness/processor.js +7 -10
- package/dist/strictness/types.js +1 -4
- package/dist/tokens/auto-fix.js +1 -4
- package/dist/tokens/cognitive-cost.js +6 -10
- package/dist/tokens/compliance.js +4 -8
- package/dist/tokens/confidence.js +6 -9
- package/dist/tokens/estimator.js +20 -26
- package/dist/tokens/improvements.js +12 -16
- package/dist/tokens/index.js +22 -40
- package/dist/tokens/parser.js +4 -7
- package/dist/tokens/patterns.js +2 -5
- package/dist/tokens/runtime-estimator.js +9 -12
- package/dist/tokens/smart-report.js +10 -14
- package/dist/tokens/spec-estimator.js +10 -15
- package/dist/tokens/types.js +1 -2
- package/dist/tokens/validator.js +3 -6
- package/dist/types.js +1 -2
- package/dist/validate/compliance.js +4 -8
- package/dist/validate/connection.js +5 -8
- package/dist/validate/types.js +1 -2
- package/dist/validate/validate.js +16 -19
- package/dist-cjs/__tests__/diagnostics.test.cjs +61 -0
- package/dist-cjs/__tests__/ofs.test.cjs +53 -0
- package/dist-cjs/__tests__/structural.test.cjs +30 -0
- package/dist-cjs/__tests__/validate.test.cjs +110 -0
- package/dist-cjs/cli/index.cjs +110 -0
- package/dist-cjs/detect/json/detectIntent.cjs +82 -0
- package/dist-cjs/detect/json/detectMarkdown.cjs +304 -0
- package/dist-cjs/detect/json/detectPresence.cjs +195 -0
- package/dist-cjs/detect/json/index.cjs +34 -0
- package/dist-cjs/detect/json/types.cjs +2 -0
- package/dist-cjs/extract/extract.cjs +72 -0
- package/dist-cjs/extract/types.cjs +2 -0
- package/dist-cjs/flex-md-loader.cjs +102 -0
- package/dist-cjs/index.cjs +79 -0
- package/dist-cjs/logger.cjs +22 -0
- package/dist-cjs/md/match.cjs +47 -0
- package/dist-cjs/md/normalize.cjs +13 -0
- package/dist-cjs/md/outline.cjs +49 -0
- package/dist-cjs/md/parse.cjs +199 -0
- package/dist-cjs/ofs/adapter.cjs +195 -0
- package/dist-cjs/ofs/enricher.cjs +151 -0
- package/dist-cjs/ofs/infer.cjs +63 -0
- package/dist-cjs/ofs/issuesEnvelope.cjs +76 -0
- package/dist-cjs/ofs/memory.cjs +26 -0
- package/dist-cjs/ofs/parser.cjs +373 -0
- package/dist-cjs/ofs/stringify.cjs +45 -0
- package/dist-cjs/pipeline/enforce.cjs +49 -0
- package/dist-cjs/pipeline/kind.cjs +30 -0
- package/dist-cjs/pipeline/repair.cjs +115 -0
- package/dist-cjs/strictness/container.cjs +49 -0
- package/dist-cjs/strictness/processor.cjs +32 -0
- package/dist-cjs/strictness/types.cjs +109 -0
- package/dist-cjs/tokens/auto-fix.cjs +59 -0
- package/dist-cjs/tokens/cognitive-cost.cjs +209 -0
- package/dist-cjs/tokens/compliance.cjs +74 -0
- package/dist-cjs/tokens/confidence.cjs +335 -0
- package/dist-cjs/tokens/estimator.cjs +157 -0
- package/dist-cjs/tokens/improvements.cjs +701 -0
- package/dist-cjs/tokens/index.cjs +74 -0
- package/dist-cjs/tokens/parser.cjs +100 -0
- package/dist-cjs/tokens/patterns.cjs +23 -0
- package/dist-cjs/tokens/runtime-estimator.cjs +74 -0
- package/dist-cjs/tokens/smart-report.cjs +191 -0
- package/dist-cjs/tokens/spec-estimator.cjs +125 -0
- package/dist-cjs/tokens/types.cjs +2 -0
- package/dist-cjs/tokens/validator.cjs +62 -0
- package/dist-cjs/types.cjs +2 -0
- package/dist-cjs/validate/compliance.cjs +103 -0
- package/dist-cjs/validate/connection.cjs +47 -0
- package/dist-cjs/validate/types.cjs +2 -0
- package/dist-cjs/validate/validate.cjs +319 -0
- package/docs/consumption.md +1 -1
- package/package.json +14 -8
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateConfidence = calculateConfidence;
|
|
4
|
+
const parser_js_1 = require("./parser.cjs");
|
|
5
|
+
const compliance_js_1 = require("./compliance.cjs");
|
|
6
|
+
/**
|
|
7
|
+
* Calculate confidence in token estimation
|
|
8
|
+
*/
|
|
9
|
+
function calculateConfidence(spec) {
|
|
10
|
+
const bySection = [];
|
|
11
|
+
const factors = [];
|
|
12
|
+
let totalConfidence = 0;
|
|
13
|
+
let totalWeight = 0;
|
|
14
|
+
// Analyze each section
|
|
15
|
+
for (const section of spec.sections) {
|
|
16
|
+
const sectionConf = analyzeSectionConfidence(section);
|
|
17
|
+
bySection.push(sectionConf);
|
|
18
|
+
// Weight required sections more heavily
|
|
19
|
+
const weight = section.required !== false ? 2 : 1;
|
|
20
|
+
totalConfidence += sectionConf.confidence * weight;
|
|
21
|
+
totalWeight += weight;
|
|
22
|
+
}
|
|
23
|
+
// Calculate coverage factor
|
|
24
|
+
const coverageFactor = calculateCoverageFactor(spec);
|
|
25
|
+
factors.push(coverageFactor);
|
|
26
|
+
// Calculate specificity factor
|
|
27
|
+
const specificityFactor = calculateSpecificityFactor(bySection);
|
|
28
|
+
factors.push(specificityFactor);
|
|
29
|
+
// Calculate consistency factor
|
|
30
|
+
const consistencyFactor = calculateConsistencyFactor(bySection);
|
|
31
|
+
factors.push(consistencyFactor);
|
|
32
|
+
// Calculate required coverage factor
|
|
33
|
+
const requiredFactor = calculateRequiredCoverageFactor(spec, bySection);
|
|
34
|
+
factors.push(requiredFactor);
|
|
35
|
+
// Combine all factors
|
|
36
|
+
let overall = totalWeight > 0 ? totalConfidence / totalWeight : 0;
|
|
37
|
+
// Apply factor adjustments
|
|
38
|
+
for (const factor of factors) {
|
|
39
|
+
overall += factor.score;
|
|
40
|
+
}
|
|
41
|
+
overall = Math.max(0, Math.min(100, overall));
|
|
42
|
+
return {
|
|
43
|
+
overall,
|
|
44
|
+
grade: getConfidenceGrade(overall),
|
|
45
|
+
bySection,
|
|
46
|
+
factors,
|
|
47
|
+
recommendations: generateConfidenceRecommendations(overall, factors, bySection)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Analyze confidence for individual section
|
|
52
|
+
*/
|
|
53
|
+
function analyzeSectionConfidence(section) {
|
|
54
|
+
const kind = section.kind || 'text';
|
|
55
|
+
const systemPart = (0, parser_js_1.parseSystemPart)(section.instruction, kind);
|
|
56
|
+
const hasSystemPart = systemPart !== null;
|
|
57
|
+
const level = systemPart ? (0, compliance_js_1.detectSystemPartLevel)(systemPart, kind) : null;
|
|
58
|
+
let confidence = 0;
|
|
59
|
+
let quality = 'none';
|
|
60
|
+
if (systemPart && level !== null) {
|
|
61
|
+
// Base confidence from level
|
|
62
|
+
const levelConfidence = { 0: 0, 1: 60, 2: 80, 3: 90 };
|
|
63
|
+
confidence = levelConfidence[level] || 0;
|
|
64
|
+
// Adjust based on section characteristics
|
|
65
|
+
const adjustment = getConfidenceAdjustment(systemPart, section);
|
|
66
|
+
confidence += adjustment;
|
|
67
|
+
confidence = Math.max(0, Math.min(100, confidence));
|
|
68
|
+
// Determine quality
|
|
69
|
+
if (confidence >= 85)
|
|
70
|
+
quality = 'excellent';
|
|
71
|
+
else if (confidence >= 70)
|
|
72
|
+
quality = 'good';
|
|
73
|
+
else if (confidence >= 50)
|
|
74
|
+
quality = 'fair';
|
|
75
|
+
else
|
|
76
|
+
quality = 'poor';
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
sectionName: section.name,
|
|
80
|
+
confidence,
|
|
81
|
+
hasSystemPart,
|
|
82
|
+
level,
|
|
83
|
+
estimateQuality: quality
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function getConfidenceAdjustment(systemPart, _section) {
|
|
87
|
+
let adjustment = 0;
|
|
88
|
+
const { parsed } = systemPart;
|
|
89
|
+
switch (parsed.type) {
|
|
90
|
+
case 'length':
|
|
91
|
+
// Specific values are better than vague ones
|
|
92
|
+
if (['1 sentence', '1-2 sentences', '1 paragraph', '2-3 paragraphs'].includes(parsed.value)) {
|
|
93
|
+
adjustment += 10; // Concrete measurements
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case 'items':
|
|
97
|
+
// Exact numbers or tight ranges are better
|
|
98
|
+
if (parsed.max !== null) {
|
|
99
|
+
const range = parsed.max - parsed.min;
|
|
100
|
+
if (range <= 2)
|
|
101
|
+
adjustment += 10; // Tight range
|
|
102
|
+
else if (range <= 5)
|
|
103
|
+
adjustment += 5; // Reasonable range
|
|
104
|
+
}
|
|
105
|
+
else if (!parsed.atLeast) {
|
|
106
|
+
adjustment += 10; // Exact number
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case 'table':
|
|
110
|
+
// Both dimensions specified tightly
|
|
111
|
+
const rowsExact = parsed.rows.max === null && !parsed.rows.atLeast;
|
|
112
|
+
const colsExact = parsed.columns.max === null && !parsed.columns.atLeast;
|
|
113
|
+
if (rowsExact && colsExact) {
|
|
114
|
+
adjustment += 10; // Both exact
|
|
115
|
+
}
|
|
116
|
+
else if (rowsExact || colsExact) {
|
|
117
|
+
adjustment += 5; // One exact
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case 'lines':
|
|
121
|
+
// Non-approximate is better
|
|
122
|
+
if (!parsed.approximate) {
|
|
123
|
+
adjustment += 5;
|
|
124
|
+
}
|
|
125
|
+
if (parsed.max !== null) {
|
|
126
|
+
const range = parsed.max - parsed.min;
|
|
127
|
+
if (range <= 10)
|
|
128
|
+
adjustment += 5; // Tight range
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
// Penalty for very vague patterns
|
|
133
|
+
if (parsed.type === 'items' && parsed.atLeast && parsed.min < 3) {
|
|
134
|
+
adjustment -= 10; // "at least 1" is very vague
|
|
135
|
+
}
|
|
136
|
+
return adjustment;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Calculate coverage factor (what % of sections have system parts)
|
|
140
|
+
*/
|
|
141
|
+
function calculateCoverageFactor(spec) {
|
|
142
|
+
const total = spec.sections.length;
|
|
143
|
+
const withSystemParts = spec.sections.filter(s => (0, parser_js_1.parseSystemPart)(s.instruction, s.kind || 'text') !== null).length;
|
|
144
|
+
const coverage = total > 0 ? (withSystemParts / total) * 100 : 0;
|
|
145
|
+
let score = 0;
|
|
146
|
+
let impact = 'neutral';
|
|
147
|
+
if (coverage >= 90) {
|
|
148
|
+
score = 10;
|
|
149
|
+
impact = 'positive';
|
|
150
|
+
}
|
|
151
|
+
else if (coverage >= 70) {
|
|
152
|
+
score = 5;
|
|
153
|
+
impact = 'positive';
|
|
154
|
+
}
|
|
155
|
+
else if (coverage < 50) {
|
|
156
|
+
score = -10;
|
|
157
|
+
impact = 'negative';
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
score = -5;
|
|
161
|
+
impact = 'negative';
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
factor: 'coverage',
|
|
165
|
+
impact,
|
|
166
|
+
score,
|
|
167
|
+
description: `${coverage.toFixed(0)}% of sections have system parts`
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Calculate specificity factor (are system parts specific or vague?)
|
|
172
|
+
*/
|
|
173
|
+
function calculateSpecificityFactor(sections) {
|
|
174
|
+
const withSystemParts = sections.filter(s => s.hasSystemPart);
|
|
175
|
+
if (withSystemParts.length === 0) {
|
|
176
|
+
return {
|
|
177
|
+
factor: 'specificity',
|
|
178
|
+
impact: 'negative',
|
|
179
|
+
score: -15,
|
|
180
|
+
description: 'No system parts to evaluate'
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const avgLevel = withSystemParts.reduce((sum, s) => sum + (s.level || 0), 0) / withSystemParts.length;
|
|
184
|
+
let score = 0;
|
|
185
|
+
let impact = 'neutral';
|
|
186
|
+
if (avgLevel >= 2.5) {
|
|
187
|
+
score = 10;
|
|
188
|
+
impact = 'positive';
|
|
189
|
+
}
|
|
190
|
+
else if (avgLevel >= 2) {
|
|
191
|
+
score = 5;
|
|
192
|
+
impact = 'positive';
|
|
193
|
+
}
|
|
194
|
+
else if (avgLevel < 1.5) {
|
|
195
|
+
score = -5;
|
|
196
|
+
impact = 'negative';
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
factor: 'specificity',
|
|
200
|
+
impact,
|
|
201
|
+
score,
|
|
202
|
+
description: `Average specificity level: L${avgLevel.toFixed(1)}`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Calculate consistency factor (are all sections at similar level?)
|
|
207
|
+
*/
|
|
208
|
+
function calculateConsistencyFactor(sections) {
|
|
209
|
+
const levels = sections
|
|
210
|
+
.filter(s => s.level !== null)
|
|
211
|
+
.map(s => s.level);
|
|
212
|
+
if (levels.length === 0) {
|
|
213
|
+
return {
|
|
214
|
+
factor: 'consistency',
|
|
215
|
+
impact: 'neutral',
|
|
216
|
+
score: 0,
|
|
217
|
+
description: 'No levels to compare'
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const min = Math.min(...levels);
|
|
221
|
+
const max = Math.max(...levels);
|
|
222
|
+
const variance = max - min;
|
|
223
|
+
let score = 0;
|
|
224
|
+
let impact = 'neutral';
|
|
225
|
+
if (variance === 0) {
|
|
226
|
+
score = 5;
|
|
227
|
+
impact = 'positive';
|
|
228
|
+
}
|
|
229
|
+
else if (variance >= 3) {
|
|
230
|
+
score = -5;
|
|
231
|
+
impact = 'negative';
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
factor: 'consistency',
|
|
235
|
+
impact,
|
|
236
|
+
score,
|
|
237
|
+
description: variance === 0
|
|
238
|
+
? 'All sections at same level (consistent)'
|
|
239
|
+
: `Level variance: ${variance} (${min} to ${max})`
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Calculate required coverage (do all required sections have good system parts?)
|
|
244
|
+
*/
|
|
245
|
+
function calculateRequiredCoverageFactor(spec, sections) {
|
|
246
|
+
const requiredSections = spec.sections.filter(s => s.required !== false);
|
|
247
|
+
if (requiredSections.length === 0) {
|
|
248
|
+
return {
|
|
249
|
+
factor: 'required_coverage',
|
|
250
|
+
impact: 'neutral',
|
|
251
|
+
score: 0,
|
|
252
|
+
description: 'No required sections'
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const requiredWithSystemParts = sections.filter(s => {
|
|
256
|
+
const section = spec.sections.find(sec => sec.name === s.sectionName);
|
|
257
|
+
return section?.required !== false && s.hasSystemPart;
|
|
258
|
+
}).length;
|
|
259
|
+
const coverage = (requiredWithSystemParts / requiredSections.length) * 100;
|
|
260
|
+
let score = 0;
|
|
261
|
+
let impact = 'neutral';
|
|
262
|
+
if (coverage === 100) {
|
|
263
|
+
score = 15;
|
|
264
|
+
impact = 'positive';
|
|
265
|
+
}
|
|
266
|
+
else if (coverage >= 80) {
|
|
267
|
+
score = 5;
|
|
268
|
+
impact = 'positive';
|
|
269
|
+
}
|
|
270
|
+
else if (coverage < 50) {
|
|
271
|
+
score = -15;
|
|
272
|
+
impact = 'negative';
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
score = -5;
|
|
276
|
+
impact = 'negative';
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
factor: 'required_coverage',
|
|
280
|
+
impact,
|
|
281
|
+
score,
|
|
282
|
+
description: `${coverage.toFixed(0)}% of required sections have system parts`
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function getConfidenceGrade(score) {
|
|
286
|
+
if (score >= 90)
|
|
287
|
+
return 'A';
|
|
288
|
+
if (score >= 80)
|
|
289
|
+
return 'B';
|
|
290
|
+
if (score >= 70)
|
|
291
|
+
return 'C';
|
|
292
|
+
if (score >= 60)
|
|
293
|
+
return 'D';
|
|
294
|
+
return 'F';
|
|
295
|
+
}
|
|
296
|
+
function generateConfidenceRecommendations(overall, factors, sections) {
|
|
297
|
+
const recommendations = [];
|
|
298
|
+
// Overall quality
|
|
299
|
+
if (overall < 70) {
|
|
300
|
+
recommendations.push('Overall confidence is low. Consider adding more specific system parts.');
|
|
301
|
+
}
|
|
302
|
+
// Check negative factors
|
|
303
|
+
for (const factor of factors) {
|
|
304
|
+
if (factor.impact === 'negative') {
|
|
305
|
+
switch (factor.factor) {
|
|
306
|
+
case 'coverage':
|
|
307
|
+
recommendations.push('Add system parts to more sections to improve coverage.');
|
|
308
|
+
break;
|
|
309
|
+
case 'specificity':
|
|
310
|
+
recommendations.push('Upgrade some L1 system parts to L2 for better precision.');
|
|
311
|
+
break;
|
|
312
|
+
case 'consistency':
|
|
313
|
+
recommendations.push('Consider standardizing compliance level across sections.');
|
|
314
|
+
break;
|
|
315
|
+
case 'required_coverage':
|
|
316
|
+
recommendations.push('Ensure all required sections have system parts.');
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Check poor quality sections
|
|
322
|
+
const poorSections = sections.filter(s => s.estimateQuality === 'poor' || s.estimateQuality === 'none');
|
|
323
|
+
if (poorSections.length > 0) {
|
|
324
|
+
const names = poorSections.map(s => s.sectionName).join(', ');
|
|
325
|
+
recommendations.push(`Improve system parts for: ${names}`);
|
|
326
|
+
}
|
|
327
|
+
// Positive feedback
|
|
328
|
+
if (overall >= 90) {
|
|
329
|
+
recommendations.push('Excellent! Token estimation will be highly accurate.');
|
|
330
|
+
}
|
|
331
|
+
else if (overall >= 80) {
|
|
332
|
+
recommendations.push('Good confidence. Minor improvements possible.');
|
|
333
|
+
}
|
|
334
|
+
return recommendations;
|
|
335
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TOKEN_CONSTANTS = void 0;
|
|
4
|
+
exports.estimateTokens = estimateTokens;
|
|
5
|
+
exports.getFallbackEstimate = getFallbackEstimate;
|
|
6
|
+
exports.estimateTextTokens = estimateTextTokens;
|
|
7
|
+
// Calibrated token constants
|
|
8
|
+
exports.TOKEN_CONSTANTS = {
|
|
9
|
+
// Text lengths (enumerated)
|
|
10
|
+
lengths: {
|
|
11
|
+
'1 sentence': 25,
|
|
12
|
+
'1-2 sentences': 40,
|
|
13
|
+
'1 paragraph': 100,
|
|
14
|
+
'2-3 paragraphs': 250,
|
|
15
|
+
'brief': 75,
|
|
16
|
+
'moderate': 150,
|
|
17
|
+
'detailed': 300,
|
|
18
|
+
'extensive': 500
|
|
19
|
+
},
|
|
20
|
+
// Per-unit estimates
|
|
21
|
+
perItem: 40,
|
|
22
|
+
perTableCell: 20,
|
|
23
|
+
perCodeLine: 5,
|
|
24
|
+
// Overhead
|
|
25
|
+
headingOverhead: 10,
|
|
26
|
+
baseOverhead: 50,
|
|
27
|
+
// Text token estimation (rough approximation: 1 token ≈ 4 characters)
|
|
28
|
+
charsPerToken: 4
|
|
29
|
+
};
|
|
30
|
+
function estimateTokens(systemPart) {
|
|
31
|
+
const { parsed } = systemPart;
|
|
32
|
+
switch (parsed.type) {
|
|
33
|
+
case 'length':
|
|
34
|
+
return estimateTextLength(parsed.value);
|
|
35
|
+
case 'items':
|
|
36
|
+
return estimateItems(parsed);
|
|
37
|
+
case 'table':
|
|
38
|
+
return estimateTable(parsed);
|
|
39
|
+
case 'lines':
|
|
40
|
+
return estimateCodeLines(parsed);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function estimateTextLength(value) {
|
|
44
|
+
const estimated = exports.TOKEN_CONSTANTS.lengths[value];
|
|
45
|
+
return {
|
|
46
|
+
estimated,
|
|
47
|
+
min: Math.floor(estimated * 0.7),
|
|
48
|
+
max: Math.ceil(estimated * 1.3),
|
|
49
|
+
confidence: 'high'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function estimateItems(parsed) {
|
|
53
|
+
const { min, max, atLeast } = parsed;
|
|
54
|
+
if (max !== null) {
|
|
55
|
+
// Range: "3-5" → use average
|
|
56
|
+
const avg = (min + max) / 2;
|
|
57
|
+
const estimated = Math.round(avg * exports.TOKEN_CONSTANTS.perItem);
|
|
58
|
+
return {
|
|
59
|
+
estimated,
|
|
60
|
+
min: min * exports.TOKEN_CONSTANTS.perItem,
|
|
61
|
+
max: max * exports.TOKEN_CONSTANTS.perItem,
|
|
62
|
+
confidence: 'high'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (atLeast) {
|
|
66
|
+
// "at least 3" → estimate at min + 50%
|
|
67
|
+
const estimated = Math.round(min * 1.5 * exports.TOKEN_CONSTANTS.perItem);
|
|
68
|
+
return {
|
|
69
|
+
estimated,
|
|
70
|
+
min: min * exports.TOKEN_CONSTANTS.perItem,
|
|
71
|
+
max: min * 3 * exports.TOKEN_CONSTANTS.perItem,
|
|
72
|
+
confidence: 'medium'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Exact: "3" → use exact value
|
|
76
|
+
const estimated = min * exports.TOKEN_CONSTANTS.perItem;
|
|
77
|
+
return {
|
|
78
|
+
estimated,
|
|
79
|
+
min: Math.floor(estimated * 0.8),
|
|
80
|
+
max: Math.ceil(estimated * 1.2),
|
|
81
|
+
confidence: 'high'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function estimateTable(parsed) {
|
|
85
|
+
const rowAvg = parsed.rows.max
|
|
86
|
+
? (parsed.rows.min + parsed.rows.max) / 2
|
|
87
|
+
: parsed.rows.atLeast
|
|
88
|
+
? parsed.rows.min * 1.5
|
|
89
|
+
: parsed.rows.min;
|
|
90
|
+
const colAvg = parsed.columns.max
|
|
91
|
+
? (parsed.columns.min + parsed.columns.max) / 2
|
|
92
|
+
: parsed.columns.atLeast
|
|
93
|
+
? parsed.columns.min * 1.5
|
|
94
|
+
: parsed.columns.min;
|
|
95
|
+
const estimated = Math.round(rowAvg * colAvg * exports.TOKEN_CONSTANTS.perTableCell);
|
|
96
|
+
const minCells = parsed.rows.min * parsed.columns.min;
|
|
97
|
+
const maxRows = parsed.rows.max || (parsed.rows.atLeast ? parsed.rows.min * 2 : parsed.rows.min);
|
|
98
|
+
const maxCols = parsed.columns.max || (parsed.columns.atLeast ? parsed.columns.min * 2 : parsed.columns.min);
|
|
99
|
+
const maxCells = maxRows * maxCols;
|
|
100
|
+
return {
|
|
101
|
+
estimated,
|
|
102
|
+
min: minCells * exports.TOKEN_CONSTANTS.perTableCell,
|
|
103
|
+
max: maxCells * exports.TOKEN_CONSTANTS.perTableCell,
|
|
104
|
+
confidence: (parsed.rows.max && parsed.columns.max) ? 'high' : 'medium'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function estimateCodeLines(parsed) {
|
|
108
|
+
const { min, max, approximate } = parsed;
|
|
109
|
+
if (max !== null) {
|
|
110
|
+
// Range: "10-20"
|
|
111
|
+
const avg = (min + max) / 2;
|
|
112
|
+
const estimated = Math.round(avg * exports.TOKEN_CONSTANTS.perCodeLine);
|
|
113
|
+
return {
|
|
114
|
+
estimated,
|
|
115
|
+
min: min * exports.TOKEN_CONSTANTS.perCodeLine,
|
|
116
|
+
max: max * exports.TOKEN_CONSTANTS.perCodeLine,
|
|
117
|
+
confidence: 'high'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Exact or approximate: "10" or "~10"
|
|
121
|
+
const estimated = min * exports.TOKEN_CONSTANTS.perCodeLine;
|
|
122
|
+
const variance = approximate ? 0.3 : 0.15;
|
|
123
|
+
return {
|
|
124
|
+
estimated,
|
|
125
|
+
min: Math.floor(estimated * (1 - variance)),
|
|
126
|
+
max: Math.ceil(estimated * (1 + variance)),
|
|
127
|
+
confidence: approximate ? 'medium' : 'high'
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Fallback for sections without system parts
|
|
131
|
+
function getFallbackEstimate(kind, required) {
|
|
132
|
+
const fallbacks = {
|
|
133
|
+
text: 150,
|
|
134
|
+
list: 200,
|
|
135
|
+
'ordered-list': 200,
|
|
136
|
+
table: 300,
|
|
137
|
+
code: 400
|
|
138
|
+
};
|
|
139
|
+
const base = fallbacks[kind] || 150;
|
|
140
|
+
return {
|
|
141
|
+
estimated: base,
|
|
142
|
+
min: Math.floor(base * 0.5),
|
|
143
|
+
max: Math.ceil(base * 2),
|
|
144
|
+
confidence: 'low'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Estimate tokens from plain text using character-based approximation.
|
|
149
|
+
* Uses a rule of thumb: ~4 characters per token (varies by model, but good approximation).
|
|
150
|
+
*/
|
|
151
|
+
function estimateTextTokens(text) {
|
|
152
|
+
if (!text || text.length === 0)
|
|
153
|
+
return 0;
|
|
154
|
+
// Rough approximation: 1 token ≈ 4 characters for English text
|
|
155
|
+
// This is a common rule of thumb and works reasonably well for most cases
|
|
156
|
+
return Math.ceil(text.length / exports.TOKEN_CONSTANTS.charsPerToken);
|
|
157
|
+
}
|