planfs-core 1.2.0 → 1.4.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 (78) hide show
  1. package/README.md +107 -0
  2. package/dist/ai.d.ts +1 -19
  3. package/dist/ai.d.ts.map +1 -1
  4. package/dist/ai.js +0 -124
  5. package/dist/ai.js.map +1 -1
  6. package/dist/entity-content.d.ts +6 -0
  7. package/dist/entity-content.d.ts.map +1 -0
  8. package/dist/entity-content.js +94 -0
  9. package/dist/entity-content.js.map +1 -0
  10. package/dist/entity-factory.d.ts +13 -0
  11. package/dist/entity-factory.d.ts.map +1 -0
  12. package/dist/entity-factory.js +109 -0
  13. package/dist/entity-factory.js.map +1 -0
  14. package/dist/files.d.ts.map +1 -1
  15. package/dist/files.js +7 -0
  16. package/dist/files.js.map +1 -1
  17. package/dist/format.d.ts +19 -0
  18. package/dist/format.d.ts.map +1 -0
  19. package/dist/format.js +73 -0
  20. package/dist/format.js.map +1 -0
  21. package/dist/history.d.ts +8 -0
  22. package/dist/history.d.ts.map +1 -0
  23. package/dist/history.js +59 -0
  24. package/dist/history.js.map +1 -0
  25. package/dist/index.d.ts +13 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +13 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/repository.d.ts +5 -29
  30. package/dist/repository.d.ts.map +1 -1
  31. package/dist/repository.js +20 -196
  32. package/dist/repository.js.map +1 -1
  33. package/dist/search.d.ts +3 -0
  34. package/dist/search.d.ts.map +1 -1
  35. package/dist/search.js +34 -1
  36. package/dist/search.js.map +1 -1
  37. package/dist/semantic-analyzer.d.ts +50 -0
  38. package/dist/semantic-analyzer.d.ts.map +1 -0
  39. package/dist/semantic-analyzer.js +315 -0
  40. package/dist/semantic-analyzer.js.map +1 -0
  41. package/dist/semantic-context.d.ts +67 -0
  42. package/dist/semantic-context.d.ts.map +1 -0
  43. package/dist/semantic-context.js +109 -0
  44. package/dist/semantic-context.js.map +1 -0
  45. package/dist/semantic-formatter.d.ts +52 -0
  46. package/dist/semantic-formatter.d.ts.map +1 -0
  47. package/dist/semantic-formatter.js +354 -0
  48. package/dist/semantic-formatter.js.map +1 -0
  49. package/dist/semantic-inspection.d.ts +119 -0
  50. package/dist/semantic-inspection.d.ts.map +1 -0
  51. package/dist/semantic-inspection.js +295 -0
  52. package/dist/semantic-inspection.js.map +1 -0
  53. package/dist/semantic-profiles.d.ts +5 -0
  54. package/dist/semantic-profiles.d.ts.map +1 -0
  55. package/dist/semantic-profiles.js +70 -0
  56. package/dist/semantic-profiles.js.map +1 -0
  57. package/dist/semantic-types.d.ts +199 -0
  58. package/dist/semantic-types.d.ts.map +1 -0
  59. package/dist/semantic-types.js +3 -0
  60. package/dist/semantic-types.js.map +1 -0
  61. package/dist/semantic-validator.d.ts +46 -0
  62. package/dist/semantic-validator.d.ts.map +1 -0
  63. package/dist/semantic-validator.js +313 -0
  64. package/dist/semantic-validator.js.map +1 -0
  65. package/dist/semantic.d.ts +4 -0
  66. package/dist/semantic.d.ts.map +1 -0
  67. package/dist/semantic.js +641 -0
  68. package/dist/semantic.js.map +1 -0
  69. package/dist/task-update.d.ts +26 -0
  70. package/dist/task-update.d.ts.map +1 -0
  71. package/dist/task-update.js +165 -0
  72. package/dist/task-update.js.map +1 -0
  73. package/dist/types.d.ts +2 -0
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/validator.d.ts.map +1 -1
  76. package/dist/validator.js +18 -1
  77. package/dist/validator.js.map +1 -1
  78. package/package.json +4 -3
@@ -0,0 +1,641 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSemanticDocument = parseSemanticDocument;
4
+ const markdown_1 = require("@lezer/markdown");
5
+ const semantic_profiles_1 = require("./semantic-profiles");
6
+ const semanticMarkdownParser = markdown_1.parser.configure(markdown_1.GFM);
7
+ const HEADING_PATTERN = /^(?:ATX|Setext)Heading([1-6])$/;
8
+ const ENTITY_ID_PATTERN = /TASK-[0-9]{3,}|EPIC-[A-Za-z0-9][A-Za-z0-9-]*|MILESTONE-[A-Za-z0-9][A-Za-z0-9-]*|DECISION-[A-Za-z0-9][A-Za-z0-9-]*/g;
9
+ const OPAQUE_NODE_NAMES = new Set(['FencedCode', 'CodeBlock', 'HTMLBlock', 'InlineCode']);
10
+ const VOID_HTML_ELEMENTS = new Set([
11
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',
12
+ 'param', 'source', 'track', 'wbr'
13
+ ]);
14
+ class SourceLocator {
15
+ constructor(source) {
16
+ this.source = source;
17
+ this.lineStarts = [0];
18
+ for (let index = 0; index < source.length; index += 1) {
19
+ if (source.charCodeAt(index) === 10) {
20
+ this.lineStarts.push(index + 1);
21
+ }
22
+ }
23
+ }
24
+ point(offset) {
25
+ const bounded = Math.max(0, Math.min(offset, this.source.length));
26
+ let low = 0;
27
+ let high = this.lineStarts.length;
28
+ while (low + 1 < high) {
29
+ const middle = Math.floor((low + high) / 2);
30
+ if ((this.lineStarts[middle] ?? 0) <= bounded) {
31
+ low = middle;
32
+ }
33
+ else {
34
+ high = middle;
35
+ }
36
+ }
37
+ const lineStart = this.lineStarts[low] ?? 0;
38
+ return { offset: bounded, line: low + 1, column: bounded - lineStart + 1 };
39
+ }
40
+ range(from, to) {
41
+ return { start: this.point(from), end: this.point(to) };
42
+ }
43
+ }
44
+ function parseSemanticDocument(entityType, rawMarkdown, options = {}) {
45
+ const profile = (0, semantic_profiles_1.getSemanticContentProfile)(entityType);
46
+ const locator = new SourceLocator(rawMarkdown);
47
+ try {
48
+ const tree = semanticMarkdownParser.parse(rawMarkdown);
49
+ const root = tree.topNode;
50
+ const rootChildren = children(root);
51
+ const opaqueRanges = collectOpaqueRanges(root, rawMarkdown);
52
+ const sectionWork = buildSections(rootChildren, rawMarkdown, locator, profile);
53
+ const sections = sectionWork.map(work => work.section);
54
+ const knownSections = groupKnownSections(sections);
55
+ const diagnostics = collectStructuralDiagnostics(rootChildren, root, rawMarkdown, locator, sections, knownSections);
56
+ const criteria = extractCriteria(sectionWork, rawMarkdown, locator);
57
+ const findings = extractEntries(sectionWork.filter(work => work.section.key === 'findings'), rawMarkdown, locator);
58
+ const questions = extractEntries(sectionWork.filter(work => work.section.key === 'questions'), rawMarkdown, locator);
59
+ const decisions = extractEntries(sectionWork.filter(work => work.section.key === 'decisions' || (entityType === 'decision' && work.section.key === 'decision')), rawMarkdown, locator);
60
+ const { references, occurrences, diagnostics: referenceDiagnostics } = extractReferences(root, rawMarkdown, locator, sections, opaqueRanges);
61
+ diagnostics.push(...referenceDiagnostics);
62
+ const mentions = extractMentions(rawMarkdown, locator, sections, opaqueRanges, occurrences);
63
+ references.push(...mentions
64
+ .filter(mention => mention.form === 'prose')
65
+ .map(mention => ({
66
+ kind: 'bare-entity-id',
67
+ label: null,
68
+ target: mention.id,
69
+ range: mention.range,
70
+ sectionIndex: mention.sectionIndex,
71
+ provenance: 'rule-inferred'
72
+ })));
73
+ references.sort((left, right) => left.range.start.offset - right.range.start.offset);
74
+ const firstSectionStart = sectionWork[0]?.headingNode.from ?? rawMarkdown.length;
75
+ const preambleMarkdown = rawMarkdown.slice(0, firstSectionStart);
76
+ return {
77
+ contractVersion: '1.0.0',
78
+ profile: { entityType, version: profile.version },
79
+ source: { filePath: options.filePath ?? '', rawMarkdown },
80
+ preamble: {
81
+ markdown: preambleMarkdown,
82
+ text: markdownToText(preambleMarkdown),
83
+ range: locator.range(0, firstSectionStart),
84
+ empty: preambleMarkdown.trim().length === 0
85
+ },
86
+ sections,
87
+ knownSections,
88
+ criteria,
89
+ findings,
90
+ questions,
91
+ decisions,
92
+ references,
93
+ mentions,
94
+ diagnostics: diagnostics.sort(compareDiagnostics),
95
+ conformance: {
96
+ baseline: diagnostics.some(diagnostic => (diagnostic.conformance === 'baseline' && diagnostic.severity === 'error')) ? 'nonconformant' : 'conformant',
97
+ automationReady: 'not-evaluated',
98
+ lifecycle: 'not-evaluated'
99
+ },
100
+ analyzerResults: []
101
+ };
102
+ }
103
+ catch (error) {
104
+ return fallbackDocument(entityType, rawMarkdown, options, locator, error);
105
+ }
106
+ }
107
+ function buildSections(rootChildren, source, locator, profile) {
108
+ const boundaries = rootChildren
109
+ .map((node, rootIndex) => ({ node, rootIndex, level: headingLevel(node) }))
110
+ .filter((item) => item.level === 2);
111
+ return boundaries.map((boundary, index) => {
112
+ const next = boundaries[index + 1];
113
+ const sectionEnd = next?.node.from ?? source.length;
114
+ const headingEnd = semanticNodeEnd(boundary.node, source);
115
+ const contentStart = consumeLineEnding(source, headingEnd);
116
+ const heading = headingText(boundary.node, source);
117
+ const match = matchSection(profile, heading);
118
+ const contentNodes = rootChildren.slice(boundary.rootIndex + 1, next?.rootIndex);
119
+ const contentMarkdown = source.slice(contentStart, sectionEnd);
120
+ const section = {
121
+ index,
122
+ heading,
123
+ normalizedHeading: normalizeHeading(heading),
124
+ headingLevel: 2,
125
+ key: match?.key ?? null,
126
+ provenance: match?.provenance ?? 'rule-inferred',
127
+ headingRange: locator.range(boundary.node.from, headingEnd),
128
+ contentRange: locator.range(contentStart, sectionEnd),
129
+ range: locator.range(boundary.node.from, sectionEnd),
130
+ markdown: source.slice(boundary.node.from, sectionEnd),
131
+ contentMarkdown,
132
+ text: markdownToText(contentMarkdown),
133
+ contentShape: determineContentShape(contentNodes, contentMarkdown),
134
+ empty: contentMarkdown.trim().length === 0,
135
+ subsections: buildSubsections(contentNodes, sectionEnd, source, locator)
136
+ };
137
+ return { section, headingNode: boundary.node, contentNodes };
138
+ });
139
+ }
140
+ function buildSubsections(contentNodes, sectionEnd, source, locator) {
141
+ const headings = contentNodes
142
+ .map(node => ({ node, level: headingLevel(node) }))
143
+ .filter((item) => (item.level !== null && item.level >= 3));
144
+ function build(parentLevel, start, end) {
145
+ const result = [];
146
+ let index = start;
147
+ while (index < end) {
148
+ const current = headings[index];
149
+ if (!current)
150
+ break;
151
+ if (current.level <= parentLevel) {
152
+ index += 1;
153
+ continue;
154
+ }
155
+ let boundary = index + 1;
156
+ while (boundary < end && (headings[boundary]?.level ?? 0) > current.level) {
157
+ boundary += 1;
158
+ }
159
+ const rangeEnd = headings[boundary]?.node.from ?? sectionEnd;
160
+ result.push({
161
+ heading: headingText(current.node, source),
162
+ headingLevel: current.level,
163
+ headingRange: locator.range(current.node.from, semanticNodeEnd(current.node, source)),
164
+ range: locator.range(current.node.from, rangeEnd),
165
+ markdown: source.slice(current.node.from, rangeEnd),
166
+ text: markdownToText(source.slice(consumeLineEnding(source, semanticNodeEnd(current.node, source)), rangeEnd)),
167
+ children: build(current.level, index + 1, boundary)
168
+ });
169
+ index = boundary;
170
+ }
171
+ return result;
172
+ }
173
+ return build(2, 0, headings.length);
174
+ }
175
+ function matchSection(profile, heading) {
176
+ const normalized = normalizeHeading(heading);
177
+ for (const definition of profile.sections) {
178
+ if (normalizeHeading(definition.canonicalHeading) === normalized) {
179
+ return { key: definition.key, provenance: 'canonical' };
180
+ }
181
+ if (definition.aliases.some(alias => normalizeHeading(alias) === normalized)) {
182
+ return { key: definition.key, provenance: 'alias' };
183
+ }
184
+ }
185
+ return undefined;
186
+ }
187
+ function groupKnownSections(sections) {
188
+ var _a;
189
+ const known = {};
190
+ for (const section of sections) {
191
+ if (section.key) {
192
+ (known[_a = section.key] ?? (known[_a] = [])).push(section);
193
+ }
194
+ }
195
+ return known;
196
+ }
197
+ function collectStructuralDiagnostics(rootChildren, root, source, locator, sections, knownSections) {
198
+ const diagnostics = [];
199
+ for (const section of sections) {
200
+ if (section.provenance === 'alias') {
201
+ diagnostics.push(diagnostic('content.section.alias', 'info', `Section heading '${section.heading}' is a supported alias for '${section.key}'.`, section.headingRange, section.index, 'alias', 'automation-ready', 'Use the canonical heading when normalizing this document.', 'format', true));
202
+ }
203
+ if (section.key && section.empty) {
204
+ diagnostics.push(diagnostic('content.section.empty', 'warning', `Recognized section '${section.heading}' is empty.`, section.contentRange, section.index, section.provenance, 'automation-ready', 'Add content or remove the empty section.', 'edit-markdown', false));
205
+ }
206
+ }
207
+ for (const [key, matches] of Object.entries(knownSections)) {
208
+ if (matches.length < 2)
209
+ continue;
210
+ const range = matches[1]?.headingRange ?? null;
211
+ diagnostics.push(diagnostic('content.section.duplicate', 'warning', `Section key '${key}' appears ${matches.length} times.`, range, matches[1]?.index ?? null, matches[1]?.provenance ?? 'rule-inferred', 'automation-ready', 'Review and explicitly merge or retain the duplicate sections.', 'edit-markdown', false, { key, count: matches.length }));
212
+ const distinct = new Set(matches.map(match => normalizeComparisonText(match.text)));
213
+ if (distinct.size > 1) {
214
+ diagnostics.push(diagnostic('content.section.ambiguous', 'warning', `Duplicate '${key}' sections contain different content.`, range, matches[1]?.index ?? null, matches[1]?.provenance ?? 'rule-inferred', 'automation-ready', 'Choose or merge the intended content explicitly.', 'edit-markdown', false, { key }));
215
+ }
216
+ }
217
+ for (const node of rootChildren) {
218
+ if (headingLevel(node) === 1) {
219
+ diagnostics.push(diagnostic('content.unexpected-heading-level', 'warning', 'Level-one headings do not create semantic sections because the entity title is frontmatter-owned.', locator.range(node.from, node.to), sectionIndexAt(node.from, sections), 'rule-inferred', 'automation-ready', 'Use a level-two heading for a top-level semantic section.', 'edit-markdown', true));
220
+ }
221
+ }
222
+ for (const node of descendants(root, candidate => candidate.name === 'FencedCode')) {
223
+ const codeMarks = descendants(node, candidate => candidate.name === 'CodeMark');
224
+ if (codeMarks.length < 2) {
225
+ diagnostics.push(diagnostic('content.markdown.unclosed-fence', 'warning', 'A fenced code block is not closed and consumes the remaining Markdown body.', locator.range(node.from, node.to), sectionIndexAt(node.from, sections), 'rule-inferred', 'baseline', 'Add a matching closing code fence.', 'edit-markdown', true));
226
+ }
227
+ }
228
+ for (const node of descendants(root, candidate => candidate.name === '⚠')) {
229
+ diagnostics.push(diagnostic('content.markdown.unsupported-region', 'error', 'The Markdown parser could not safely classify this region.', locator.range(node.from, node.to), sectionIndexAt(node.from, sections), 'rule-inferred', 'baseline', 'Review the Markdown syntax in this region.', 'edit-markdown', false, { markdown: source.slice(node.from, node.to) }));
230
+ }
231
+ return diagnostics;
232
+ }
233
+ function extractCriteria(work, source, locator) {
234
+ const criteria = [];
235
+ for (const current of work.filter(item => (item.section.key === 'acceptanceCriteria' || item.section.key === 'releaseCriteria'))) {
236
+ const items = current.contentNodes
237
+ .flatMap(node => descendantsIncluding(node, candidate => candidate.name === 'ListItem'))
238
+ .sort((left, right) => left.from - right.from);
239
+ const ids = new Map();
240
+ items.forEach((item, ordinal) => ids.set(nodeIdentity(item), `criterion:${current.section.index}:${ordinal}`));
241
+ for (const item of items) {
242
+ const marker = findTaskMarker(item);
243
+ const parentItem = nearestParent(item, candidate => candidate.name === 'ListItem');
244
+ const contentNode = children(item).find(child => child.name === 'Task' || child.name === 'Paragraph');
245
+ const id = ids.get(nodeIdentity(item));
246
+ if (!id)
247
+ continue;
248
+ criteria.push({
249
+ id,
250
+ checked: marker ? source.slice(marker.from, marker.to).toLowerCase() === '[x]' : null,
251
+ markdown: source.slice(item.from, semanticNodeEnd(item, source)),
252
+ text: markdownToText(contentNode ? source.slice(contentNode.from, contentNode.to) : source.slice(item.from, item.to)),
253
+ range: locator.range(item.from, semanticNodeEnd(item, source)),
254
+ sectionIndex: current.section.index,
255
+ listDepth: ancestorCount(item, candidate => candidate.name === 'ListItem'),
256
+ parentCriterionId: parentItem ? ids.get(nodeIdentity(parentItem)) ?? null : null,
257
+ provenance: current.section.provenance
258
+ });
259
+ }
260
+ }
261
+ return criteria;
262
+ }
263
+ function extractEntries(work, source, locator) {
264
+ const result = [];
265
+ for (const current of work) {
266
+ const items = current.contentNodes.flatMap(node => (descendantsIncluding(node, candidate => candidate.name === 'ListItem')));
267
+ const paragraphs = current.contentNodes.flatMap(node => (descendantsIncluding(node, candidate => (candidate.name === 'Paragraph' && !nearestParent(candidate, parent => parent.name === 'ListItem')))));
268
+ const nodes = [...items, ...paragraphs].sort((left, right) => left.from - right.from);
269
+ for (const node of nodes) {
270
+ result.push({
271
+ markdown: source.slice(node.from, semanticNodeEnd(node, source)),
272
+ text: markdownToText(source.slice(node.from, node.to)),
273
+ range: locator.range(node.from, semanticNodeEnd(node, source)),
274
+ sectionIndex: current.section.index,
275
+ provenance: current.section.provenance
276
+ });
277
+ }
278
+ }
279
+ return result;
280
+ }
281
+ function extractReferences(root, source, locator, sections, opaqueRanges) {
282
+ const references = [];
283
+ const occurrences = [];
284
+ const diagnostics = [];
285
+ const definitions = collectLinkDefinitions(root, source);
286
+ const nodes = descendants(root, node => ['Link', 'Autolink', 'URL'].includes(node.name));
287
+ for (const node of nodes) {
288
+ if (isInsideRanges(node.from, opaqueRanges))
289
+ continue;
290
+ if (node.name === 'URL' && node.parent && ['Link', 'Autolink', 'LinkReference'].includes(node.parent.name)) {
291
+ continue;
292
+ }
293
+ const sectionIndex = sectionIndexAt(node.from, sections);
294
+ const section = sectionIndex === null ? undefined : sections[sectionIndex];
295
+ const provenance = section?.key === 'references' ? section.provenance : 'rule-inferred';
296
+ if (node.name === 'Autolink' || node.name === 'URL') {
297
+ const urlNode = node.name === 'URL'
298
+ ? node
299
+ : descendants(node, child => child.name === 'URL')[0];
300
+ if (!urlNode)
301
+ continue;
302
+ const target = source.slice(urlNode.from, urlNode.to);
303
+ const range = locator.range(node.from, node.to);
304
+ references.push({ kind: 'autolink', label: null, target, range, sectionIndex, provenance });
305
+ occurrences.push({
306
+ range: { from: node.from, to: node.to },
307
+ labelRange: null,
308
+ targetRange: { from: urlNode.from, to: urlNode.to },
309
+ target
310
+ });
311
+ continue;
312
+ }
313
+ const urlNode = descendants(node, child => child.name === 'URL')[0];
314
+ const labelNode = descendants(node, child => child.name === 'LinkLabel')[0];
315
+ const target = urlNode
316
+ ? source.slice(urlNode.from, urlNode.to)
317
+ : labelNode
318
+ ? definitions.get(normalizeLinkLabel(source.slice(labelNode.from, labelNode.to)))
319
+ : undefined;
320
+ const labelRange = linkLabelRange(node);
321
+ if (!target) {
322
+ diagnostics.push(diagnostic('content.reference.unresolved-definition', 'warning', 'A reference-style link does not have a resolvable definition.', locator.range(node.from, node.to), sectionIndex, provenance, 'automation-ready', 'Add or repair the matching link reference definition.', 'edit-markdown', false));
323
+ continue;
324
+ }
325
+ references.push({
326
+ kind: 'link',
327
+ label: labelRange ? markdownToText(source.slice(labelRange.from, labelRange.to)) : null,
328
+ target,
329
+ range: locator.range(node.from, node.to),
330
+ sectionIndex,
331
+ provenance
332
+ });
333
+ occurrences.push({
334
+ range: { from: node.from, to: node.to },
335
+ labelRange,
336
+ targetRange: urlNode ? { from: urlNode.from, to: urlNode.to } : null,
337
+ target
338
+ });
339
+ }
340
+ for (const reference of descendants(root, node => node.name === 'LinkReference')) {
341
+ const urlNode = descendants(reference, node => node.name === 'URL')[0];
342
+ if (!urlNode)
343
+ continue;
344
+ occurrences.push({
345
+ range: { from: reference.from, to: reference.to },
346
+ labelRange: null,
347
+ targetRange: { from: urlNode.from, to: urlNode.to },
348
+ target: source.slice(urlNode.from, urlNode.to)
349
+ });
350
+ }
351
+ return { references, occurrences, diagnostics };
352
+ }
353
+ function collectLinkDefinitions(root, source) {
354
+ const definitions = new Map();
355
+ for (const reference of descendants(root, node => node.name === 'LinkReference')) {
356
+ const label = descendants(reference, node => node.name === 'LinkLabel')[0];
357
+ const url = descendants(reference, node => node.name === 'URL')[0];
358
+ if (label && url) {
359
+ definitions.set(normalizeLinkLabel(source.slice(label.from, label.to)), source.slice(url.from, url.to));
360
+ }
361
+ }
362
+ return definitions;
363
+ }
364
+ function extractMentions(source, locator, sections, opaqueRanges, links) {
365
+ const mentions = [];
366
+ ENTITY_ID_PATTERN.lastIndex = 0;
367
+ let match;
368
+ while ((match = ENTITY_ID_PATTERN.exec(source)) !== null) {
369
+ const id = match[0];
370
+ const from = match.index;
371
+ const to = from + id.length;
372
+ if (!hasEntityBoundaries(source, from, to) || isInsideRanges(from, opaqueRanges)) {
373
+ continue;
374
+ }
375
+ const link = links.find(candidate => from >= candidate.range.from && to <= candidate.range.to);
376
+ const inTarget = link?.targetRange
377
+ ? from >= link.targetRange.from && to <= link.targetRange.to
378
+ : false;
379
+ const inLabel = link?.labelRange
380
+ ? from >= link.labelRange.from && to <= link.labelRange.to
381
+ : false;
382
+ mentions.push({
383
+ id,
384
+ entityType: mentionEntityType(id),
385
+ form: inTarget ? 'link-target' : inLabel ? 'link-label' : 'prose',
386
+ range: locator.range(from, to),
387
+ sectionIndex: sectionIndexAt(from, sections),
388
+ referenceTarget: link?.target ?? null,
389
+ resolved: null,
390
+ authoritative: false,
391
+ provenance: 'rule-inferred'
392
+ });
393
+ }
394
+ return mentions;
395
+ }
396
+ function collectOpaqueRanges(root, source) {
397
+ const ranges = descendants(root, node => OPAQUE_NODE_NAMES.has(node.name))
398
+ .map(node => ({ from: node.from, to: node.to }));
399
+ const htmlTags = descendants(root, node => node.name === 'HTMLTag').sort((a, b) => a.from - b.from);
400
+ const stack = [];
401
+ for (const tag of htmlTags) {
402
+ const raw = source.slice(tag.from, tag.to);
403
+ const parsed = /^<\s*(\/?)\s*([A-Za-z][\w:-]*)/.exec(raw);
404
+ if (!parsed?.[2]) {
405
+ ranges.push({ from: tag.from, to: tag.to });
406
+ continue;
407
+ }
408
+ const name = parsed[2].toLowerCase();
409
+ const closing = parsed[1] === '/';
410
+ if (closing) {
411
+ const openIndex = stack.map(item => item.name).lastIndexOf(name);
412
+ if (openIndex >= 0) {
413
+ const open = stack[openIndex];
414
+ if (open)
415
+ ranges.push({ from: open.from, to: tag.to });
416
+ stack.splice(openIndex);
417
+ }
418
+ else {
419
+ ranges.push({ from: tag.from, to: tag.to });
420
+ }
421
+ }
422
+ else if (/\/\s*>$/.test(raw) || VOID_HTML_ELEMENTS.has(name)) {
423
+ ranges.push({ from: tag.from, to: tag.to });
424
+ }
425
+ else {
426
+ stack.push({ name, from: tag.from });
427
+ }
428
+ }
429
+ ranges.push(...stack.map(open => ({ from: open.from, to: source.length })));
430
+ return mergeRanges(ranges);
431
+ }
432
+ function determineContentShape(nodes, markdown) {
433
+ if (markdown.trim().length === 0)
434
+ return 'empty';
435
+ const hasTask = nodes.some(node => descendantsIncluding(node, child => child.name === 'Task').length > 0);
436
+ const hasList = nodes.some(node => ['BulletList', 'OrderedList'].includes(node.name));
437
+ const hasProse = nodes.some(node => ['Paragraph', 'Blockquote'].includes(node.name));
438
+ if (hasTask && hasProse)
439
+ return 'mixed';
440
+ if (hasTask)
441
+ return 'task-list';
442
+ const hasReference = nodes.some(node => (descendantsIncluding(node, child => ['Link', 'Autolink', 'URL'].includes(child.name)).length > 0));
443
+ if ((hasList || hasReference) && hasProse)
444
+ return 'mixed';
445
+ if (hasList)
446
+ return 'list';
447
+ if (hasReference)
448
+ return 'references';
449
+ return 'prose';
450
+ }
451
+ function fallbackDocument(entityType, rawMarkdown, options, locator, error) {
452
+ const message = error instanceof Error ? error.message : String(error);
453
+ return {
454
+ contractVersion: '1.0.0',
455
+ profile: { entityType, version: '1.0.0' },
456
+ source: { filePath: options.filePath ?? '', rawMarkdown },
457
+ preamble: {
458
+ markdown: rawMarkdown,
459
+ text: markdownToText(rawMarkdown),
460
+ range: locator.range(0, rawMarkdown.length),
461
+ empty: rawMarkdown.trim().length === 0
462
+ },
463
+ sections: [],
464
+ knownSections: {},
465
+ criteria: [],
466
+ findings: [],
467
+ questions: [],
468
+ decisions: [],
469
+ references: [],
470
+ mentions: [],
471
+ diagnostics: [diagnostic('content.markdown.unsupported-region', 'error', `Markdown parsing failed without discarding the raw body: ${message}`, locator.range(0, rawMarkdown.length), null, 'rule-inferred', 'baseline', 'Review the Markdown body and parser compatibility.', 'edit-markdown', false)],
472
+ conformance: {
473
+ baseline: 'nonconformant',
474
+ automationReady: 'not-evaluated',
475
+ lifecycle: 'not-evaluated'
476
+ },
477
+ analyzerResults: []
478
+ };
479
+ }
480
+ function diagnostic(code, severity, message, range, sectionIndex, provenance, conformance, repairSummary, repairKind, previewable, data) {
481
+ return {
482
+ code,
483
+ severity,
484
+ message,
485
+ range,
486
+ sectionIndex,
487
+ provenance,
488
+ conformance,
489
+ repair: { summary: repairSummary, kind: repairKind, previewable },
490
+ ...(data ? { data } : {})
491
+ };
492
+ }
493
+ function compareDiagnostics(left, right) {
494
+ return (left.range?.start.offset ?? -1) - (right.range?.start.offset ?? -1)
495
+ || left.code.localeCompare(right.code);
496
+ }
497
+ function headingLevel(node) {
498
+ const match = HEADING_PATTERN.exec(node.name);
499
+ if (!match?.[1])
500
+ return null;
501
+ const level = Number(match[1]);
502
+ return level >= 1 && level <= 6 ? level : null;
503
+ }
504
+ function headingText(node, source) {
505
+ const marks = descendants(node, child => child.name === 'HeaderMark')
506
+ .map(mark => ({ from: mark.from, to: mark.to }));
507
+ return markdownToText(removeAbsoluteRanges(source, node.from, node.to, marks));
508
+ }
509
+ function markdownToText(markdown) {
510
+ return markdown
511
+ .replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, ' ')
512
+ .replace(/<[^>]*>/g, ' ')
513
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
514
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
515
+ .replace(/\[([^\]]+)\]\[[^\]]*\]/g, '$1')
516
+ .replace(/^\s{0,3}(?:#{1,6}\s+|[-+*]\s+|\d+[.)]\s+)/gm, '')
517
+ .replace(/^\s*\[[ xX]\]\s*/gm, '')
518
+ .replace(/[`*_~]/g, '')
519
+ .replace(/\\([\\`*{}[\]()#+.!_>~-])/g, '$1')
520
+ .replace(/\s+/gu, ' ')
521
+ .trim();
522
+ }
523
+ function normalizeHeading(value) {
524
+ return value.trim().replace(/\s+/gu, ' ').toLowerCase();
525
+ }
526
+ function normalizeComparisonText(value) {
527
+ return value.trim().replace(/\s+/gu, ' ').toLowerCase();
528
+ }
529
+ function normalizeLinkLabel(value) {
530
+ return value.replace(/^\[|\]$/g, '').trim().replace(/\s+/gu, ' ').toLowerCase();
531
+ }
532
+ function consumeLineEnding(source, offset) {
533
+ if (source.slice(offset, offset + 2) === '\r\n')
534
+ return offset + 2;
535
+ if (source[offset] === '\r' || source[offset] === '\n')
536
+ return offset + 1;
537
+ return offset;
538
+ }
539
+ function semanticNodeEnd(node, source) {
540
+ return node.to > node.from && source[node.to - 1] === '\r' && source[node.to] === '\n'
541
+ ? node.to - 1
542
+ : node.to;
543
+ }
544
+ function children(node) {
545
+ const result = [];
546
+ for (let child = node.firstChild; child; child = child.nextSibling) {
547
+ result.push(child);
548
+ }
549
+ return result;
550
+ }
551
+ function descendants(node, predicate) {
552
+ return children(node).flatMap(child => [
553
+ ...(predicate(child) ? [child] : []),
554
+ ...descendants(child, predicate)
555
+ ]);
556
+ }
557
+ function descendantsIncluding(node, predicate) {
558
+ return [...(predicate(node) ? [node] : []), ...descendants(node, predicate)];
559
+ }
560
+ function nearestParent(node, predicate) {
561
+ let parent = node.parent;
562
+ while (parent) {
563
+ if (predicate(parent))
564
+ return parent;
565
+ parent = parent.parent;
566
+ }
567
+ return null;
568
+ }
569
+ function ancestorCount(node, predicate) {
570
+ let count = 0;
571
+ let parent = node.parent;
572
+ while (parent) {
573
+ if (predicate(parent))
574
+ count += 1;
575
+ parent = parent.parent;
576
+ }
577
+ return count;
578
+ }
579
+ function findTaskMarker(item) {
580
+ for (const child of children(item)) {
581
+ if (child.name === 'BulletList' || child.name === 'OrderedList')
582
+ continue;
583
+ const marker = descendantsIncluding(child, candidate => candidate.name === 'TaskMarker')[0];
584
+ if (marker)
585
+ return marker;
586
+ }
587
+ return undefined;
588
+ }
589
+ function nodeIdentity(node) {
590
+ return `${node.from}:${node.to}`;
591
+ }
592
+ function linkLabelRange(node) {
593
+ const marks = descendants(node, child => child.name === 'LinkMark').sort((a, b) => a.from - b.from);
594
+ const opening = marks[0];
595
+ const closing = marks.find(mark => mark.from > (opening?.to ?? node.from));
596
+ return opening && closing ? { from: opening.to, to: closing.from } : null;
597
+ }
598
+ function sectionIndexAt(offset, sections) {
599
+ const section = sections.find(candidate => (offset >= candidate.range.start.offset && offset < candidate.range.end.offset));
600
+ return section?.index ?? null;
601
+ }
602
+ function mentionEntityType(id) {
603
+ if (id.startsWith('TASK-'))
604
+ return 'task';
605
+ if (id.startsWith('EPIC-'))
606
+ return 'epic';
607
+ if (id.startsWith('MILESTONE-'))
608
+ return 'milestone';
609
+ return 'decision';
610
+ }
611
+ function hasEntityBoundaries(source, from, to) {
612
+ const boundary = /[A-Za-z0-9_-]/;
613
+ return !boundary.test(source[from - 1] ?? '') && !boundary.test(source[to] ?? '');
614
+ }
615
+ function isInsideRanges(offset, ranges) {
616
+ return ranges.some(range => offset >= range.from && offset < range.to);
617
+ }
618
+ function mergeRanges(ranges) {
619
+ const sorted = ranges.filter(range => range.to > range.from).sort((a, b) => a.from - b.from);
620
+ const merged = [];
621
+ for (const range of sorted) {
622
+ const previous = merged[merged.length - 1];
623
+ if (previous && range.from <= previous.to) {
624
+ previous.to = Math.max(previous.to, range.to);
625
+ }
626
+ else {
627
+ merged.push({ ...range });
628
+ }
629
+ }
630
+ return merged;
631
+ }
632
+ function removeAbsoluteRanges(source, from, to, ranges) {
633
+ let result = '';
634
+ let cursor = from;
635
+ for (const range of ranges.sort((a, b) => a.from - b.from)) {
636
+ result += source.slice(cursor, Math.max(cursor, range.from));
637
+ cursor = Math.max(cursor, range.to);
638
+ }
639
+ return result + source.slice(cursor, to);
640
+ }
641
+ //# sourceMappingURL=semantic.js.map