flex-md 3.5.0 → 4.1.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.
Files changed (74) hide show
  1. package/README.md +423 -39
  2. package/dist/index.cjs +62 -3
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +2 -0
  5. package/dist/md/parse.d.ts +1 -0
  6. package/dist/md/parse.js +12 -0
  7. package/dist/ofs/parser.js +31 -10
  8. package/dist/tokens/auto-fix.d.ts +10 -0
  9. package/dist/tokens/auto-fix.js +56 -0
  10. package/dist/tokens/cognitive-cost.d.ts +10 -0
  11. package/dist/tokens/cognitive-cost.js +205 -0
  12. package/dist/tokens/compliance.d.ts +10 -0
  13. package/dist/tokens/compliance.js +70 -0
  14. package/dist/tokens/confidence.d.ts +6 -0
  15. package/dist/tokens/confidence.js +332 -0
  16. package/dist/tokens/estimator.d.ts +12 -0
  17. package/dist/tokens/estimator.js +138 -0
  18. package/dist/tokens/improvements.d.ts +10 -0
  19. package/dist/tokens/improvements.js +697 -0
  20. package/dist/tokens/index.d.ts +24 -0
  21. package/dist/tokens/index.js +31 -0
  22. package/dist/tokens/parser.d.ts +3 -0
  23. package/dist/tokens/parser.js +97 -0
  24. package/dist/tokens/patterns.d.ts +9 -0
  25. package/dist/tokens/patterns.js +20 -0
  26. package/dist/tokens/smart-report.d.ts +10 -0
  27. package/dist/tokens/smart-report.js +187 -0
  28. package/dist/tokens/spec-estimator.d.ts +7 -0
  29. package/dist/tokens/spec-estimator.js +68 -0
  30. package/dist/tokens/types.d.ts +185 -0
  31. package/dist/tokens/validator.d.ts +16 -0
  32. package/dist/tokens/validator.js +59 -0
  33. package/docs/Recommended New Strategies for AI Request Builder.md +691 -0
  34. package/package.json +7 -3
  35. package/dist/detection/detector.d.ts +0 -6
  36. package/dist/detection/detector.js +0 -104
  37. package/dist/detection/extractor.d.ts +0 -10
  38. package/dist/detection/extractor.js +0 -54
  39. package/dist/issues/build.d.ts +0 -26
  40. package/dist/issues/build.js +0 -62
  41. package/dist/md/lists.d.ts +0 -14
  42. package/dist/md/lists.js +0 -33
  43. package/dist/md/tables.d.ts +0 -25
  44. package/dist/md/tables.js +0 -72
  45. package/dist/ofs/extractor.d.ts +0 -9
  46. package/dist/ofs/extractor.js +0 -75
  47. package/dist/ofs/issues.d.ts +0 -14
  48. package/dist/ofs/issues.js +0 -92
  49. package/dist/ofs/validator.d.ts +0 -10
  50. package/dist/ofs/validator.js +0 -91
  51. package/dist/outline/builder.d.ts +0 -10
  52. package/dist/outline/builder.js +0 -85
  53. package/dist/outline/renderer.d.ts +0 -6
  54. package/dist/outline/renderer.js +0 -23
  55. package/dist/parser.d.ts +0 -2
  56. package/dist/parser.js +0 -199
  57. package/dist/parsers/lists.d.ts +0 -6
  58. package/dist/parsers/lists.js +0 -36
  59. package/dist/parsers/tables.d.ts +0 -10
  60. package/dist/parsers/tables.js +0 -58
  61. package/dist/stringify.d.ts +0 -2
  62. package/dist/stringify.js +0 -110
  63. package/dist/test-pipeline.js +0 -53
  64. package/dist/test-runner.d.ts +0 -1
  65. package/dist/test-runner.js +0 -331
  66. package/dist/test-strictness.d.ts +0 -1
  67. package/dist/test-strictness.js +0 -213
  68. package/dist/util.d.ts +0 -5
  69. package/dist/util.js +0 -64
  70. package/dist/validate/policy.d.ts +0 -10
  71. package/dist/validate/policy.js +0 -17
  72. package/dist/validator.d.ts +0 -2
  73. package/dist/validator.js +0 -80
  74. /package/dist/{test-pipeline.d.ts → tokens/types.js} +0 -0
@@ -96,36 +96,53 @@ export function parseOutputFormatSpec(md, opts = {}) {
96
96
  const tables = [];
97
97
  let emptySectionValue;
98
98
  let inTables = false;
99
+ let currentSection = null;
99
100
  for (const rawLine of block.split("\n")) {
100
101
  const line = rawLine.trim();
101
102
  if (/^tables\b/i.test(line)) {
102
103
  inTables = true;
104
+ currentSection = null;
103
105
  continue;
104
106
  }
105
107
  if (/^empty sections\b/i.test(line)) {
106
108
  inTables = false;
109
+ currentSection = null;
107
110
  continue;
108
111
  }
109
112
  // Empty section rule
110
113
  const mNone = line.match(/write\s+`([^`]+)`/i);
111
114
  if (/empty/i.test(line) && mNone) {
112
115
  emptySectionValue = mNone[1];
116
+ currentSection = null;
113
117
  continue;
114
118
  }
115
119
  // bullet items
116
120
  const bullet = line.match(/^- (.+)$/);
117
- if (!bullet)
118
- continue;
119
- const item = bullet[1];
120
- if (inTables) {
121
- const t = parseTableDecl(item);
122
- if (t)
123
- tables.push(t);
121
+ if (bullet) {
122
+ const item = bullet[1];
123
+ if (inTables) {
124
+ const t = parseTableDecl(item);
125
+ if (t)
126
+ tables.push(t);
127
+ currentSection = null;
128
+ }
129
+ else {
130
+ const s = parseSectionDecl(item, !!opts.allowDelimiterFallbacks);
131
+ if (s) {
132
+ sections.push(s);
133
+ currentSection = s;
134
+ }
135
+ else {
136
+ currentSection = null;
137
+ }
138
+ }
124
139
  continue;
125
140
  }
126
- const s = parseSectionDecl(item, !!opts.allowDelimiterFallbacks);
127
- if (s)
128
- sections.push(s);
141
+ // If not a bullet and we have a current section, it's an instruction
142
+ if (currentSection && line.length > 0) {
143
+ const existing = currentSection.instruction || "";
144
+ currentSection.instruction = existing ? `${existing} ${line}` : line;
145
+ }
129
146
  }
130
147
  if (!sections.length)
131
148
  return null;
@@ -169,6 +186,10 @@ function normalizeSectionKind(rest) {
169
186
  return "list";
170
187
  if (r.includes("prose") || r.includes("text"))
171
188
  return "text";
189
+ if (r.includes("ordered") && r.includes("table"))
190
+ return "ordered_table";
191
+ if (r.includes("table"))
192
+ return "table";
172
193
  return null;
173
194
  }
174
195
  function parseTableDecl(item) {
@@ -0,0 +1,10 @@
1
+ import type { AutoFixResult, Improvement } from './types.js';
2
+ import type { OutputFormatSpec } from '../types.js';
3
+ /**
4
+ * Automatically apply fixable improvements
5
+ */
6
+ export declare function autoFix(spec: OutputFormatSpec, improvements: Improvement[], options?: {
7
+ applyQuickWinsOnly?: boolean;
8
+ maxPriority?: Improvement['priority'];
9
+ skipManual?: boolean;
10
+ }): AutoFixResult;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Automatically apply fixable improvements
3
+ */
4
+ export function autoFix(spec, improvements, options = {}) {
5
+ const { applyQuickWinsOnly = false, maxPriority = 'low', skipManual = true } = options;
6
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
7
+ const maxPriorityLevel = priorityOrder[maxPriority] ?? 3;
8
+ const appliedFixes = [];
9
+ const skippedFixes = [];
10
+ const fixedSections = spec.sections.map(section => {
11
+ const sectionImprovements = improvements.filter(imp => imp.sectionName === section.name);
12
+ let currentInstruction = section.instruction;
13
+ let modified = false;
14
+ // Sort improvements by priority for this section
15
+ const sortedImps = [...sectionImprovements].sort((a, b) => (priorityOrder[a.priority] ?? 3) - (priorityOrder[b.priority] ?? 3));
16
+ for (const improvement of sortedImps) {
17
+ // Check if should apply
18
+ if ((priorityOrder[improvement.priority] ?? 3) > maxPriorityLevel)
19
+ continue;
20
+ if (applyQuickWinsOnly && improvement.effort !== 'trivial' && improvement.effort !== 'easy')
21
+ continue;
22
+ if (!improvement.autoFixable) {
23
+ skippedFixes.push({
24
+ sectionName: section.name,
25
+ reason: 'Requires manual review',
26
+ manualActionNeeded: improvement.suggestion
27
+ });
28
+ continue;
29
+ }
30
+ // Apply fix (only the first one for simplicity, or could chain)
31
+ // For now, we chain them by using the 'after' of the previous as 'before' of the next
32
+ // But 'before' in improvement object is relative to the ORIGINAL state.
33
+ // So we just apply the one with the highest priority for the section.
34
+ appliedFixes.push({
35
+ sectionName: section.name,
36
+ improvement: improvement.issue,
37
+ before: currentInstruction || '',
38
+ after: improvement.after
39
+ });
40
+ currentInstruction = improvement.after;
41
+ modified = true;
42
+ break; // Only apply one fix per section in this pass to avoid conflicts
43
+ }
44
+ return modified ? { ...section, instruction: currentInstruction } : section;
45
+ });
46
+ const summary = `Applied ${appliedFixes.length} fixes, skipped ${skippedFixes.length}`;
47
+ return {
48
+ fixed: {
49
+ ...spec,
50
+ sections: fixedSections
51
+ },
52
+ appliedFixes,
53
+ skippedFixes,
54
+ summary
55
+ };
56
+ }
@@ -0,0 +1,10 @@
1
+ import type { CognitiveCost, LevelCostComparison } from './types.js';
2
+ import type { OutputFormatSpec } from '../types.js';
3
+ /**
4
+ * Calculate cognitive cost of creating/maintaining spec
5
+ */
6
+ export declare function calculateCognitiveCost(spec: OutputFormatSpec): CognitiveCost;
7
+ /**
8
+ * Compare cognitive cost across different compliance levels
9
+ */
10
+ export declare function compareLevelCosts(spec: OutputFormatSpec): LevelCostComparison;
@@ -0,0 +1,205 @@
1
+ import { parseSystemPart } from './parser.js';
2
+ import { detectSystemPartLevel } from './compliance.js';
3
+ /**
4
+ * Calculate cognitive cost of creating/maintaining spec
5
+ */
6
+ export function calculateCognitiveCost(spec) {
7
+ const perSection = [];
8
+ let totalCost = 0;
9
+ let complexityCost = 0;
10
+ let decisionCost = 0;
11
+ for (const section of spec.sections) {
12
+ const sectionCost = calculateSectionCost(section);
13
+ perSection.push(sectionCost);
14
+ totalCost += sectionCost.cost;
15
+ // Track breakdown
16
+ complexityCost += getComplexityCost(section.kind || 'text');
17
+ decisionCost += getDecisionCost(sectionCost.level);
18
+ }
19
+ const baseCost = spec.sections.length * 1; // Base effort per section
20
+ return {
21
+ totalCost: Math.min(100, totalCost),
22
+ perSection,
23
+ breakdown: {
24
+ baseCost,
25
+ complexityCost,
26
+ decisionCost,
27
+ totalSections: spec.sections.length
28
+ },
29
+ recommendation: generateCostRecommendation(totalCost, spec.sections.length)
30
+ };
31
+ }
32
+ /**
33
+ * Calculate cost for individual section
34
+ */
35
+ function calculateSectionCost(section) {
36
+ const kind = section.kind || 'text';
37
+ const systemPart = parseSystemPart(section.instruction, kind);
38
+ const level = systemPart ? detectSystemPartLevel(systemPart, kind) : null;
39
+ const factors = [];
40
+ let cost = 0;
41
+ // Base cost by level
42
+ const levelCosts = { 0: 0, 1: 1, 2: 3, 3: 5 };
43
+ const levelCost = levelCosts[level || 0];
44
+ cost += levelCost;
45
+ if (level !== null && level > 0) {
46
+ factors.push({
47
+ factor: 'system_part_level',
48
+ impact: levelCost,
49
+ description: `L${level} requires ${getLevelEffortDescription(level)}`
50
+ });
51
+ }
52
+ // Complexity by section kind
53
+ const kindCost = getComplexityCost(kind);
54
+ cost += kindCost;
55
+ if (kindCost > 0) {
56
+ factors.push({
57
+ factor: 'section_complexity',
58
+ impact: kindCost,
59
+ description: getKindComplexityDescription(kind)
60
+ });
61
+ }
62
+ // Additional cost for specific patterns
63
+ if (systemPart) {
64
+ const patternCost = getPatternCost(systemPart);
65
+ cost += patternCost.cost;
66
+ if (patternCost.cost > 0) {
67
+ factors.push({
68
+ factor: 'pattern_complexity',
69
+ impact: patternCost.cost,
70
+ description: patternCost.description
71
+ });
72
+ }
73
+ }
74
+ return {
75
+ sectionName: section.name,
76
+ cost,
77
+ level,
78
+ factors
79
+ };
80
+ }
81
+ function getLevelEffortDescription(level) {
82
+ const descriptions = {
83
+ 0: 'no thinking',
84
+ 1: 'picking from 4 simple options',
85
+ 2: 'thinking about ranges',
86
+ 3: 'considering minimums and approximations'
87
+ };
88
+ return descriptions[level];
89
+ }
90
+ function getComplexityCost(kind) {
91
+ const costs = {
92
+ 'text': 0, // Simplest
93
+ 'list': 1,
94
+ 'ordered_list': 1,
95
+ 'code': 1,
96
+ 'table': 2, // Most complex (2 dimensions)
97
+ 'ordered_table': 2
98
+ };
99
+ return costs[kind] || 0;
100
+ }
101
+ function getKindComplexityDescription(kind) {
102
+ const descriptions = {
103
+ 'text': 'Simple single value',
104
+ 'list': 'Need to estimate item count',
105
+ 'ordered_list': 'Need to estimate item count',
106
+ 'code': 'Need to estimate line count',
107
+ 'table': 'Need to think about rows AND columns',
108
+ 'ordered_table': 'Need to think about rows AND columns'
109
+ };
110
+ return descriptions[kind] || '';
111
+ }
112
+ function getDecisionCost(level) {
113
+ if (level === null)
114
+ return 0;
115
+ const costs = { 0: 0, 1: 0.5, 2: 1.5, 3: 2.5 };
116
+ return costs[level];
117
+ }
118
+ function getPatternCost(systemPart) {
119
+ const { parsed } = systemPart;
120
+ switch (parsed.type) {
121
+ case 'items':
122
+ case 'lines':
123
+ if (parsed.atLeast) {
124
+ return { cost: 1, description: 'Deciding "at least" minimum requires judgment' };
125
+ }
126
+ if (parsed.max !== null) {
127
+ return { cost: 0.5, description: 'Ranges require estimating both min and max' };
128
+ }
129
+ return { cost: 0, description: '' };
130
+ case 'table':
131
+ let tableCost = 0;
132
+ let desc = [];
133
+ if (parsed.rows.atLeast || parsed.columns.atLeast) {
134
+ tableCost += 1;
135
+ desc.push('minimums require judgment');
136
+ }
137
+ if (parsed.rows.max !== null || parsed.columns.max !== null) {
138
+ tableCost += 0.5;
139
+ desc.push('ranges require estimation');
140
+ }
141
+ return {
142
+ cost: tableCost,
143
+ description: desc.length > 0 ? `Two dimensions: ${desc.join(', ')}` : ''
144
+ };
145
+ default:
146
+ return { cost: 0, description: '' };
147
+ }
148
+ }
149
+ function generateCostRecommendation(totalCost, sectionCount) {
150
+ if (sectionCount === 0)
151
+ return 'Empty spec';
152
+ const avgCost = totalCost / sectionCount;
153
+ if (avgCost < 2) {
154
+ return 'Low cognitive load - easy to write and maintain';
155
+ }
156
+ else if (avgCost < 4) {
157
+ return 'Moderate cognitive load - reasonable effort required';
158
+ }
159
+ else if (avgCost < 6) {
160
+ return 'High cognitive load - consider simplifying some sections';
161
+ }
162
+ else {
163
+ return 'Very high cognitive load - strongly consider reducing to L2 or L1 for some sections';
164
+ }
165
+ }
166
+ /**
167
+ * Compare cognitive cost across different compliance levels
168
+ */
169
+ export function compareLevelCosts(spec) {
170
+ const costs = { 0: 0, 1: 0, 2: 0, 3: 0 };
171
+ // Simulate cost at each level
172
+ for (let level = 0; level <= 3; level++) {
173
+ costs[level] = estimateCostAtLevel(spec, level);
174
+ }
175
+ return {
176
+ byLevel: costs,
177
+ recommendation: getBestLevelForCost(costs)
178
+ };
179
+ }
180
+ function estimateCostAtLevel(spec, level) {
181
+ const levelBaseCosts = { 0: 0, 1: 1, 2: 3, 3: 5 };
182
+ const baseCost = levelBaseCosts[level];
183
+ let total = spec.sections.length * baseCost;
184
+ // Add complexity cost
185
+ for (const section of spec.sections) {
186
+ total += getComplexityCost(section.kind || 'text');
187
+ total += getDecisionCost(level);
188
+ }
189
+ return total;
190
+ }
191
+ function getBestLevelForCost(costs) {
192
+ // L2 is usually the sweet spot
193
+ if (costs[2] < 50) {
194
+ return 'L2 recommended - good balance of precision and effort';
195
+ }
196
+ else if (costs[1] < 30) {
197
+ return 'L1 recommended - keep it simple for this spec';
198
+ }
199
+ else if (costs[3] < 70) {
200
+ return 'L3 acceptable - complexity is manageable';
201
+ }
202
+ else {
203
+ return 'Consider reducing spec size or using L1/L2';
204
+ }
205
+ }
@@ -0,0 +1,10 @@
1
+ import type { ComplianceLevel, ComplianceReport, SystemPart } from './types.js';
2
+ import type { OutputFormatSpec, SectionKind } from '../types.js';
3
+ /**
4
+ * Detect the compliance level of a system part
5
+ */
6
+ export declare function detectSystemPartLevel(systemPart: SystemPart, kind: SectionKind | 'code'): ComplianceLevel;
7
+ /**
8
+ * Check if a spec complies with a target level
9
+ */
10
+ export declare function checkSpecCompliance(spec: OutputFormatSpec, targetLevel?: ComplianceLevel): ComplianceReport;
@@ -0,0 +1,70 @@
1
+ import { parseSystemPart } from './parser.js';
2
+ /**
3
+ * Detect the compliance level of a system part
4
+ */
5
+ export function detectSystemPartLevel(systemPart, kind) {
6
+ const { parsed } = systemPart;
7
+ switch (parsed.type) {
8
+ case 'length':
9
+ // Ranges in lengths are L2
10
+ if (['1-2 sentences', '2-3 paragraphs'].includes(parsed.value)) {
11
+ return 2;
12
+ }
13
+ return 1;
14
+ case 'items':
15
+ case 'lines':
16
+ if (parsed.atLeast || parsed.approximate)
17
+ return 3;
18
+ if (parsed.max !== null)
19
+ return 2;
20
+ return 1;
21
+ case 'table':
22
+ if (parsed.rows.atLeast || parsed.columns.atLeast)
23
+ return 3;
24
+ if (parsed.rows.max !== null || parsed.columns.max !== null)
25
+ return 2;
26
+ return 1;
27
+ default:
28
+ return 0;
29
+ }
30
+ }
31
+ /**
32
+ * Check if a spec complies with a target level
33
+ */
34
+ export function checkSpecCompliance(spec, targetLevel = 2) {
35
+ const violations = [];
36
+ let totalLevel = 0;
37
+ let compliantCount = 0;
38
+ for (const section of spec.sections) {
39
+ const kind = section.kind || 'text';
40
+ const systemPart = parseSystemPart(section.instruction, kind);
41
+ const level = systemPart ? detectSystemPartLevel(systemPart, kind) : 0;
42
+ totalLevel += level;
43
+ if (level >= targetLevel) {
44
+ compliantCount++;
45
+ }
46
+ else if (section.required !== false) {
47
+ violations.push({
48
+ sectionName: section.name,
49
+ kind,
50
+ level,
51
+ message: `Section '${section.name}' is at L${level}, but target is L${targetLevel}`
52
+ });
53
+ }
54
+ else {
55
+ // Optional sections don't cause violations but we still count them if they are compliant
56
+ if (level >= targetLevel)
57
+ compliantCount++;
58
+ }
59
+ }
60
+ return {
61
+ compliant: violations.length === 0,
62
+ level: targetLevel,
63
+ violations,
64
+ summary: {
65
+ totalSections: spec.sections.length,
66
+ compliantSections: compliantCount,
67
+ averageLevel: spec.sections.length > 0 ? totalLevel / spec.sections.length : 0
68
+ }
69
+ };
70
+ }
@@ -0,0 +1,6 @@
1
+ import type { ConfidenceScore } from './types.js';
2
+ import type { OutputFormatSpec } from '../types.js';
3
+ /**
4
+ * Calculate confidence in token estimation
5
+ */
6
+ export declare function calculateConfidence(spec: OutputFormatSpec): ConfidenceScore;