flex-md 4.7.2 → 4.7.3

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.
Files changed (104) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/diagnostics.test.js +45 -47
  3. package/dist/__tests__/ofs.test.js +28 -30
  4. package/dist/__tests__/structural.test.js +19 -21
  5. package/dist/__tests__/validate.test.js +27 -29
  6. package/dist/cli/index.js +13 -15
  7. package/dist/detect/json/detectIntent.js +1 -4
  8. package/dist/detect/json/detectMarkdown.js +29 -35
  9. package/dist/detect/json/detectPresence.js +2 -6
  10. package/dist/detect/json/index.js +10 -31
  11. package/dist/detect/json/types.js +1 -2
  12. package/dist/extract/extract.js +11 -14
  13. package/dist/extract/types.js +1 -2
  14. package/dist/index.js +22 -69
  15. package/dist/logger.js +3 -6
  16. package/dist/md/match.js +1 -4
  17. package/dist/md/normalize.js +1 -4
  18. package/dist/md/outline.js +4 -8
  19. package/dist/md/parse.js +11 -20
  20. package/dist/ofs/adapter.js +45 -49
  21. package/dist/ofs/enricher.js +3 -8
  22. package/dist/ofs/infer.js +4 -7
  23. package/dist/ofs/issuesEnvelope.js +5 -10
  24. package/dist/ofs/memory.js +6 -10
  25. package/dist/ofs/parser.js +7 -13
  26. package/dist/ofs/stringify.js +1 -4
  27. package/dist/pipeline/enforce.js +12 -15
  28. package/dist/pipeline/kind.js +3 -6
  29. package/dist/pipeline/repair.js +8 -11
  30. package/dist/strictness/container.js +1 -4
  31. package/dist/strictness/processor.js +7 -10
  32. package/dist/strictness/types.js +1 -4
  33. package/dist/tokens/auto-fix.js +1 -4
  34. package/dist/tokens/cognitive-cost.js +6 -10
  35. package/dist/tokens/compliance.js +4 -8
  36. package/dist/tokens/confidence.js +6 -9
  37. package/dist/tokens/estimator.js +20 -26
  38. package/dist/tokens/improvements.js +12 -16
  39. package/dist/tokens/index.js +22 -40
  40. package/dist/tokens/parser.js +4 -7
  41. package/dist/tokens/patterns.js +2 -5
  42. package/dist/tokens/runtime-estimator.js +9 -12
  43. package/dist/tokens/smart-report.js +10 -14
  44. package/dist/tokens/spec-estimator.js +10 -15
  45. package/dist/tokens/types.js +1 -2
  46. package/dist/tokens/validator.js +3 -6
  47. package/dist/types.js +1 -2
  48. package/dist/validate/compliance.js +4 -8
  49. package/dist/validate/connection.js +5 -8
  50. package/dist/validate/types.js +1 -2
  51. package/dist/validate/validate.js +16 -19
  52. package/dist-cjs/__tests__/diagnostics.test.cjs +61 -0
  53. package/dist-cjs/__tests__/ofs.test.cjs +53 -0
  54. package/dist-cjs/__tests__/structural.test.cjs +30 -0
  55. package/dist-cjs/__tests__/validate.test.cjs +110 -0
  56. package/dist-cjs/cli/index.cjs +110 -0
  57. package/dist-cjs/detect/json/detectIntent.cjs +82 -0
  58. package/dist-cjs/detect/json/detectMarkdown.cjs +304 -0
  59. package/dist-cjs/detect/json/detectPresence.cjs +195 -0
  60. package/dist-cjs/detect/json/index.cjs +34 -0
  61. package/dist-cjs/detect/json/types.cjs +2 -0
  62. package/dist-cjs/extract/extract.cjs +72 -0
  63. package/dist-cjs/extract/types.cjs +2 -0
  64. package/dist-cjs/flex-md-loader.cjs +102 -0
  65. package/dist-cjs/index.cjs +79 -0
  66. package/dist-cjs/logger.cjs +22 -0
  67. package/dist-cjs/md/match.cjs +47 -0
  68. package/dist-cjs/md/normalize.cjs +13 -0
  69. package/dist-cjs/md/outline.cjs +49 -0
  70. package/dist-cjs/md/parse.cjs +199 -0
  71. package/dist-cjs/ofs/adapter.cjs +195 -0
  72. package/dist-cjs/ofs/enricher.cjs +151 -0
  73. package/dist-cjs/ofs/infer.cjs +63 -0
  74. package/dist-cjs/ofs/issuesEnvelope.cjs +76 -0
  75. package/dist-cjs/ofs/memory.cjs +26 -0
  76. package/dist-cjs/ofs/parser.cjs +373 -0
  77. package/dist-cjs/ofs/stringify.cjs +45 -0
  78. package/dist-cjs/pipeline/enforce.cjs +49 -0
  79. package/dist-cjs/pipeline/kind.cjs +30 -0
  80. package/dist-cjs/pipeline/repair.cjs +115 -0
  81. package/dist-cjs/strictness/container.cjs +49 -0
  82. package/dist-cjs/strictness/processor.cjs +32 -0
  83. package/dist-cjs/strictness/types.cjs +109 -0
  84. package/dist-cjs/tokens/auto-fix.cjs +59 -0
  85. package/dist-cjs/tokens/cognitive-cost.cjs +209 -0
  86. package/dist-cjs/tokens/compliance.cjs +74 -0
  87. package/dist-cjs/tokens/confidence.cjs +335 -0
  88. package/dist-cjs/tokens/estimator.cjs +157 -0
  89. package/dist-cjs/tokens/improvements.cjs +701 -0
  90. package/dist-cjs/tokens/index.cjs +74 -0
  91. package/dist-cjs/tokens/parser.cjs +100 -0
  92. package/dist-cjs/tokens/patterns.cjs +23 -0
  93. package/dist-cjs/tokens/runtime-estimator.cjs +74 -0
  94. package/dist-cjs/tokens/smart-report.cjs +191 -0
  95. package/dist-cjs/tokens/spec-estimator.cjs +125 -0
  96. package/dist-cjs/tokens/types.cjs +2 -0
  97. package/dist-cjs/tokens/validator.cjs +62 -0
  98. package/dist-cjs/types.cjs +2 -0
  99. package/dist-cjs/validate/compliance.cjs +103 -0
  100. package/dist-cjs/validate/connection.cjs +47 -0
  101. package/dist-cjs/validate/types.cjs +2 -0
  102. package/dist-cjs/validate/validate.cjs +319 -0
  103. package/docs/consumption.md +1 -1
  104. 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
+ }