flex-md 3.2.0 → 4.0.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/README.md +423 -39
- package/dist/__tests__/structural.test.js +28 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +108 -0
- package/dist/index.cjs +62 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/md/outline.d.ts +6 -3
- package/dist/md/outline.js +28 -50
- package/dist/md/parse.js +15 -4
- package/dist/ofs/parser.js +31 -10
- package/dist/tokens/auto-fix.d.ts +10 -0
- package/dist/tokens/auto-fix.js +56 -0
- package/dist/tokens/cognitive-cost.d.ts +10 -0
- package/dist/tokens/cognitive-cost.js +205 -0
- package/dist/tokens/compliance.d.ts +10 -0
- package/dist/tokens/compliance.js +70 -0
- package/dist/tokens/confidence.d.ts +6 -0
- package/dist/tokens/confidence.js +332 -0
- package/dist/tokens/estimator.d.ts +12 -0
- package/dist/tokens/estimator.js +138 -0
- package/dist/tokens/improvements.d.ts +10 -0
- package/dist/tokens/improvements.js +697 -0
- package/dist/tokens/index.d.ts +24 -0
- package/dist/tokens/index.js +31 -0
- package/dist/tokens/parser.d.ts +3 -0
- package/dist/tokens/parser.js +97 -0
- package/dist/tokens/patterns.d.ts +9 -0
- package/dist/tokens/patterns.js +20 -0
- package/dist/tokens/smart-report.d.ts +10 -0
- package/dist/tokens/smart-report.js +187 -0
- package/dist/tokens/spec-estimator.d.ts +7 -0
- package/dist/tokens/spec-estimator.js +68 -0
- package/dist/tokens/types.d.ts +185 -0
- package/dist/tokens/validator.d.ts +16 -0
- package/dist/tokens/validator.js +59 -0
- package/dist/validate/connection.d.ts +12 -0
- package/dist/validate/connection.js +44 -0
- package/docs/Recommended New Strategies for AI Request Builder.md +691 -0
- package/package.json +8 -3
- package/dist/detection/detector.d.ts +0 -6
- package/dist/detection/detector.js +0 -104
- package/dist/detection/extractor.d.ts +0 -10
- package/dist/detection/extractor.js +0 -54
- package/dist/issues/build.d.ts +0 -26
- package/dist/issues/build.js +0 -62
- package/dist/md/lists.d.ts +0 -14
- package/dist/md/lists.js +0 -33
- package/dist/md/tables.d.ts +0 -25
- package/dist/md/tables.js +0 -72
- package/dist/ofs/extractor.d.ts +0 -9
- package/dist/ofs/extractor.js +0 -75
- package/dist/ofs/issues.d.ts +0 -14
- package/dist/ofs/issues.js +0 -92
- package/dist/ofs/validator.d.ts +0 -10
- package/dist/ofs/validator.js +0 -91
- package/dist/outline/builder.d.ts +0 -10
- package/dist/outline/builder.js +0 -85
- package/dist/outline/renderer.d.ts +0 -6
- package/dist/outline/renderer.js +0 -23
- package/dist/parser.d.ts +0 -2
- package/dist/parser.js +0 -199
- package/dist/parsers/lists.d.ts +0 -6
- package/dist/parsers/lists.js +0 -36
- package/dist/parsers/tables.d.ts +0 -10
- package/dist/parsers/tables.js +0 -58
- package/dist/stringify.d.ts +0 -2
- package/dist/stringify.js +0 -110
- package/dist/test-pipeline.js +0 -53
- package/dist/test-runner.js +0 -331
- package/dist/test-strictness.d.ts +0 -1
- package/dist/test-strictness.js +0 -213
- package/dist/util.d.ts +0 -5
- package/dist/util.js +0 -64
- package/dist/validate/policy.d.ts +0 -10
- package/dist/validate/policy.js +0 -17
- package/dist/validator.d.ts +0 -2
- package/dist/validator.js +0 -80
- /package/dist/{test-pipeline.d.ts → __tests__/structural.test.d.ts} +0 -0
- /package/dist/{test-runner.d.ts → tokens/types.js} +0 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { parseSystemPart } from './parser.js';
|
|
2
|
+
import { detectSystemPartLevel } from './compliance.js';
|
|
3
|
+
/**
|
|
4
|
+
* Main improvement detector
|
|
5
|
+
*/
|
|
6
|
+
export function detectImprovements(spec, targetLevel = 2) {
|
|
7
|
+
const improvements = [];
|
|
8
|
+
const antiPatterns = [];
|
|
9
|
+
const optimizations = [];
|
|
10
|
+
// 1. Check each section for improvements
|
|
11
|
+
for (const section of spec.sections) {
|
|
12
|
+
improvements.push(...detectSectionImprovements(section, targetLevel));
|
|
13
|
+
antiPatterns.push(...detectSectionAntiPatterns(section));
|
|
14
|
+
}
|
|
15
|
+
// 2. Detect spec-level optimizations
|
|
16
|
+
optimizations.push(...detectSpecOptimizations(spec));
|
|
17
|
+
// 3. Identify quick wins
|
|
18
|
+
const quickWins = improvements.filter(imp => imp.effort === 'trivial' || imp.effort === 'easy').filter(imp => imp.impact.confidenceGain >= 10 || imp.impact.estimateAccuracyGain >= 10);
|
|
19
|
+
// 4. Calculate summary
|
|
20
|
+
const summary = calculateImprovementSummary(improvements, quickWins);
|
|
21
|
+
return {
|
|
22
|
+
improvements: improvements.sort(byPriorityAndImpact),
|
|
23
|
+
quickWins: quickWins.sort(byImpact),
|
|
24
|
+
antiPatterns,
|
|
25
|
+
optimizations,
|
|
26
|
+
summary
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Detect improvements for individual section
|
|
31
|
+
*/
|
|
32
|
+
function detectSectionImprovements(section, targetLevel) {
|
|
33
|
+
const improvements = [];
|
|
34
|
+
const kind = (section.kind || 'text');
|
|
35
|
+
const systemPart = parseSystemPart(section.instruction, kind);
|
|
36
|
+
const currentLevel = systemPart ? detectSystemPartLevel(systemPart, kind) : null;
|
|
37
|
+
// 1. Missing system part
|
|
38
|
+
if (!systemPart) {
|
|
39
|
+
improvements.push({
|
|
40
|
+
sectionName: section.name,
|
|
41
|
+
priority: section.required !== false ? 'critical' : 'high',
|
|
42
|
+
category: 'missing',
|
|
43
|
+
issue: 'No system part present',
|
|
44
|
+
impact: {
|
|
45
|
+
confidenceGain: 20,
|
|
46
|
+
cognitiveLoadChange: targetLevel === 1 ? 1 : targetLevel === 2 ? 3 : 5,
|
|
47
|
+
estimateAccuracyGain: 25,
|
|
48
|
+
description: 'Token estimation will use fallback values without system part'
|
|
49
|
+
},
|
|
50
|
+
suggestion: `Add system part for ${kind} section`,
|
|
51
|
+
before: section.instruction || '(no instruction)',
|
|
52
|
+
after: generateSuggestedInstruction(section, targetLevel),
|
|
53
|
+
effort: 'easy',
|
|
54
|
+
autoFixable: true
|
|
55
|
+
});
|
|
56
|
+
return improvements;
|
|
57
|
+
}
|
|
58
|
+
// 2. Vague or overly simple values
|
|
59
|
+
const vagueness = detectVagueness(systemPart, kind);
|
|
60
|
+
if (vagueness) {
|
|
61
|
+
vagueness.sectionName = section.name; // Fill in section name
|
|
62
|
+
improvements.push(vagueness);
|
|
63
|
+
}
|
|
64
|
+
// 3. Insufficient level for target
|
|
65
|
+
if (currentLevel !== null && currentLevel < targetLevel) {
|
|
66
|
+
improvements.push({
|
|
67
|
+
sectionName: section.name,
|
|
68
|
+
priority: section.required !== false ? 'high' : 'medium',
|
|
69
|
+
category: 'upgrade',
|
|
70
|
+
issue: `Current level (L${currentLevel}) below target (L${targetLevel})`,
|
|
71
|
+
impact: {
|
|
72
|
+
confidenceGain: (targetLevel - currentLevel) * 8,
|
|
73
|
+
cognitiveLoadChange: (targetLevel - currentLevel) * 2,
|
|
74
|
+
estimateAccuracyGain: (targetLevel - currentLevel) * 10,
|
|
75
|
+
description: `Upgrading to L${targetLevel} will improve estimation accuracy`
|
|
76
|
+
},
|
|
77
|
+
suggestion: `Upgrade from L${currentLevel} to L${targetLevel}`,
|
|
78
|
+
before: section.instruction,
|
|
79
|
+
after: upgradeInstruction(section, systemPart, currentLevel, targetLevel),
|
|
80
|
+
effort: targetLevel - currentLevel === 1 ? 'easy' : 'moderate',
|
|
81
|
+
autoFixable: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// 4. Invalid ranges
|
|
85
|
+
const rangeIssue = detectRangeIssues(systemPart, section);
|
|
86
|
+
if (rangeIssue) {
|
|
87
|
+
improvements.push(rangeIssue);
|
|
88
|
+
}
|
|
89
|
+
// 5. Unreasonable values
|
|
90
|
+
const unreasonable = detectUnreasonableValues(systemPart, section);
|
|
91
|
+
if (unreasonable) {
|
|
92
|
+
improvements.push(unreasonable);
|
|
93
|
+
}
|
|
94
|
+
// 6. Missing guidance part
|
|
95
|
+
if (!systemPart.guidance && kind !== 'table' && kind !== 'ordered_table') {
|
|
96
|
+
improvements.push({
|
|
97
|
+
sectionName: section.name,
|
|
98
|
+
priority: 'low',
|
|
99
|
+
category: 'optimize',
|
|
100
|
+
issue: 'System part exists but no guidance for LLM',
|
|
101
|
+
impact: {
|
|
102
|
+
confidenceGain: 0,
|
|
103
|
+
cognitiveLoadChange: 1,
|
|
104
|
+
estimateAccuracyGain: 0,
|
|
105
|
+
description: 'Adding guidance helps LLM understand intent better'
|
|
106
|
+
},
|
|
107
|
+
suggestion: 'Add guidance after system part',
|
|
108
|
+
before: section.instruction,
|
|
109
|
+
after: `${systemPart.raw}. ${generateDefaultGuidance(kind)}`,
|
|
110
|
+
effort: 'easy',
|
|
111
|
+
autoFixable: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return improvements;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Detect vagueness in system parts
|
|
118
|
+
*/
|
|
119
|
+
function detectVagueness(systemPart, kind) {
|
|
120
|
+
const { parsed } = systemPart;
|
|
121
|
+
// Check for overly vague "at least" with low minimum
|
|
122
|
+
if (parsed.type === 'items' && parsed.atLeast && parsed.min <= 2) {
|
|
123
|
+
return {
|
|
124
|
+
sectionName: '', // Will be filled by caller
|
|
125
|
+
priority: 'medium',
|
|
126
|
+
category: 'vague',
|
|
127
|
+
issue: `"at least ${parsed.min}" is too vague`,
|
|
128
|
+
impact: {
|
|
129
|
+
confidenceGain: 15,
|
|
130
|
+
cognitiveLoadChange: 1,
|
|
131
|
+
estimateAccuracyGain: 20,
|
|
132
|
+
description: 'Very low minimums create wide estimation ranges'
|
|
133
|
+
},
|
|
134
|
+
suggestion: 'Use specific range or higher minimum',
|
|
135
|
+
before: systemPart.raw,
|
|
136
|
+
after: `Items: ${Math.max(3, parsed.min)}-${Math.max(5, parsed.min + 2)}`,
|
|
137
|
+
effort: 'trivial',
|
|
138
|
+
autoFixable: true
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Check for overly wide ranges
|
|
142
|
+
if (parsed.type === 'items' && parsed.max !== null) {
|
|
143
|
+
const range = parsed.max - parsed.min;
|
|
144
|
+
if (range > 10) {
|
|
145
|
+
return {
|
|
146
|
+
sectionName: '',
|
|
147
|
+
priority: 'medium',
|
|
148
|
+
category: 'vague',
|
|
149
|
+
issue: `Range too wide (${parsed.min}-${parsed.max})`,
|
|
150
|
+
impact: {
|
|
151
|
+
confidenceGain: 10,
|
|
152
|
+
cognitiveLoadChange: 0,
|
|
153
|
+
estimateAccuracyGain: 15,
|
|
154
|
+
description: 'Wide ranges reduce estimation accuracy'
|
|
155
|
+
},
|
|
156
|
+
suggestion: 'Narrow the range to ≤5-7 items',
|
|
157
|
+
before: systemPart.raw,
|
|
158
|
+
after: `Items: ${parsed.min}-${Math.min(parsed.max, parsed.min + 5)}`,
|
|
159
|
+
effort: 'trivial',
|
|
160
|
+
autoFixable: true
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Check for vague text lengths
|
|
165
|
+
if (parsed.type === 'length' && ['moderate', 'detailed', 'extensive'].includes(parsed.value)) {
|
|
166
|
+
return {
|
|
167
|
+
sectionName: '',
|
|
168
|
+
priority: 'low',
|
|
169
|
+
category: 'vague',
|
|
170
|
+
issue: `"${parsed.value}" is generic - consider specific measurement`,
|
|
171
|
+
impact: {
|
|
172
|
+
confidenceGain: 8,
|
|
173
|
+
cognitiveLoadChange: 1,
|
|
174
|
+
estimateAccuracyGain: 10,
|
|
175
|
+
description: 'Specific measurements (sentences/paragraphs) are more precise'
|
|
176
|
+
},
|
|
177
|
+
suggestion: 'Use specific sentence or paragraph count',
|
|
178
|
+
before: systemPart.raw,
|
|
179
|
+
after: 'Length: 2-3 paragraphs',
|
|
180
|
+
effort: 'trivial',
|
|
181
|
+
autoFixable: true
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Detect range issues (max < min, etc.)
|
|
188
|
+
*/
|
|
189
|
+
function detectRangeIssues(systemPart, section) {
|
|
190
|
+
const { parsed } = systemPart;
|
|
191
|
+
if (parsed.type === 'items' || parsed.type === 'lines') {
|
|
192
|
+
if (parsed.max !== null && parsed.max < parsed.min) {
|
|
193
|
+
return {
|
|
194
|
+
sectionName: section.name,
|
|
195
|
+
priority: 'critical',
|
|
196
|
+
category: 'fix',
|
|
197
|
+
issue: `Invalid range: max (${parsed.max}) < min (${parsed.min})`,
|
|
198
|
+
impact: {
|
|
199
|
+
confidenceGain: 0,
|
|
200
|
+
cognitiveLoadChange: 0,
|
|
201
|
+
estimateAccuracyGain: 0,
|
|
202
|
+
description: 'Invalid range will cause errors'
|
|
203
|
+
},
|
|
204
|
+
suggestion: 'Fix invalid range',
|
|
205
|
+
before: systemPart.raw,
|
|
206
|
+
after: `${parsed.type === 'items' ? 'Items' : 'Lines'}: ${parsed.min}-${parsed.min + 3}`,
|
|
207
|
+
effort: 'trivial',
|
|
208
|
+
autoFixable: true
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (parsed.type === 'table') {
|
|
213
|
+
const issues = [];
|
|
214
|
+
if (parsed.rows.max !== null && parsed.rows.max < parsed.rows.min) {
|
|
215
|
+
issues.push(`rows: ${parsed.rows.min}-${parsed.rows.max}`);
|
|
216
|
+
}
|
|
217
|
+
if (parsed.columns.max !== null && parsed.columns.max < parsed.columns.min) {
|
|
218
|
+
issues.push(`columns: ${parsed.columns.min}-${parsed.columns.max}`);
|
|
219
|
+
}
|
|
220
|
+
if (issues.length > 0) {
|
|
221
|
+
return {
|
|
222
|
+
sectionName: section.name,
|
|
223
|
+
priority: 'critical',
|
|
224
|
+
category: 'fix',
|
|
225
|
+
issue: `Invalid table range: ${issues.join(', ')}`,
|
|
226
|
+
impact: {
|
|
227
|
+
confidenceGain: 0,
|
|
228
|
+
cognitiveLoadChange: 0,
|
|
229
|
+
estimateAccuracyGain: 0,
|
|
230
|
+
description: 'Invalid range will cause errors'
|
|
231
|
+
},
|
|
232
|
+
suggestion: 'Fix invalid table dimensions',
|
|
233
|
+
before: systemPart.raw,
|
|
234
|
+
after: `Rows: ${parsed.rows.min}-${Math.max(parsed.rows.min + 2, parsed.rows.max || 0)}, Columns: ${parsed.columns.min}`,
|
|
235
|
+
effort: 'trivial',
|
|
236
|
+
autoFixable: true
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Detect unreasonable values
|
|
244
|
+
*/
|
|
245
|
+
function detectUnreasonableValues(systemPart, section) {
|
|
246
|
+
const { parsed } = systemPart;
|
|
247
|
+
if (parsed.type === 'items') {
|
|
248
|
+
if (parsed.min > 50) {
|
|
249
|
+
return {
|
|
250
|
+
sectionName: section.name,
|
|
251
|
+
priority: 'high',
|
|
252
|
+
category: 'fix',
|
|
253
|
+
issue: `${parsed.min} items seems unreasonably high`,
|
|
254
|
+
impact: {
|
|
255
|
+
confidenceGain: 5,
|
|
256
|
+
cognitiveLoadChange: -2,
|
|
257
|
+
estimateAccuracyGain: 10,
|
|
258
|
+
description: 'Very high item counts are unusual and may be errors'
|
|
259
|
+
},
|
|
260
|
+
suggestion: 'Verify item count or reduce',
|
|
261
|
+
before: systemPart.raw,
|
|
262
|
+
after: `Items: 10-15`,
|
|
263
|
+
effort: 'easy',
|
|
264
|
+
autoFixable: false
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (parsed.type === 'table') {
|
|
269
|
+
if (parsed.rows.min > 30 || parsed.columns.min > 15) {
|
|
270
|
+
return {
|
|
271
|
+
sectionName: section.name,
|
|
272
|
+
priority: 'high',
|
|
273
|
+
category: 'fix',
|
|
274
|
+
issue: `Table size (${parsed.rows.min}×${parsed.columns.min}) seems unreasonably large`,
|
|
275
|
+
impact: {
|
|
276
|
+
confidenceGain: 5,
|
|
277
|
+
cognitiveLoadChange: -2,
|
|
278
|
+
estimateAccuracyGain: 10,
|
|
279
|
+
description: 'Very large tables are difficult for LLMs to generate accurately'
|
|
280
|
+
},
|
|
281
|
+
suggestion: 'Consider reducing table size or splitting',
|
|
282
|
+
before: systemPart.raw,
|
|
283
|
+
after: `Rows: 5-10, Columns: ${Math.min(5, parsed.columns.min)}`,
|
|
284
|
+
effort: 'moderate',
|
|
285
|
+
autoFixable: false
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (parsed.type === 'lines') {
|
|
290
|
+
if (parsed.min > 200) {
|
|
291
|
+
return {
|
|
292
|
+
sectionName: section.name,
|
|
293
|
+
priority: 'medium',
|
|
294
|
+
category: 'fix',
|
|
295
|
+
issue: `${parsed.min} lines of code is very large`,
|
|
296
|
+
impact: {
|
|
297
|
+
confidenceGain: 3,
|
|
298
|
+
cognitiveLoadChange: -1,
|
|
299
|
+
estimateAccuracyGain: 5,
|
|
300
|
+
description: 'Very long code sections may exceed context limits'
|
|
301
|
+
},
|
|
302
|
+
suggestion: 'Consider breaking into multiple sections',
|
|
303
|
+
before: systemPart.raw,
|
|
304
|
+
after: `Lines: 30-50`,
|
|
305
|
+
effort: 'moderate',
|
|
306
|
+
autoFixable: false
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Detect anti-patterns
|
|
314
|
+
*/
|
|
315
|
+
function detectSectionAntiPatterns(section) {
|
|
316
|
+
const patterns = [];
|
|
317
|
+
const kind = (section.kind || 'text');
|
|
318
|
+
const systemPart = parseSystemPart(section.instruction, kind);
|
|
319
|
+
const isRequired = section.required !== false;
|
|
320
|
+
// Anti-pattern: Required section with no instruction at all
|
|
321
|
+
if (isRequired && !section.instruction) {
|
|
322
|
+
patterns.push({
|
|
323
|
+
sectionName: section.name,
|
|
324
|
+
pattern: 'required_no_instruction',
|
|
325
|
+
severity: 'error',
|
|
326
|
+
why: 'Required sections should always have instructions',
|
|
327
|
+
fix: 'Add instruction with system part and guidance',
|
|
328
|
+
example: 'Length: 2-3 paragraphs. Explain the methodology and findings.'
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// Anti-pattern: System part without period separator before guidance
|
|
332
|
+
if (section.instruction && systemPart && systemPart.guidance === null) {
|
|
333
|
+
const hasMoreText = section.instruction.length > systemPart.raw.length + 5;
|
|
334
|
+
if (hasMoreText) {
|
|
335
|
+
patterns.push({
|
|
336
|
+
sectionName: section.name,
|
|
337
|
+
pattern: 'missing_separator',
|
|
338
|
+
severity: 'warning',
|
|
339
|
+
why: 'System part should end with ". " to separate from guidance',
|
|
340
|
+
fix: 'Add period and space after system part',
|
|
341
|
+
example: 'Items: 3-5. Focus on the most important points.'
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Anti-pattern: Optional section with higher compliance than required
|
|
346
|
+
if (!isRequired && systemPart) {
|
|
347
|
+
const level = detectSystemPartLevel(systemPart, kind);
|
|
348
|
+
if (level === 3) {
|
|
349
|
+
patterns.push({
|
|
350
|
+
sectionName: section.name,
|
|
351
|
+
pattern: 'optional_high_complexity',
|
|
352
|
+
severity: 'info',
|
|
353
|
+
why: "Optional sections typically don't need L3 precision",
|
|
354
|
+
fix: 'Consider downgrading to L2 to reduce cognitive load',
|
|
355
|
+
example: 'Items: 3-5 (instead of "Items: at least 3")'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Anti-pattern: Table with only one dimension specified as range
|
|
360
|
+
if (systemPart?.parsed.type === 'table') {
|
|
361
|
+
const rowsHasRange = systemPart.parsed.rows.max !== null;
|
|
362
|
+
const columnsHasRange = systemPart.parsed.columns.max !== null;
|
|
363
|
+
if (rowsHasRange !== columnsHasRange) {
|
|
364
|
+
patterns.push({
|
|
365
|
+
sectionName: section.name,
|
|
366
|
+
pattern: 'asymmetric_table_spec',
|
|
367
|
+
severity: 'info',
|
|
368
|
+
why: 'Inconsistent precision between rows and columns',
|
|
369
|
+
fix: 'Either specify both as ranges or both as exact numbers',
|
|
370
|
+
example: 'Rows: 3-5, Columns: 3-4 (both ranges)'
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Anti-pattern: Using approximate (~) for small numbers
|
|
375
|
+
if (systemPart?.parsed.type === 'lines' && systemPart.parsed.approximate && systemPart.parsed.min < 20) {
|
|
376
|
+
patterns.push({
|
|
377
|
+
sectionName: section.name,
|
|
378
|
+
pattern: 'approximate_small_value',
|
|
379
|
+
severity: 'warning',
|
|
380
|
+
why: 'Approximate (~) should be used for larger values (50+)',
|
|
381
|
+
fix: 'Use exact number or range for small values',
|
|
382
|
+
example: 'Lines: 15-20 (instead of ~15)'
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return patterns;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Detect spec-level optimizations
|
|
389
|
+
*/
|
|
390
|
+
function detectSpecOptimizations(spec) {
|
|
391
|
+
const opts = [];
|
|
392
|
+
if (spec.sections.length === 0)
|
|
393
|
+
return opts;
|
|
394
|
+
const firstKind = (spec.sections[0].kind || 'text');
|
|
395
|
+
// Optimization: All sections at L1, could upgrade key ones to L2
|
|
396
|
+
const levels = spec.sections
|
|
397
|
+
.map(s => parseSystemPart(s.instruction, (s.kind || 'text')))
|
|
398
|
+
.filter(sp => sp !== null)
|
|
399
|
+
.map(sp => detectSystemPartLevel(sp, firstKind));
|
|
400
|
+
const allL1 = levels.length > 0 && levels.every(l => l === 1);
|
|
401
|
+
if (allL1 && levels.length >= 3) {
|
|
402
|
+
opts.push({
|
|
403
|
+
type: 'rebalance',
|
|
404
|
+
scope: 'spec',
|
|
405
|
+
description: 'All sections are L1 - consider upgrading required sections to L2',
|
|
406
|
+
benefit: 'Better token estimation accuracy with minimal effort increase',
|
|
407
|
+
effort: 'easy',
|
|
408
|
+
sections: spec.sections.filter(s => s.required !== false).map(s => s.name)
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
// Optimization: Mix of L1, L2, L3 - could standardize
|
|
412
|
+
const hasL1 = levels.some(l => l === 1);
|
|
413
|
+
const hasL2 = levels.some(l => l === 2);
|
|
414
|
+
const hasL3 = levels.some(l => l === 3);
|
|
415
|
+
const varietyCount = [hasL1, hasL2, hasL3].filter(Boolean).length;
|
|
416
|
+
if (varietyCount === 3) {
|
|
417
|
+
opts.push({
|
|
418
|
+
type: 'rebalance',
|
|
419
|
+
scope: 'spec',
|
|
420
|
+
description: 'Spec has sections at L1, L2, and L3 - consider standardizing',
|
|
421
|
+
benefit: 'More consistent cognitive load and maintenance',
|
|
422
|
+
effort: 'moderate',
|
|
423
|
+
sections: undefined
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
// Optimization: Too many sections without system parts
|
|
427
|
+
const withoutSystemParts = spec.sections.filter(s => parseSystemPart(s.instruction, (s.kind || 'text')) === null);
|
|
428
|
+
if (withoutSystemParts.length > spec.sections.length * 0.3) {
|
|
429
|
+
opts.push({
|
|
430
|
+
type: 'simplify',
|
|
431
|
+
scope: 'spec',
|
|
432
|
+
description: `${withoutSystemParts.length} sections missing system parts`,
|
|
433
|
+
benefit: 'Adding system parts will greatly improve token estimation',
|
|
434
|
+
effort: 'easy',
|
|
435
|
+
sections: withoutSystemParts.map(s => s.name)
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
// Optimization: Very large spec (10+ sections)
|
|
439
|
+
if (spec.sections.length > 10) {
|
|
440
|
+
opts.push({
|
|
441
|
+
type: 'split',
|
|
442
|
+
scope: 'spec',
|
|
443
|
+
description: `Spec has ${spec.sections.length} sections - consider splitting`,
|
|
444
|
+
benefit: 'Smaller specs are easier to maintain and use',
|
|
445
|
+
effort: 'hard',
|
|
446
|
+
sections: undefined
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
// Optimization: All text sections could benefit from more specific lengths
|
|
450
|
+
const textSections = spec.sections.filter(s => (s.kind || 'text') === 'text');
|
|
451
|
+
const vagueLengths = textSections.filter(s => {
|
|
452
|
+
const sp = parseSystemPart(s.instruction, 'text');
|
|
453
|
+
return sp?.parsed.type === 'length' &&
|
|
454
|
+
['brief', 'moderate', 'detailed', 'extensive'].includes(sp.parsed.value);
|
|
455
|
+
});
|
|
456
|
+
if (textSections.length > 0 && vagueLengths.length === textSections.length && textSections.length >= 3) {
|
|
457
|
+
opts.push({
|
|
458
|
+
type: 'simplify',
|
|
459
|
+
scope: 'spec',
|
|
460
|
+
description: 'All text sections use generic lengths (brief/moderate/etc)',
|
|
461
|
+
benefit: 'Upgrading to specific counts (1-2 sentences, 2-3 paragraphs) improves accuracy',
|
|
462
|
+
effort: 'easy',
|
|
463
|
+
sections: vagueLengths.map(s => s.name)
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return opts;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Helper: Generate suggested instruction
|
|
470
|
+
*/
|
|
471
|
+
function generateSuggestedInstruction(section, level) {
|
|
472
|
+
const kind = (section.kind || 'text');
|
|
473
|
+
const systemPart = suggestSystemPartForLevel(kind, level);
|
|
474
|
+
const guidance = generateDefaultGuidance(kind);
|
|
475
|
+
if (section.instruction) {
|
|
476
|
+
return `${systemPart}. ${section.instruction}`;
|
|
477
|
+
}
|
|
478
|
+
return `${systemPart}. ${guidance}`;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Helper: Suggest system part for level
|
|
482
|
+
*/
|
|
483
|
+
function suggestSystemPartForLevel(kind, level) {
|
|
484
|
+
if (level <= 1) {
|
|
485
|
+
const defaults = {
|
|
486
|
+
'text': 'Length: brief',
|
|
487
|
+
'list': 'Items: 3',
|
|
488
|
+
'ordered_list': 'Items: 3',
|
|
489
|
+
'code': 'Lines: 10',
|
|
490
|
+
'table': 'Rows: 3, Columns: 3',
|
|
491
|
+
'ordered_table': 'Rows: 3, Columns: 3'
|
|
492
|
+
};
|
|
493
|
+
return defaults[kind] || 'Length: moderate';
|
|
494
|
+
}
|
|
495
|
+
if (level === 2) {
|
|
496
|
+
const defaults = {
|
|
497
|
+
'text': 'Length: 1-2 sentences',
|
|
498
|
+
'list': 'Items: 3-5',
|
|
499
|
+
'ordered_list': 'Items: 3-5',
|
|
500
|
+
'code': 'Lines: 10-20',
|
|
501
|
+
'table': 'Rows: 3-5, Columns: 3',
|
|
502
|
+
'ordered_table': 'Rows: 3-5, Columns: 3'
|
|
503
|
+
};
|
|
504
|
+
return defaults[kind] || 'Length: 1-2 sentences';
|
|
505
|
+
}
|
|
506
|
+
// Level 3
|
|
507
|
+
const defaults = {
|
|
508
|
+
'text': 'Length: 1-2 sentences',
|
|
509
|
+
'list': 'Items: at least 3',
|
|
510
|
+
'ordered_list': 'Items: at least 3',
|
|
511
|
+
'code': 'Lines: ~20',
|
|
512
|
+
'table': 'Rows: at least 3, Columns: 3',
|
|
513
|
+
'ordered_table': 'Rows: at least 3, Columns: 3'
|
|
514
|
+
};
|
|
515
|
+
return defaults[kind] || 'Length: 1-2 sentences';
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Helper: Upgrade instruction to higher level
|
|
519
|
+
*/
|
|
520
|
+
function upgradeInstruction(section, systemPart, currentLevel, targetLevel) {
|
|
521
|
+
const { parsed, guidance } = systemPart;
|
|
522
|
+
let newSystemPart = '';
|
|
523
|
+
if (currentLevel === 1 && targetLevel >= 2) {
|
|
524
|
+
// L1 \u2192 L2: Add ranges
|
|
525
|
+
switch (parsed.type) {
|
|
526
|
+
case 'length':
|
|
527
|
+
newSystemPart = 'Length: 2-3 paragraphs';
|
|
528
|
+
break;
|
|
529
|
+
case 'items':
|
|
530
|
+
newSystemPart = `Items: ${parsed.min}-${parsed.min + 2}`;
|
|
531
|
+
break;
|
|
532
|
+
case 'lines':
|
|
533
|
+
newSystemPart = `Lines: ${parsed.min}-${parsed.min + 10}`;
|
|
534
|
+
break;
|
|
535
|
+
case 'table':
|
|
536
|
+
newSystemPart = `Rows: ${parsed.rows.min}-${parsed.rows.min + 2}, Columns: ${parsed.columns.min}`;
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else if (currentLevel === 2 && targetLevel === 3) {
|
|
541
|
+
// L2 \u2192 L3: Add "at least" or "~"
|
|
542
|
+
switch (parsed.type) {
|
|
543
|
+
case 'items':
|
|
544
|
+
newSystemPart = `Items: at least ${parsed.min}`;
|
|
545
|
+
break;
|
|
546
|
+
case 'lines':
|
|
547
|
+
newSystemPart = `Lines: ~${parsed.min}`;
|
|
548
|
+
break;
|
|
549
|
+
case 'table':
|
|
550
|
+
newSystemPart = `Rows: at least ${parsed.rows.min}, Columns: ${parsed.columns.min}`;
|
|
551
|
+
break;
|
|
552
|
+
default:
|
|
553
|
+
newSystemPart = systemPart.raw;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
newSystemPart = systemPart.raw;
|
|
558
|
+
}
|
|
559
|
+
return guidance ? `${newSystemPart}. ${guidance}` : newSystemPart;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Helper: Generate default guidance
|
|
563
|
+
*/
|
|
564
|
+
function generateDefaultGuidance(kind) {
|
|
565
|
+
const defaults = {
|
|
566
|
+
'text': 'Provide clear and concise explanation',
|
|
567
|
+
'list': 'Focus on the most important points',
|
|
568
|
+
'ordered_list': 'List in order of priority or sequence',
|
|
569
|
+
'table': 'Include relevant data with clear headers',
|
|
570
|
+
'ordered_table': 'Include relevant data with clear headers',
|
|
571
|
+
'code': 'Include comments and error handling'
|
|
572
|
+
};
|
|
573
|
+
return defaults[kind] || 'Provide relevant content';
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Calculate improvement summary
|
|
577
|
+
*/
|
|
578
|
+
function calculateImprovementSummary(improvements, quickWins) {
|
|
579
|
+
const criticalCount = improvements.filter(i => i.priority === 'critical').length;
|
|
580
|
+
const potentialConfidenceGain = improvements.reduce((sum, i) => sum + i.impact.confidenceGain, 0);
|
|
581
|
+
let estimatedEffort = 'minimal';
|
|
582
|
+
if (improvements.length > 10)
|
|
583
|
+
estimatedEffort = 'significant';
|
|
584
|
+
else if (improvements.length > 5)
|
|
585
|
+
estimatedEffort = 'moderate';
|
|
586
|
+
return {
|
|
587
|
+
totalImprovements: improvements.length,
|
|
588
|
+
criticalCount,
|
|
589
|
+
potentialConfidenceGain: Math.min(100, potentialConfidenceGain),
|
|
590
|
+
quickWinCount: quickWins.length,
|
|
591
|
+
estimatedEffort
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
// Sorting functions
|
|
595
|
+
function byPriorityAndImpact(a, b) {
|
|
596
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
597
|
+
const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
598
|
+
if (pDiff !== 0)
|
|
599
|
+
return pDiff;
|
|
600
|
+
return b.impact.confidenceGain - a.impact.confidenceGain;
|
|
601
|
+
}
|
|
602
|
+
function byImpact(a, b) {
|
|
603
|
+
return b.impact.confidenceGain - a.impact.confidenceGain;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Format improvement report
|
|
607
|
+
*/
|
|
608
|
+
export function formatImprovementReport(analysis) {
|
|
609
|
+
const lines = [];
|
|
610
|
+
lines.push('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2551');
|
|
611
|
+
lines.push('\u2551 IMPROVEMENT DETECTION REPORT \u2551');
|
|
612
|
+
lines.push('\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d');
|
|
613
|
+
lines.push('');
|
|
614
|
+
// Summary
|
|
615
|
+
lines.push('\ud83d\udcca SUMMARY');
|
|
616
|
+
lines.push('\u2500'.repeat(50));
|
|
617
|
+
lines.push(`Total improvements found: ${analysis.summary.totalImprovements}`);
|
|
618
|
+
lines.push(`Critical issues: ${analysis.summary.criticalCount}`);
|
|
619
|
+
lines.push(`Quick wins available: ${analysis.summary.quickWinCount}`);
|
|
620
|
+
lines.push(`Potential confidence gain: +${analysis.summary.potentialConfidenceGain}%`);
|
|
621
|
+
lines.push(`Estimated effort: ${analysis.summary.estimatedEffort}`);
|
|
622
|
+
lines.push('');
|
|
623
|
+
// Quick Wins
|
|
624
|
+
if (analysis.quickWins.length > 0) {
|
|
625
|
+
lines.push('\u26a1 QUICK WINS (Low Effort, High Impact)');
|
|
626
|
+
lines.push('\u2500'.repeat(50));
|
|
627
|
+
analysis.quickWins.slice(0, 5).forEach((imp, i) => {
|
|
628
|
+
lines.push(`${i + 1}. "${imp.sectionName}" - ${imp.issue}`);
|
|
629
|
+
lines.push(` Impact: +${imp.impact.confidenceGain}% confidence, +${imp.impact.estimateAccuracyGain}% accuracy`);
|
|
630
|
+
lines.push(` Before: ${imp.before}`);
|
|
631
|
+
lines.push(` After: ${imp.after}`);
|
|
632
|
+
lines.push('');
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
// Critical Issues
|
|
636
|
+
const critical = analysis.improvements.filter(i => i.priority === 'critical');
|
|
637
|
+
if (critical.length > 0) {
|
|
638
|
+
lines.push('\ud83d\udd34 CRITICAL ISSUES');
|
|
639
|
+
lines.push('\u2500'.repeat(50));
|
|
640
|
+
critical.forEach((imp, i) => {
|
|
641
|
+
lines.push(`${i + 1}. "${imp.sectionName}" - ${imp.issue}`);
|
|
642
|
+
lines.push(` Fix: ${imp.suggestion}`);
|
|
643
|
+
lines.push(` Before: ${imp.before}`);
|
|
644
|
+
lines.push(` After: ${imp.after}`);
|
|
645
|
+
lines.push('');
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
// Anti-Patterns
|
|
649
|
+
if (analysis.antiPatterns.length > 0) {
|
|
650
|
+
lines.push('\u26a0\ufe0f ANTI-PATTERNS DETECTED');
|
|
651
|
+
lines.push('\u2500'.repeat(50));
|
|
652
|
+
analysis.antiPatterns.forEach((ap, i) => {
|
|
653
|
+
const icon = ap.severity === 'error' ? '\u274c' : ap.severity === 'warning' ? '\u26a0\ufe0f' : '\u2139\ufe0f';
|
|
654
|
+
lines.push(`${i + 1}. ${icon} "${ap.sectionName}" - ${ap.pattern}`);
|
|
655
|
+
lines.push(` Why: ${ap.why}`);
|
|
656
|
+
lines.push(` Fix: ${ap.fix}`);
|
|
657
|
+
lines.push(` Example: ${ap.example}`);
|
|
658
|
+
lines.push('');
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
// Optimizations
|
|
662
|
+
if (analysis.optimizations.length > 0) {
|
|
663
|
+
lines.push('\ud83d\udca1 OPTIMIZATION OPPORTUNITIES');
|
|
664
|
+
lines.push('\u2500'.repeat(50));
|
|
665
|
+
analysis.optimizations.forEach((opt, i) => {
|
|
666
|
+
lines.push(`${i + 1}. [${opt.type.toUpperCase()}] ${opt.description}`);
|
|
667
|
+
lines.push(` Benefit: ${opt.benefit}`);
|
|
668
|
+
lines.push(` Effort: ${opt.effort}`);
|
|
669
|
+
if (opt.sections) {
|
|
670
|
+
lines.push(` Sections: ${opt.sections.join(', ')}`);
|
|
671
|
+
}
|
|
672
|
+
lines.push('');
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
// All Improvements (grouped by category)
|
|
676
|
+
lines.push('\ud83d\udccb ALL IMPROVEMENTS');
|
|
677
|
+
lines.push('\u2500'.repeat(50));
|
|
678
|
+
const byCategory = {
|
|
679
|
+
missing: analysis.improvements.filter(i => i.category === 'missing'),
|
|
680
|
+
vague: analysis.improvements.filter(i => i.category === 'vague'),
|
|
681
|
+
upgrade: analysis.improvements.filter(i => i.category === 'upgrade'),
|
|
682
|
+
fix: analysis.improvements.filter(i => i.category === 'fix'),
|
|
683
|
+
optimize: analysis.improvements.filter(i => i.category === 'optimize')
|
|
684
|
+
};
|
|
685
|
+
Object.entries(byCategory).forEach(([category, imps]) => {
|
|
686
|
+
if (imps.length > 0) {
|
|
687
|
+
lines.push(`\n${category.toUpperCase()} (${imps.length}):`);
|
|
688
|
+
imps.forEach(imp => {
|
|
689
|
+
const icon = imp.priority === 'critical' ? '\ud83d\udd34' :
|
|
690
|
+
imp.priority === 'high' ? '\ud83d\udfe0' :
|
|
691
|
+
imp.priority === 'medium' ? '\ud83d\udfe1' : '\ud83d\udfe2';
|
|
692
|
+
lines.push(` ${icon} ${imp.sectionName}: ${imp.issue}`);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
return lines.join('\n');
|
|
697
|
+
}
|