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.
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,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processResponseMarkdown = processResponseMarkdown;
4
+ const validate_js_1 = require("../validate/validate.cjs");
5
+ const extract_js_1 = require("../extract/extract.cjs");
6
+ const issuesEnvelope_js_1 = require("../ofs/issuesEnvelope.cjs");
7
+ /**
8
+ * Unified entry point for processing a response against an OFS.
9
+ */
10
+ function processResponseMarkdown(text, spec, strict // StrictnessOptions
11
+ ) {
12
+ const level = strict.level ?? 0;
13
+ const validation = (0, validate_js_1.validateMarkdownAgainstOfs)(text, spec, level, strict.policy);
14
+ const result = {
15
+ ok: validation.ok,
16
+ strictness: strict,
17
+ usedContainer: validation.stats?.container.fenceCount === 1,
18
+ innerMarkdown: text, // Simplified: ideally strip container here
19
+ validation,
20
+ issues: validation.issues,
21
+ };
22
+ if (validation.ok) {
23
+ result.extracted = (0, extract_js_1.extractFromMarkdown)(text, spec);
24
+ }
25
+ else {
26
+ const issuesEnv = (0, issuesEnvelope_js_1.parseIssuesEnvelope)(text);
27
+ if (issuesEnv.isIssuesEnvelope) {
28
+ result.issuesEnvelope = issuesEnv;
29
+ }
30
+ }
31
+ return result;
32
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.strictnessDefaults = strictnessDefaults;
4
+ function strictnessDefaults(level) {
5
+ const common = {
6
+ tablesGuidance: "auto",
7
+ listsGuidance: "auto",
8
+ emptyNoneGuidance: "auto",
9
+ containerGuidance: "auto",
10
+ typedJsonGuidance: "auto",
11
+ acceptAnyHeadingLevel: true,
12
+ duplicateSectionStrategy: "merge",
13
+ };
14
+ if (level === 0) {
15
+ return {
16
+ ...common,
17
+ level,
18
+ container: "none",
19
+ requireOfs: false,
20
+ requireAllSections: false,
21
+ policy: {
22
+ outsideContainer: "ignore",
23
+ missingContainer: "ignore",
24
+ multipleContainers: "ignore",
25
+ missingRequiredSection: "warn",
26
+ missingOptionalSection: "ignore",
27
+ wrongSectionKindRequired: "warn",
28
+ wrongSectionKindOptional: "ignore",
29
+ emptySectionWithoutNoneRequired: "warn",
30
+ emptySectionWithoutNoneOptional: "ignore",
31
+ missingRequiredTable: "warn",
32
+ missingOptionalTable: "ignore",
33
+ orderedTableViolationsRequired: "warn",
34
+ orderedTableViolationsOptional: "ignore",
35
+ }
36
+ };
37
+ }
38
+ if (level === 1) {
39
+ return {
40
+ ...common,
41
+ level,
42
+ container: "none",
43
+ requireOfs: true,
44
+ requireAllSections: true,
45
+ policy: {
46
+ missingRequiredSection: "error",
47
+ missingOptionalSection: "ignore",
48
+ wrongSectionKindRequired: "error",
49
+ wrongSectionKindOptional: "warn",
50
+ emptySectionWithoutNoneRequired: "error",
51
+ emptySectionWithoutNoneOptional: "warn",
52
+ missingRequiredTable: "error",
53
+ missingOptionalTable: "ignore",
54
+ orderedTableViolationsRequired: "error",
55
+ orderedTableViolationsOptional: "warn",
56
+ }
57
+ };
58
+ }
59
+ if (level === 2) {
60
+ return {
61
+ ...common,
62
+ level,
63
+ container: "markdown_fence",
64
+ requireSingleContainer: true,
65
+ requireOfs: true,
66
+ requireAllSections: true,
67
+ policy: {
68
+ outsideContainer: "error",
69
+ missingContainer: "error",
70
+ multipleContainers: "error",
71
+ missingRequiredSection: "error",
72
+ missingOptionalSection: "ignore",
73
+ wrongSectionKindRequired: "error",
74
+ wrongSectionKindOptional: "warn",
75
+ emptySectionWithoutNoneRequired: "error",
76
+ emptySectionWithoutNoneOptional: "warn",
77
+ missingRequiredTable: "error",
78
+ missingOptionalTable: "ignore",
79
+ orderedTableViolationsRequired: "error",
80
+ orderedTableViolationsOptional: "warn",
81
+ }
82
+ };
83
+ }
84
+ return {
85
+ ...common,
86
+ level: 3,
87
+ container: "markdown_fence",
88
+ requireSingleContainer: true,
89
+ requireOfs: true,
90
+ requireAllSections: true,
91
+ requireTypedJson: true,
92
+ policy: {
93
+ outsideContainer: "error",
94
+ missingContainer: "error",
95
+ multipleContainers: "error",
96
+ missingRequiredSection: "error",
97
+ missingOptionalSection: "ignore",
98
+ wrongSectionKindRequired: "error",
99
+ wrongSectionKindOptional: "warn",
100
+ emptySectionWithoutNoneRequired: "error",
101
+ emptySectionWithoutNoneOptional: "warn",
102
+ missingRequiredTable: "error",
103
+ missingOptionalTable: "ignore",
104
+ orderedTableViolationsRequired: "error",
105
+ orderedTableViolationsOptional: "warn",
106
+ jsonPayloadMissing: "error"
107
+ }
108
+ };
109
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.autoFix = autoFix;
4
+ /**
5
+ * Automatically apply fixable improvements
6
+ */
7
+ function autoFix(spec, improvements, options = {}) {
8
+ const { applyQuickWinsOnly = false, maxPriority = 'low', skipManual = true } = options;
9
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
10
+ const maxPriorityLevel = priorityOrder[maxPriority] ?? 3;
11
+ const appliedFixes = [];
12
+ const skippedFixes = [];
13
+ const fixedSections = spec.sections.map(section => {
14
+ const sectionImprovements = improvements.filter(imp => imp.sectionName === section.name);
15
+ let currentInstruction = section.instruction;
16
+ let modified = false;
17
+ // Sort improvements by priority for this section
18
+ const sortedImps = [...sectionImprovements].sort((a, b) => (priorityOrder[a.priority] ?? 3) - (priorityOrder[b.priority] ?? 3));
19
+ for (const improvement of sortedImps) {
20
+ // Check if should apply
21
+ if ((priorityOrder[improvement.priority] ?? 3) > maxPriorityLevel)
22
+ continue;
23
+ if (applyQuickWinsOnly && improvement.effort !== 'trivial' && improvement.effort !== 'easy')
24
+ continue;
25
+ if (!improvement.autoFixable) {
26
+ skippedFixes.push({
27
+ sectionName: section.name,
28
+ reason: 'Requires manual review',
29
+ manualActionNeeded: improvement.suggestion
30
+ });
31
+ continue;
32
+ }
33
+ // Apply fix (only the first one for simplicity, or could chain)
34
+ // For now, we chain them by using the 'after' of the previous as 'before' of the next
35
+ // But 'before' in improvement object is relative to the ORIGINAL state.
36
+ // So we just apply the one with the highest priority for the section.
37
+ appliedFixes.push({
38
+ sectionName: section.name,
39
+ improvement: improvement.issue,
40
+ before: currentInstruction || '',
41
+ after: improvement.after
42
+ });
43
+ currentInstruction = improvement.after;
44
+ modified = true;
45
+ break; // Only apply one fix per section in this pass to avoid conflicts
46
+ }
47
+ return modified ? { ...section, instruction: currentInstruction } : section;
48
+ });
49
+ const summary = `Applied ${appliedFixes.length} fixes, skipped ${skippedFixes.length}`;
50
+ return {
51
+ fixed: {
52
+ ...spec,
53
+ sections: fixedSections
54
+ },
55
+ appliedFixes,
56
+ skippedFixes,
57
+ summary
58
+ };
59
+ }
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateCognitiveCost = calculateCognitiveCost;
4
+ exports.compareLevelCosts = compareLevelCosts;
5
+ const parser_js_1 = require("./parser.cjs");
6
+ const compliance_js_1 = require("./compliance.cjs");
7
+ /**
8
+ * Calculate cognitive cost of creating/maintaining spec
9
+ */
10
+ function calculateCognitiveCost(spec) {
11
+ const perSection = [];
12
+ let totalCost = 0;
13
+ let complexityCost = 0;
14
+ let decisionCost = 0;
15
+ for (const section of spec.sections) {
16
+ const sectionCost = calculateSectionCost(section);
17
+ perSection.push(sectionCost);
18
+ totalCost += sectionCost.cost;
19
+ // Track breakdown
20
+ complexityCost += getComplexityCost(section.kind || 'text');
21
+ decisionCost += getDecisionCost(sectionCost.level);
22
+ }
23
+ const baseCost = spec.sections.length * 1; // Base effort per section
24
+ return {
25
+ totalCost: Math.min(100, totalCost),
26
+ perSection,
27
+ breakdown: {
28
+ baseCost,
29
+ complexityCost,
30
+ decisionCost,
31
+ totalSections: spec.sections.length
32
+ },
33
+ recommendation: generateCostRecommendation(totalCost, spec.sections.length)
34
+ };
35
+ }
36
+ /**
37
+ * Calculate cost for individual section
38
+ */
39
+ function calculateSectionCost(section) {
40
+ const kind = section.kind || 'text';
41
+ const systemPart = (0, parser_js_1.parseSystemPart)(section.instruction, kind);
42
+ const level = systemPart ? (0, compliance_js_1.detectSystemPartLevel)(systemPart, kind) : null;
43
+ const factors = [];
44
+ let cost = 0;
45
+ // Base cost by level
46
+ const levelCosts = { 0: 0, 1: 1, 2: 3, 3: 5 };
47
+ const levelCost = levelCosts[level || 0];
48
+ cost += levelCost;
49
+ if (level !== null && level > 0) {
50
+ factors.push({
51
+ factor: 'system_part_level',
52
+ impact: levelCost,
53
+ description: `L${level} requires ${getLevelEffortDescription(level)}`
54
+ });
55
+ }
56
+ // Complexity by section kind
57
+ const kindCost = getComplexityCost(kind);
58
+ cost += kindCost;
59
+ if (kindCost > 0) {
60
+ factors.push({
61
+ factor: 'section_complexity',
62
+ impact: kindCost,
63
+ description: getKindComplexityDescription(kind)
64
+ });
65
+ }
66
+ // Additional cost for specific patterns
67
+ if (systemPart) {
68
+ const patternCost = getPatternCost(systemPart);
69
+ cost += patternCost.cost;
70
+ if (patternCost.cost > 0) {
71
+ factors.push({
72
+ factor: 'pattern_complexity',
73
+ impact: patternCost.cost,
74
+ description: patternCost.description
75
+ });
76
+ }
77
+ }
78
+ return {
79
+ sectionName: section.name,
80
+ cost,
81
+ level,
82
+ factors
83
+ };
84
+ }
85
+ function getLevelEffortDescription(level) {
86
+ const descriptions = {
87
+ 0: 'no thinking',
88
+ 1: 'picking from 4 simple options',
89
+ 2: 'thinking about ranges',
90
+ 3: 'considering minimums and approximations'
91
+ };
92
+ return descriptions[level];
93
+ }
94
+ function getComplexityCost(kind) {
95
+ const costs = {
96
+ 'text': 0, // Simplest
97
+ 'list': 1,
98
+ 'ordered_list': 1,
99
+ 'code': 1,
100
+ 'table': 2, // Most complex (2 dimensions)
101
+ 'ordered_table': 2
102
+ };
103
+ return costs[kind] || 0;
104
+ }
105
+ function getKindComplexityDescription(kind) {
106
+ const descriptions = {
107
+ 'text': 'Simple single value',
108
+ 'list': 'Need to estimate item count',
109
+ 'ordered_list': 'Need to estimate item count',
110
+ 'code': 'Need to estimate line count',
111
+ 'table': 'Need to think about rows AND columns',
112
+ 'ordered_table': 'Need to think about rows AND columns'
113
+ };
114
+ return descriptions[kind] || '';
115
+ }
116
+ function getDecisionCost(level) {
117
+ if (level === null)
118
+ return 0;
119
+ const costs = { 0: 0, 1: 0.5, 2: 1.5, 3: 2.5 };
120
+ return costs[level];
121
+ }
122
+ function getPatternCost(systemPart) {
123
+ const { parsed } = systemPart;
124
+ switch (parsed.type) {
125
+ case 'items':
126
+ case 'lines':
127
+ if (parsed.atLeast) {
128
+ return { cost: 1, description: 'Deciding "at least" minimum requires judgment' };
129
+ }
130
+ if (parsed.max !== null) {
131
+ return { cost: 0.5, description: 'Ranges require estimating both min and max' };
132
+ }
133
+ return { cost: 0, description: '' };
134
+ case 'table':
135
+ let tableCost = 0;
136
+ let desc = [];
137
+ if (parsed.rows.atLeast || parsed.columns.atLeast) {
138
+ tableCost += 1;
139
+ desc.push('minimums require judgment');
140
+ }
141
+ if (parsed.rows.max !== null || parsed.columns.max !== null) {
142
+ tableCost += 0.5;
143
+ desc.push('ranges require estimation');
144
+ }
145
+ return {
146
+ cost: tableCost,
147
+ description: desc.length > 0 ? `Two dimensions: ${desc.join(', ')}` : ''
148
+ };
149
+ default:
150
+ return { cost: 0, description: '' };
151
+ }
152
+ }
153
+ function generateCostRecommendation(totalCost, sectionCount) {
154
+ if (sectionCount === 0)
155
+ return 'Empty spec';
156
+ const avgCost = totalCost / sectionCount;
157
+ if (avgCost < 2) {
158
+ return 'Low cognitive load - easy to write and maintain';
159
+ }
160
+ else if (avgCost < 4) {
161
+ return 'Moderate cognitive load - reasonable effort required';
162
+ }
163
+ else if (avgCost < 6) {
164
+ return 'High cognitive load - consider simplifying some sections';
165
+ }
166
+ else {
167
+ return 'Very high cognitive load - strongly consider reducing to L2 or L1 for some sections';
168
+ }
169
+ }
170
+ /**
171
+ * Compare cognitive cost across different compliance levels
172
+ */
173
+ function compareLevelCosts(spec) {
174
+ const costs = { 0: 0, 1: 0, 2: 0, 3: 0 };
175
+ // Simulate cost at each level
176
+ for (let level = 0; level <= 3; level++) {
177
+ costs[level] = estimateCostAtLevel(spec, level);
178
+ }
179
+ return {
180
+ byLevel: costs,
181
+ recommendation: getBestLevelForCost(costs)
182
+ };
183
+ }
184
+ function estimateCostAtLevel(spec, level) {
185
+ const levelBaseCosts = { 0: 0, 1: 1, 2: 3, 3: 5 };
186
+ const baseCost = levelBaseCosts[level];
187
+ let total = spec.sections.length * baseCost;
188
+ // Add complexity cost
189
+ for (const section of spec.sections) {
190
+ total += getComplexityCost(section.kind || 'text');
191
+ total += getDecisionCost(level);
192
+ }
193
+ return total;
194
+ }
195
+ function getBestLevelForCost(costs) {
196
+ // L2 is usually the sweet spot
197
+ if (costs[2] < 50) {
198
+ return 'L2 recommended - good balance of precision and effort';
199
+ }
200
+ else if (costs[1] < 30) {
201
+ return 'L1 recommended - keep it simple for this spec';
202
+ }
203
+ else if (costs[3] < 70) {
204
+ return 'L3 acceptable - complexity is manageable';
205
+ }
206
+ else {
207
+ return 'Consider reducing spec size or using L1/L2';
208
+ }
209
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectSystemPartLevel = detectSystemPartLevel;
4
+ exports.checkSpecCompliance = checkSpecCompliance;
5
+ const parser_js_1 = require("./parser.cjs");
6
+ /**
7
+ * Detect the compliance level of a system part
8
+ */
9
+ function detectSystemPartLevel(systemPart, kind) {
10
+ const { parsed } = systemPart;
11
+ switch (parsed.type) {
12
+ case 'length':
13
+ // Ranges in lengths are L2
14
+ if (['1-2 sentences', '2-3 paragraphs'].includes(parsed.value)) {
15
+ return 2;
16
+ }
17
+ return 1;
18
+ case 'items':
19
+ case 'lines':
20
+ if (parsed.atLeast || parsed.approximate)
21
+ return 3;
22
+ if (parsed.max !== null)
23
+ return 2;
24
+ return 1;
25
+ case 'table':
26
+ if (parsed.rows.atLeast || parsed.columns.atLeast)
27
+ return 3;
28
+ if (parsed.rows.max !== null || parsed.columns.max !== null)
29
+ return 2;
30
+ return 1;
31
+ default:
32
+ return 0;
33
+ }
34
+ }
35
+ /**
36
+ * Check if a spec complies with a target level
37
+ */
38
+ function checkSpecCompliance(spec, targetLevel = 2) {
39
+ const violations = [];
40
+ let totalLevel = 0;
41
+ let compliantCount = 0;
42
+ for (const section of spec.sections) {
43
+ const kind = section.kind || 'text';
44
+ const systemPart = (0, parser_js_1.parseSystemPart)(section.instruction, kind);
45
+ const level = systemPart ? detectSystemPartLevel(systemPart, kind) : 0;
46
+ totalLevel += level;
47
+ if (level >= targetLevel) {
48
+ compliantCount++;
49
+ }
50
+ else if (section.required !== false) {
51
+ violations.push({
52
+ sectionName: section.name,
53
+ kind,
54
+ level,
55
+ message: `Section '${section.name}' is at L${level}, but target is L${targetLevel}`
56
+ });
57
+ }
58
+ else {
59
+ // Optional sections don't cause violations but we still count them if they are compliant
60
+ if (level >= targetLevel)
61
+ compliantCount++;
62
+ }
63
+ }
64
+ return {
65
+ compliant: violations.length === 0,
66
+ level: targetLevel,
67
+ violations,
68
+ summary: {
69
+ totalSections: spec.sections.length,
70
+ compliantSections: compliantCount,
71
+ averageLevel: spec.sections.length > 0 ? totalLevel / spec.sections.length : 0
72
+ }
73
+ };
74
+ }