agentera 3.0.0-dev.72 → 3.0.0-dev.74

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 (92) hide show
  1. package/README.md +111 -0
  2. package/bundle/.agentera-build-source.json +8 -0
  3. package/bundle/CHANGELOG.md +17 -17
  4. package/bundle/extract-corpus-parity.json +1 -1
  5. package/bundle/references/adapters/package-publication.json +49 -53
  6. package/bundle/references/adapters/package-registry.yaml +5 -1
  7. package/bundle/references/adapters/package-surface-characterization.md +6 -4
  8. package/bundle/references/analysis/evidence-tier-authority.yaml +45 -4
  9. package/bundle/references/analysis/personal-glossary-evaluation-authority.yaml +175 -0
  10. package/bundle/references/analysis/personal-glossary-evaluation-corpus.yaml +291 -0
  11. package/bundle/references/analysis/personal-glossary-holdout.yaml +192 -0
  12. package/bundle/references/artifacts/glossary-entry-contract.yaml +1533 -35
  13. package/bundle/references/cli/update-channels.yaml +2 -2
  14. package/bundle/references/cli/vocabulary.md +1 -1
  15. package/bundle/references/meta/retained-reference-authority.yaml +15 -0
  16. package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +11 -7
  17. package/bundle/skills/agentera/capabilities/profile/schemas/exit.yaml +6 -3
  18. package/bundle/skills/agentera/capabilities/profile/schemas/validation.yaml +13 -7
  19. package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +5 -0
  20. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +7 -0
  21. package/dist/.agentera-build-source.json +8 -0
  22. package/dist/analytics/extractCorpus/copilotSessions.js +13 -4
  23. package/dist/analytics/extractCorpus/core.js +223 -3
  24. package/dist/analytics/extractCorpus/corpus.js +18 -1
  25. package/dist/analytics/extractCorpus/cursorSessions.js +22 -4
  26. package/dist/analytics/extractCorpus/evidenceTiers.js +123 -17
  27. package/dist/analytics/extractCorpus/filesystemSources.js +4 -0
  28. package/dist/analytics/extractCorpus/index.js +2 -2
  29. package/dist/analytics/extractCorpus/jsonlSessions.js +52 -3
  30. package/dist/analytics/extractCorpus/sqliteSessions.js +12 -3
  31. package/dist/analytics/personalGlossaryAdmission.js +26 -46
  32. package/dist/analytics/personalGlossaryCandidateProjection.js +692 -0
  33. package/dist/analytics/personalGlossaryCandidateProjectionExcerpts.js +144 -0
  34. package/dist/analytics/personalGlossaryCandidateReadView.js +23 -0
  35. package/dist/analytics/personalGlossaryCurrentGeneration.js +25 -0
  36. package/dist/analytics/personalGlossaryDecision.js +203 -0
  37. package/dist/analytics/personalGlossaryExplicit.js +788 -0
  38. package/dist/analytics/personalGlossaryExplicitMining.js +285 -0
  39. package/dist/analytics/personalGlossaryExplicitScope.js +41 -0
  40. package/dist/analytics/personalGlossaryExplicitSegments.js +418 -0
  41. package/dist/analytics/personalGlossaryExplicitTypes.js +26 -0
  42. package/dist/analytics/personalGlossaryLexicalClassifier.js +175 -0
  43. package/dist/analytics/personalGlossaryProfile.js +13 -3
  44. package/dist/analytics/personalGlossaryRecurrence.js +618 -0
  45. package/dist/analytics/personalGlossaryReviewRecordStorage.js +428 -0
  46. package/dist/analytics/personalGlossaryReviewRecords.js +332 -0
  47. package/dist/capabilities/index.js +5 -1
  48. package/dist/capabilities/profile/instructions.js +50 -15
  49. package/dist/cli/commands/personalGlossaryCandidateReads.js +744 -0
  50. package/dist/cli/commands/personalGlossaryDecision.js +349 -0
  51. package/dist/cli/commands/personalGlossaryPublish.js +484 -0
  52. package/dist/cli/commands/personalGlossaryReviewRecordReads.js +209 -0
  53. package/dist/cli/commands/personalGlossaryReviewRecords.js +623 -0
  54. package/dist/cli/commands/prime/briefOrientation.js +118 -19
  55. package/dist/cli/commands/report.js +18 -2
  56. package/dist/cli/commands/schema.js +179 -0
  57. package/dist/cli/commands/verify.js +51 -9
  58. package/dist/cli/dispatch/lifecycle.js +51 -6
  59. package/dist/cli/help.js +48 -2
  60. package/dist/cli/migrationRequired.js +4 -1
  61. package/dist/core/atomicWriter.js +4 -1
  62. package/dist/core/developmentInvocation.js +4 -1
  63. package/dist/core/yaml.js +53 -1
  64. package/dist/eval/glossaryEvaluation.js +751 -0
  65. package/dist/eval/glossaryEvaluationProcess.js +29 -0
  66. package/dist/eval/glossaryEvaluationRunner.js +571 -0
  67. package/dist/eval/glossaryEvaluationSuccessReport.js +297 -0
  68. package/dist/registries/activationTuples.js +31 -3
  69. package/dist/registries/evidenceTierContract.js +4 -0
  70. package/dist/registries/explicitSegmentGrammarContract.js +619 -0
  71. package/dist/registries/glossaryCandidateContractPaths.js +6 -0
  72. package/dist/registries/glossaryCandidateContracts.js +820 -0
  73. package/dist/registries/glossaryCandidateDecisionAuthority.js +119 -0
  74. package/dist/registries/glossaryCandidateDecisionContract.js +62 -0
  75. package/dist/registries/glossaryCandidateProjectionAuthority.js +197 -0
  76. package/dist/registries/glossaryCandidateProjectionContract.js +104 -0
  77. package/dist/registries/glossaryEntryContract.js +105 -103
  78. package/dist/registries/glossaryEntryTemporal.js +12 -0
  79. package/dist/registries/glossaryMiningAuthority.js +764 -0
  80. package/dist/registries/glossaryProfileFullContract.js +24 -0
  81. package/dist/registries/glossaryReviewRecordsAuthority.js +426 -0
  82. package/dist/registries/glossaryReviewRecordsContract.js +129 -0
  83. package/dist/registries/glossaryTermIdentity.js +122 -0
  84. package/dist/registries/packagePublication.js +3 -3
  85. package/dist/registries/personalGlossaryContracts.js +1 -0
  86. package/dist/release/releaseMetadata.js +14 -7
  87. package/dist/upgrade/npxPlatformStatus.js +2 -1
  88. package/dist/upgrade/projectIntegration.js +7 -1
  89. package/dist/validate/activationArtifactEvidence.js +18 -4
  90. package/dist/validate/activationEvidenceManifest.js +101 -22
  91. package/package.json +2 -4
  92. package/dist/cli/commands/personalGlossary.js +0 -176
@@ -0,0 +1,788 @@
1
+ import { loadYamlMappingFile } from "../core/yaml.js";
2
+ import { glossaryEntryAuthorityPath } from "../registries/glossaryEntryContract.js";
3
+ import { loadExplicitSegmentGrammarContract } from "../registries/explicitSegmentGrammarContract.js";
4
+ import { compareGlossaryUnicodeStrings, stableGlossaryTermIdentity, } from "../registries/glossaryTermIdentity.js";
5
+ import { EXPLICIT_GLOSSARY_REASONS, } from "./personalGlossaryExplicitTypes.js";
6
+ import { segmentAndBindExplicitCues } from "./personalGlossaryExplicitSegments.js";
7
+ import { adjacentScopeTexts, contextText } from "./personalGlossaryExplicitScope.js";
8
+ export * from "./personalGlossaryExplicitTypes.js";
9
+ const OPEN_TO_CLOSE = new Map([
10
+ ['"', '"'],
11
+ ["'", "'"],
12
+ ["`", "`"],
13
+ ["“", "”"],
14
+ ["‘", "’"],
15
+ ["«", "»"],
16
+ ["「", "」"],
17
+ ["『", "』"],
18
+ ]);
19
+ const CLOSE_TO_OPEN = new Map([...OPEN_TO_CLOSE.entries()].map(([open, close]) => [close, open]));
20
+ const SENTENCE_TERMINATORS = new Set([".", "!", "?"]);
21
+ const TERM_EDGE_RE = /[\s,;:()[\]{}]/u;
22
+ const LETTER_OR_NUMBER_RE = /[\p{L}\p{N}]/u;
23
+ const ACRONYM_RE = /^[\p{Lu}\p{N}][\p{Lu}\p{N}._-]{1,31}$/u;
24
+ const DEFINITION_LIST_MARKER_RE = /^(?:definition|term)$/iu;
25
+ const DEFINITION_LIST_PREFIX_RE = /^\s*(?:[-*+]\s+)?/u;
26
+ const EXAMPLE_RE = /\b(?:for\s+example|e\.g\.?|example|such\s+as)\b/iu;
27
+ const HYPOTHETICAL_RE = /\b(?:if|when|unless|whenever|suppose|assuming|hypothetically|imagine|would|could|might|may)\b/iu;
28
+ const QUESTION_RE = /\?|^\s*(?:does|do|did|can|could|would|should|what|why|how)\b/iu;
29
+ const INDIRECT_QUESTION_RE = /\b(?:i\s+(?:wonder|ask|want\s+to\s+know)|tell\s+me|whether|what|why|how)\b/iu;
30
+ const FUTURE_RE = /\b(?:will|shall|going\s+to|plan\s+to|intend\s+to|in\s+the\s+future|from\s+now\s+on|later)\b/iu;
31
+ const NEGATION_RE = /\b(?:does\s+not|doesn['’]t|do\s+not|don['’]t|did\s+not|never|isn['’]t|is\s+not)\b/iu;
32
+ const SARCASM_RE = /(?:\b(?:sarcasm|sarcastically|just\s+kidding|jk)\b|\/s(?:\b|$))/iu;
33
+ const ATTRIBUTION_RE = /(?:\b(?:according\s+to|quoted\s+from|as\s+stated\s+by|the\s+(?:agent|assistant|docs?|documentation)\s+(?:says?|defines?)|[A-Z][\p{L}\p{M}'-]{1,31}\s+(?:says?|defines?|said|wrote|called))\b|^\s*(?!(?:correction|definition|example|note|term|meaning)\b)[\p{L}][\p{L}\p{M}'-]{1,31}\s*:\s*["'“‘`])/iu;
34
+ const RETRACTION_RE = /\b(?:i\s+retract|retract(?:ed|ion)?|withdraw(?:n|al)?|supersed(?:e|ed|es)|no\s+longer|disregard|take\s+back|replace(?:d|ment)?)\b/iu;
35
+ const CORRECTION_RE = /\b(?:actually|correction|not\s+quite|instead|rather)\b/iu;
36
+ const PROJECT_SCOPE_RE = /\b(?:in|within|for)\s+(?:this|the|our)\s+(?:repo(?:sitory)?|project|codebase)\b|\b(?:repo(?:sitory)?|project|codebase)[ -]only\b|\b(?:project|repo(?:sitory)?|codebase)\s+(?:term|meaning|definition)\b/iu;
37
+ const DIRECT_SCOPE_REFERENCE_RE = /\b(?:this|that|the\s+(?:following|above|below)|following|above|below)\s+(?:term|meaning|definition|usage|entry)\b/iu;
38
+ const PERSONAL_SCOPE_RE = /\b(?:i|we)\s+(?:use|mean|call|define)\b|\bmy\s+(?:term|meaning|definition)\b|\bfor\s+me\b|\bpersonally\b/iu;
39
+ export function dataObject(record) {
40
+ const data = record.data;
41
+ return data !== null && typeof data === "object" && !Array.isArray(data)
42
+ ? data
43
+ : {};
44
+ }
45
+ export function compareText(left, right) {
46
+ return compareGlossaryUnicodeStrings(left, right);
47
+ }
48
+ export function utf8Span(text, start, end) {
49
+ return {
50
+ start: Buffer.byteLength(text.slice(0, start), "utf8"),
51
+ end: Buffer.byteLength(text.slice(0, end), "utf8"),
52
+ };
53
+ }
54
+ function scalarAt(text, index) {
55
+ const value = text.codePointAt(index);
56
+ return value === undefined ? "" : String.fromCodePoint(value);
57
+ }
58
+ function isApostropheInWord(text, index) {
59
+ return (text[index] === "'" &&
60
+ LETTER_OR_NUMBER_RE.test(scalarAt(text, index - 1)) &&
61
+ LETTER_OR_NUMBER_RE.test(scalarAt(text, index + 1)));
62
+ }
63
+ function delimitedRanges(text) {
64
+ const ranges = [];
65
+ const stack = [];
66
+ for (let index = 0; index < text.length; index += 1) {
67
+ const character = text[index];
68
+ if (isApostropheInWord(text, index))
69
+ continue;
70
+ const current = stack.at(-1);
71
+ if (current) {
72
+ if (character === current.close &&
73
+ !(current.open === current.close && index === current.start)) {
74
+ stack.pop();
75
+ ranges.push({ start: current.start, end: index });
76
+ }
77
+ continue;
78
+ }
79
+ const close = OPEN_TO_CLOSE.get(character);
80
+ if (close)
81
+ stack.push({ open: character, close, start: index });
82
+ }
83
+ return ranges.sort((left, right) => left.start - right.start);
84
+ }
85
+ function insideRange(index, ranges) {
86
+ for (const range of ranges) {
87
+ if (range.start >= index)
88
+ break;
89
+ if (index < range.end)
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+ function sentenceBoundaries(text, ranges) {
95
+ const boundaries = [];
96
+ for (let index = 0; index < text.length; index += 1) {
97
+ const character = text[index];
98
+ if (!SENTENCE_TERMINATORS.has(character) || insideRange(index, ranges))
99
+ continue;
100
+ if (character === "." &&
101
+ ((/[\p{N}]/u.test(text[index - 1] ?? "") && /[\p{N}]/u.test(text[index + 1] ?? "")) ||
102
+ (text[index + 1] !== "" && !/[\s"'”’)]/u.test(text[index + 1] ?? "")))) {
103
+ continue;
104
+ }
105
+ boundaries.push(index + 1);
106
+ }
107
+ return boundaries;
108
+ }
109
+ function previousBoundary(boundaries, index) {
110
+ let result = 0;
111
+ for (const boundary of boundaries) {
112
+ if (boundary > index)
113
+ break;
114
+ result = boundary;
115
+ }
116
+ return result;
117
+ }
118
+ function nextBoundary(text, boundaries, index) {
119
+ for (const boundary of boundaries) {
120
+ if (boundary > index)
121
+ return boundary;
122
+ }
123
+ return text.length;
124
+ }
125
+ function trimWhitespace(text, start, end) {
126
+ while (start < end && /\s/u.test(text[start]))
127
+ start += 1;
128
+ while (end > start && /\s/u.test(text[end - 1]))
129
+ end -= 1;
130
+ return [start, end];
131
+ }
132
+ function trimTerm(text, start, end) {
133
+ [start, end] = trimWhitespace(text, start, end);
134
+ while (start < end && TERM_EDGE_RE.test(text[start]))
135
+ start += 1;
136
+ while (end > start && TERM_EDGE_RE.test(text[end - 1]))
137
+ end -= 1;
138
+ return trimWhitespace(text, start, end);
139
+ }
140
+ function trimMeaning(text, start, end) {
141
+ [start, end] = trimWhitespace(text, start, end);
142
+ while (end > start && (SENTENCE_TERMINATORS.has(text[end - 1]) || text[end - 1] === ";")) {
143
+ end -= 1;
144
+ }
145
+ return trimWhitespace(text, start, end);
146
+ }
147
+ function lastIndexOutsideRanges(text, character, start, end, ranges) {
148
+ for (let index = end - 1; index >= start; index -= 1) {
149
+ if (text[index] === character && !insideRange(index, ranges))
150
+ return index;
151
+ }
152
+ return -1;
153
+ }
154
+ function delimitedTermBefore(text, end, floor) {
155
+ while (end > floor && /[\s,;:]/u.test(text[end - 1]))
156
+ end -= 1;
157
+ const close = text[end - 1];
158
+ if (!close || !CLOSE_TO_OPEN.has(close))
159
+ return null;
160
+ const open = CLOSE_TO_OPEN.get(close);
161
+ const openIndex = text.lastIndexOf(open, end - 2);
162
+ if (openIndex < floor || openIndex >= end - 1)
163
+ return null;
164
+ const [start, finish] = trimWhitespace(text, openIndex + 1, end - 1);
165
+ return finish > start ? [start, finish] : [start, finish];
166
+ }
167
+ function lastWordIndex(text, word, start, end) {
168
+ const expression = new RegExp(`(?:^|[^\\p{L}\\p{N}_])${word}(?=$|[^\\p{L}\\p{N}_])`, "giu");
169
+ let result = -1;
170
+ for (const match of text.slice(start, end).matchAll(expression)) {
171
+ result = start + (match.index ?? 0) + (match[0].length - word.length);
172
+ }
173
+ return result;
174
+ }
175
+ function removeLeadingTermLabel(text, start, end) {
176
+ const value = text.slice(start, end);
177
+ const match = /^(?:the\s+)?(?:term|word|phrase|concept)\s*[:=]?\s+/iu.exec(value);
178
+ return match ? start + match[0].length : start;
179
+ }
180
+ function unquotedTermBefore(text, cueStart, segmentStart, kind, ranges) {
181
+ let start = segmentStart;
182
+ const prefixEnd = cueStart;
183
+ const prefix = text.slice(segmentStart, prefixEnd);
184
+ if (kind === "by_i_mean") {
185
+ const by = lastWordIndex(text, "by", segmentStart, prefixEnd);
186
+ if (by >= 0)
187
+ start = by + 2;
188
+ }
189
+ else if (kind === "use_for" || kind === "use_to_mean") {
190
+ const use = lastWordIndex(text, "use", segmentStart, prefixEnd);
191
+ if (use >= 0)
192
+ start = use + 3;
193
+ }
194
+ else {
195
+ const directives = [
196
+ "to clarify",
197
+ "prefer",
198
+ "please use",
199
+ "i use",
200
+ "use",
201
+ "actually",
202
+ "correction",
203
+ "not quite",
204
+ "instead",
205
+ "rather",
206
+ ];
207
+ let directiveEnd = -1;
208
+ for (const directive of directives) {
209
+ const expression = new RegExp(`(?:^|[^\\p{L}\\p{N}_])${directive}(?=$|[^\\p{L}\\p{N}_])`, "giu");
210
+ for (const match of prefix.matchAll(expression)) {
211
+ const end = segmentStart + (match.index ?? 0) + match[0].length;
212
+ if (end > directiveEnd)
213
+ directiveEnd = end;
214
+ }
215
+ }
216
+ if (directiveEnd >= 0)
217
+ start = directiveEnd;
218
+ const separator = Math.max(lastIndexOutsideRanges(text, ",", start, prefixEnd, ranges), lastIndexOutsideRanges(text, ";", start, prefixEnd, ranges), lastIndexOutsideRanges(text, ":", start, prefixEnd, ranges));
219
+ if (separator >= start)
220
+ start = separator + 1;
221
+ }
222
+ start = removeLeadingTermLabel(text, start, prefixEnd);
223
+ let result = trimTerm(text, start, cueStart);
224
+ return result;
225
+ }
226
+ function matchingClose(text, openIndex) {
227
+ const open = text[openIndex];
228
+ const close = open ? OPEN_TO_CLOSE.get(open) : undefined;
229
+ if (!close)
230
+ return -1;
231
+ let depth = 0;
232
+ for (let index = openIndex; index < text.length; index += 1) {
233
+ if (text[index] === open)
234
+ depth += 1;
235
+ else if (text[index] === close) {
236
+ depth -= 1;
237
+ if (depth === 0)
238
+ return index;
239
+ }
240
+ }
241
+ return -1;
242
+ }
243
+ function meaningSpan(text, cueEnd, sentenceEnd, ranges) {
244
+ let start = cueEnd;
245
+ while (start < sentenceEnd && /[\s:,-]/u.test(text[start]))
246
+ start += 1;
247
+ let end = sentenceEnd;
248
+ for (let index = cueEnd; index < sentenceEnd; index += 1) {
249
+ if (text[index] !== "\n" || insideRange(index, ranges))
250
+ continue;
251
+ const nextLineStart = index + 1;
252
+ const nextLineEnd = text.indexOf("\n", nextLineStart) < 0 ? sentenceEnd : text.indexOf("\n", nextLineStart);
253
+ if (looksLikeDefinitionLine(text, nextLineStart, nextLineEnd, ranges)) {
254
+ end = index;
255
+ break;
256
+ }
257
+ }
258
+ return trimMeaning(text, start, end);
259
+ }
260
+ function unwrapMeaning(text, start, end) {
261
+ [start, end] = trimMeaning(text, start, end);
262
+ const open = text[start];
263
+ if (!open || !OPEN_TO_CLOSE.has(open))
264
+ return [start, end];
265
+ const close = matchingClose(text, start);
266
+ if (close !== end - 1)
267
+ return [start, end];
268
+ return trimWhitespace(text, start + 1, close);
269
+ }
270
+ function parseWordCue(text, cueStart, cueEnd, kind, boundaries, ranges, termEndForParsing = cueStart) {
271
+ const sentenceStart = previousBoundary(boundaries, cueStart);
272
+ const sentenceBoundary = nextBoundary(text, boundaries, cueStart);
273
+ const term = delimitedTermBefore(text, termEndForParsing, sentenceStart);
274
+ const [termStart, termEnd] = term ?? unquotedTermBefore(text, termEndForParsing, sentenceStart, kind, ranges);
275
+ const [meaningStart, meaningEnd] = unwrapMeaning(text, ...meaningSpan(text, cueEnd, sentenceBoundary, ranges));
276
+ return {
277
+ kind,
278
+ termStart,
279
+ termEnd,
280
+ meaningStart,
281
+ meaningEnd,
282
+ sentenceStart,
283
+ sentenceEnd: sentenceBoundary,
284
+ };
285
+ }
286
+ function wordCues(text, boundaries, ranges) {
287
+ const patterns = [
288
+ { kind: "means", expression: /\bmeans\b/giu },
289
+ { kind: "refers_to", expression: /\brefers\s+to\b/giu },
290
+ { kind: "stands_for", expression: /\bstands\s+for\b/giu },
291
+ { kind: "use_to_mean", expression: /\bto\s+mean\b/giu },
292
+ { kind: "by_i_mean", expression: /\bi\s+mean\b/giu },
293
+ ];
294
+ const cues = [];
295
+ for (const pattern of patterns) {
296
+ for (const match of text.matchAll(pattern.expression)) {
297
+ const start = match.index ?? 0;
298
+ if (insideRange(start, ranges))
299
+ continue;
300
+ if (pattern.kind === "by_i_mean") {
301
+ const sentenceStart = previousBoundary(boundaries, start);
302
+ if (lastWordIndex(text, "by", sentenceStart, start) < 0)
303
+ continue;
304
+ }
305
+ cues.push(parseWordCue(text, start, start + match[0].length, pattern.kind, boundaries, ranges));
306
+ }
307
+ }
308
+ for (const match of text.matchAll(/\bfor\b/giu)) {
309
+ const start = match.index ?? 0;
310
+ if (insideRange(start, ranges))
311
+ continue;
312
+ const sentenceStart = previousBoundary(boundaries, start);
313
+ if (lastWordIndex(text, "use", sentenceStart, start) < 0)
314
+ continue;
315
+ cues.push(parseWordCue(text, start, start + match[0].length, "use_for", boundaries, ranges));
316
+ }
317
+ for (const match of text.matchAll(/\bmean\b/giu)) {
318
+ const start = match.index ?? 0;
319
+ if (insideRange(start, ranges))
320
+ continue;
321
+ const sentenceStart = previousBoundary(boundaries, start);
322
+ const sentenceEnd = nextBoundary(text, boundaries, start);
323
+ const prefix = text.slice(sentenceStart, start);
324
+ const sentence = text.slice(sentenceStart, sentenceEnd);
325
+ if (lastWordIndex(text, "by", sentenceStart, start) >= 0 ||
326
+ (!NEGATION_RE.test(prefix) &&
327
+ !QUESTION_RE.test(sentence) &&
328
+ !/^\s*(?:does|do|did|can|could|would|should|when|may)\b/iu.test(prefix))) {
329
+ continue;
330
+ }
331
+ const negation = /(?:does\s+not|doesn['’]t|do\s+not|don['’]t|did\s+not|never)\s*$/iu.exec(prefix);
332
+ cues.push(parseWordCue(text, start, start + match[0].length, "special_mean", boundaries, ranges, negation ? sentenceStart + negation.index : start));
333
+ }
334
+ return cues;
335
+ }
336
+ function lineRanges(text) {
337
+ const lines = [];
338
+ let start = 0;
339
+ for (let index = 0; index <= text.length; index += 1) {
340
+ if (index !== text.length && text[index] !== "\n")
341
+ continue;
342
+ lines.push({ start, end: index });
343
+ start = index + 1;
344
+ }
345
+ return lines;
346
+ }
347
+ function commentStart(text, line, ranges) {
348
+ const firstNonWhitespace = text.slice(line.start, line.end).search(/\S/u);
349
+ if (firstNonWhitespace >= 0) {
350
+ const index = line.start + firstNonWhitespace;
351
+ if (text[index] === "#" && !insideRange(index, ranges))
352
+ return index;
353
+ }
354
+ for (let index = line.start; index < line.end; index += 1) {
355
+ if (text[index] !== "#" || insideRange(index, ranges))
356
+ continue;
357
+ if (index === line.start || /\s/u.test(text[index - 1] ?? ""))
358
+ return index;
359
+ }
360
+ return -1;
361
+ }
362
+ function segmentFloorBefore(text, end, floor, ranges) {
363
+ for (let index = end - 1; index >= floor; index -= 1) {
364
+ if (!insideRange(index, ranges) && /[;,.!?]/u.test(text[index]))
365
+ return index + 1;
366
+ }
367
+ return floor;
368
+ }
369
+ function colonOutsideRanges(text, start, end, ranges) {
370
+ for (let index = start; index < end; index += 1) {
371
+ if (text[index] === ":" && !insideRange(index, ranges))
372
+ return index;
373
+ }
374
+ return -1;
375
+ }
376
+ function acronymInitials(value) {
377
+ const initials = [];
378
+ for (const word of value.trim().split(/\s+/u).filter(Boolean)) {
379
+ const letters = [...word].filter((character) => LETTER_OR_NUMBER_RE.test(character));
380
+ if (letters.length === 0)
381
+ continue;
382
+ const capitals = letters.filter((character) => character.toUpperCase() === character && character.toLowerCase() !== character);
383
+ if (capitals.length > 1)
384
+ initials.push(...capitals);
385
+ else
386
+ initials.push(letters[0]);
387
+ }
388
+ return initials.join("");
389
+ }
390
+ function acronymExpansionMatches(term, meaning) {
391
+ if (!ACRONYM_RE.test(term) || Buffer.byteLength(term, "utf8") > 32)
392
+ return false;
393
+ const words = meaning.trim().split(/\s+/u).filter(Boolean);
394
+ return (words.length > 0 &&
395
+ words.length <= 32 &&
396
+ acronymInitials(meaning).toLowerCase() === term.toLowerCase());
397
+ }
398
+ function safeDefinitionListTerm(text, lineStart, termFloor, colon, termStart, termEnd, meaningStart, meaningEnd, marked) {
399
+ const term = text.slice(termStart, termEnd);
400
+ const meaning = text.slice(meaningStart, meaningEnd);
401
+ const delimited = delimitedTermBefore(text, colon, termFloor);
402
+ const exactlyDelimited = delimited?.[0] === termStart &&
403
+ delimited[1] === termEnd &&
404
+ text.slice(termFloor, Math.max(termFloor, termStart - 1)).trim().length === 0 &&
405
+ text.slice(termEnd + 1, colon).trim().length === 0;
406
+ if (text.slice(lineStart, termStart).includes("#"))
407
+ return false;
408
+ if (/[/\\=@#[\]{}]/u.test(term) ||
409
+ /:\/\//u.test(text.slice(lineStart, meaningEnd)) ||
410
+ /^(?:https?|ftp):\/\//iu.test(meaning.trim()))
411
+ return false;
412
+ if (/^\d{1,2}:\d{2}(?::\d{2})?$/u.test(`${term}:${meaning}`.split(/\s+/u)[0]))
413
+ return false;
414
+ if (marked)
415
+ return LETTER_OR_NUMBER_RE.test(term);
416
+ if (exactlyDelimited)
417
+ return true;
418
+ if (acronymExpansionMatches(term, meaning))
419
+ return true;
420
+ return false;
421
+ }
422
+ function looksLikeDefinitionLine(text, start, end, ranges) {
423
+ const colon = colonOutsideRanges(text, start, end, ranges);
424
+ if (colon < 0)
425
+ return false;
426
+ const [termStart, termEnd] = trimTerm(text, start, colon);
427
+ const term = text
428
+ .slice(termStart, termEnd)
429
+ .replace(/^[-*+]\s+/u, "")
430
+ .trim();
431
+ return term.length > 0;
432
+ }
433
+ function listCues(text, ranges, boundaries) {
434
+ const cues = [];
435
+ const lines = lineRanges(text);
436
+ for (const line of lines) {
437
+ const comment = commentStart(text, line, ranges);
438
+ const scanEnd = comment < 0 ? line.end : comment;
439
+ const prefixMatch = DEFINITION_LIST_PREFIX_RE.exec(text.slice(line.start, scanEnd));
440
+ const contentStart = line.start + (prefixMatch?.[0].length ?? 0);
441
+ const colons = [];
442
+ for (let index = contentStart; index < scanEnd; index += 1) {
443
+ if (text[index] === ":" && !insideRange(index, ranges))
444
+ colons.push(index);
445
+ }
446
+ if (colons.length === 0)
447
+ continue;
448
+ for (const firstColon of colons) {
449
+ const segmentFloor = segmentFloorBefore(text, firstColon, contentStart, ranges);
450
+ const [firstStart, firstEnd] = trimTerm(text, segmentFloor, firstColon);
451
+ const firstValue = text.slice(firstStart, firstEnd);
452
+ if (DEFINITION_LIST_MARKER_RE.test(firstValue)) {
453
+ const termColon = colons.find((candidate) => candidate > firstColon);
454
+ if (termColon === undefined)
455
+ continue;
456
+ const [termStart, termEnd] = trimTerm(text, firstColon + 1, termColon);
457
+ const [meaningStart, meaningEnd] = trimMeaning(text, termColon + 1, scanEnd);
458
+ const safe = safeDefinitionListTerm(text, line.start, firstColon + 1, termColon, termStart, termEnd, meaningStart, meaningEnd, true);
459
+ cues.push({
460
+ kind: "definition_list",
461
+ termStart,
462
+ termEnd,
463
+ meaningStart: termColon + 1,
464
+ meaningEnd: scanEnd,
465
+ sentenceStart: previousBoundary(boundaries, termStart),
466
+ sentenceEnd: nextBoundary(text, boundaries, termColon),
467
+ ...(safe ? {} : { rejectionReason: EXPLICIT_GLOSSARY_REASONS.unsafeSyntax }),
468
+ });
469
+ continue;
470
+ }
471
+ const delimited = delimitedTermBefore(text, firstColon, segmentFloor);
472
+ const [termStart, termEnd] = delimited ?? trimTerm(text, segmentFloor, firstColon);
473
+ const term = text.slice(termStart, termEnd);
474
+ if (!delimited && !ACRONYM_RE.test(term))
475
+ continue;
476
+ const [meaningStart, meaningEnd] = trimMeaning(text, firstColon + 1, scanEnd);
477
+ const safe = safeDefinitionListTerm(text, line.start, segmentFloor, firstColon, termStart, termEnd, meaningStart, meaningEnd, false);
478
+ cues.push({
479
+ kind: "definition_list",
480
+ termStart,
481
+ termEnd,
482
+ meaningStart: firstColon + 1,
483
+ meaningEnd: scanEnd,
484
+ sentenceStart: previousBoundary(boundaries, termStart),
485
+ sentenceEnd: nextBoundary(text, boundaries, firstColon),
486
+ ...(safe ? {} : { rejectionReason: EXPLICIT_GLOSSARY_REASONS.unsafeSyntax }),
487
+ });
488
+ }
489
+ }
490
+ return cues;
491
+ }
492
+ function phraseBefore(text, start, ranges) {
493
+ let floor = 0;
494
+ for (let index = start - 1; index >= 0; index -= 1) {
495
+ if (text[index] === "\n" ||
496
+ text[index] === "." ||
497
+ text[index] === "!" ||
498
+ text[index] === "?" ||
499
+ text[index] === ",") {
500
+ if (!insideRange(index, ranges)) {
501
+ floor = index + 1;
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ return trimTerm(text, floor, start);
507
+ }
508
+ function acronymCues(text, ranges, boundaries) {
509
+ const cues = [];
510
+ for (let index = 0; index < text.length; index += 1) {
511
+ if (text[index] !== "(" || insideRange(index, ranges))
512
+ continue;
513
+ const close = text.indexOf(")", index + 1);
514
+ if (close < 0 || insideRange(close, ranges))
515
+ continue;
516
+ const [innerStart, innerEnd] = trimWhitespace(text, index + 1, close);
517
+ const inner = text.slice(innerStart, innerEnd);
518
+ const [leftStart, leftEnd] = phraseBefore(text, index, ranges);
519
+ const left = text.slice(leftStart, leftEnd);
520
+ const sentenceStart = previousBoundary(boundaries, index);
521
+ const sentenceEnd = nextBoundary(text, boundaries, index);
522
+ if (ACRONYM_RE.test(left) && acronymExpansionMatches(left, inner)) {
523
+ cues.push({
524
+ kind: "acronym_parenthetical",
525
+ termStart: leftStart,
526
+ termEnd: leftEnd,
527
+ meaningStart: innerStart,
528
+ meaningEnd: innerEnd,
529
+ sentenceStart,
530
+ sentenceEnd,
531
+ });
532
+ }
533
+ else if (ACRONYM_RE.test(inner) && acronymExpansionMatches(inner, left)) {
534
+ cues.push({
535
+ kind: "acronym_parenthetical",
536
+ termStart: innerStart,
537
+ termEnd: innerEnd,
538
+ meaningStart: leftStart,
539
+ meaningEnd: leftEnd,
540
+ sentenceStart,
541
+ sentenceEnd,
542
+ });
543
+ }
544
+ index = close;
545
+ }
546
+ return cues;
547
+ }
548
+ function rawCueKey(cue) {
549
+ return [cue.termStart, cue.termEnd, cue.meaningStart, cue.meaningEnd, cue.kind].join(":");
550
+ }
551
+ function rawCueOrder(left, right, text) {
552
+ return (left.termStart - right.termStart ||
553
+ left.meaningStart - right.meaningStart ||
554
+ compareText(text.slice(left.termStart, left.termEnd), text.slice(right.termStart, right.termEnd)) ||
555
+ compareText(text.slice(left.meaningStart, left.meaningEnd), text.slice(right.meaningStart, right.meaningEnd)) ||
556
+ compareText(left.kind, right.kind));
557
+ }
558
+ export function rawCues(text) {
559
+ const ranges = delimitedRanges(text);
560
+ const boundaries = sentenceBoundaries(text, ranges);
561
+ const grammar = loadExplicitSegmentGrammarContract();
562
+ const initial = [
563
+ ...wordCues(text, boundaries, ranges),
564
+ ...listCues(text, ranges, boundaries),
565
+ ...acronymCues(text, ranges, boundaries),
566
+ ];
567
+ const unique = new Map();
568
+ for (const cue of initial)
569
+ unique.set(rawCueKey(cue), cue);
570
+ return segmentAndBindExplicitCues(text, [...unique.values()], ranges, grammar, {
571
+ lineRanges,
572
+ commentStart,
573
+ colonOutsideRanges,
574
+ sentenceBoundaries,
575
+ rawCueOrder,
576
+ trimMeaning,
577
+ unwrapMeaning,
578
+ closeToOpen: CLOSE_TO_OPEN,
579
+ });
580
+ }
581
+ function cueValue(text, cue) {
582
+ return {
583
+ term: text.slice(cue.termStart, cue.termEnd),
584
+ meaning: text.slice(cue.meaningStart, cue.meaningEnd),
585
+ };
586
+ }
587
+ function termOccurs(text, term) {
588
+ const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
589
+ if (!escaped)
590
+ return false;
591
+ return new RegExp(`(?<![\\p{ID_Continue}$\\u200C\\u200D])${escaped}(?![\\p{ID_Continue}$\\u200C\\u200D])`, "iu").test(text);
592
+ }
593
+ function scopeReason(text, cue, record) {
594
+ const dataScope = String(dataObject(record ?? {}).scope ?? "").toLowerCase();
595
+ const sentences = adjacentScopeTexts(text, cue);
596
+ const term = text.slice(cue.termStart, cue.termEnd).trim();
597
+ const project = dataScope === "project" ||
598
+ dataScope === "project_only" ||
599
+ sentences.some((sentence, index) => PROJECT_SCOPE_RE.test(sentence) &&
600
+ (index === 1 ||
601
+ termOccurs(sentence, term) ||
602
+ DIRECT_SCOPE_REFERENCE_RE.test(sentence) ||
603
+ /:\s*$/u.test(sentence)));
604
+ const personal = sentences.some((sentence) => PERSONAL_SCOPE_RE.test(sentence) && termOccurs(sentence, term));
605
+ if (project && personal)
606
+ return EXPLICIT_GLOSSARY_REASONS.uncertainScope;
607
+ if (project)
608
+ return EXPLICIT_GLOSSARY_REASONS.projectOnlyScope;
609
+ return null;
610
+ }
611
+ function pureCueReason(text, cue, record) {
612
+ const value = cueValue(text, cue);
613
+ if (!value.term.trim())
614
+ return EXPLICIT_GLOSSARY_REASONS.emptyTerm;
615
+ if (!value.meaning.trim())
616
+ return EXPLICIT_GLOSSARY_REASONS.emptyMeaning;
617
+ if ((value.term.startsWith('"') ||
618
+ value.term.startsWith("'") ||
619
+ value.term.startsWith("`") ||
620
+ value.term.startsWith("“") ||
621
+ value.term.startsWith("‘")) &&
622
+ !value.term.endsWith('"') &&
623
+ !value.term.endsWith("'") &&
624
+ !value.term.endsWith("`") &&
625
+ !value.term.endsWith("”") &&
626
+ !value.term.endsWith("’")) {
627
+ return EXPLICIT_GLOSSARY_REASONS.malformedSpan;
628
+ }
629
+ const sentence = contextText(text, cue);
630
+ const beforeCue = text.slice(cue.sentenceStart, cue.termStart);
631
+ if (EXAMPLE_RE.test(sentence))
632
+ return EXPLICIT_GLOSSARY_REASONS.exampleContext;
633
+ if (NEGATION_RE.test(sentence))
634
+ return EXPLICIT_GLOSSARY_REASONS.negatedDefinition;
635
+ if (HYPOTHETICAL_RE.test(sentence))
636
+ return EXPLICIT_GLOSSARY_REASONS.hypotheticalDefinition;
637
+ if (QUESTION_RE.test(sentence))
638
+ return EXPLICIT_GLOSSARY_REASONS.questionDefinition;
639
+ if (INDIRECT_QUESTION_RE.test(beforeCue))
640
+ return EXPLICIT_GLOSSARY_REASONS.indirectQuestion;
641
+ if (FUTURE_RE.test(beforeCue))
642
+ return EXPLICIT_GLOSSARY_REASONS.futureDefinition;
643
+ if (SARCASM_RE.test(sentence))
644
+ return EXPLICIT_GLOSSARY_REASONS.sarcasmMarker;
645
+ if (ATTRIBUTION_RE.test(sentence))
646
+ return EXPLICIT_GLOSSARY_REASONS.attributedQuotation;
647
+ if (RETRACTION_RE.test(beforeCue) && !CORRECTION_RE.test(beforeCue)) {
648
+ return EXPLICIT_GLOSSARY_REASONS.retractedDefinition;
649
+ }
650
+ const scope = scopeReason(text, cue, record);
651
+ if (scope)
652
+ return scope;
653
+ return null;
654
+ }
655
+ export function candidateBounds() {
656
+ const authority = loadYamlMappingFile(glossaryEntryAuthorityPath());
657
+ const bounds = authority.candidate_contracts?.bounds;
658
+ const value = (field) => {
659
+ const number = Number(bounds?.[field]);
660
+ if (!Number.isInteger(number) || number <= 0) {
661
+ throw new TypeError(`candidate contract bound ${field} is invalid`);
662
+ }
663
+ return number;
664
+ };
665
+ return {
666
+ term: value("term_max_utf8_bytes"),
667
+ meaning: value("meaning_max_utf8_bytes"),
668
+ binding: value("binding_max_utf8_bytes"),
669
+ };
670
+ }
671
+ export function validCue(text, cue, bounds, record) {
672
+ if (cue.rejectionReason)
673
+ return { cue: null, reason: cue.rejectionReason };
674
+ const reason = pureCueReason(text, cue, record);
675
+ if (reason)
676
+ return { cue: null, reason };
677
+ const [termStart, termEnd] = trimTerm(text, cue.termStart, cue.termEnd);
678
+ const [meaningStart, meaningEnd] = trimMeaning(text, cue.meaningStart, cue.meaningEnd);
679
+ const term = text.slice(termStart, termEnd);
680
+ const meaning = text.slice(meaningStart, meaningEnd);
681
+ if (!term)
682
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.emptyTerm };
683
+ if (!meaning)
684
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.emptyMeaning };
685
+ if (!LETTER_OR_NUMBER_RE.test(term))
686
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.malformedSpan };
687
+ if (!LETTER_OR_NUMBER_RE.test(meaning))
688
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.malformedSpan };
689
+ if (cue.kind === "stands_for" && !acronymExpansionMatches(term, meaning)) {
690
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.unsafeSyntax };
691
+ }
692
+ if (Buffer.byteLength(term, "utf8") > bounds.term) {
693
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.termBoundExceeded };
694
+ }
695
+ if (Buffer.byteLength(meaning, "utf8") > bounds.meaning) {
696
+ return { cue: null, reason: EXPLICIT_GLOSSARY_REASONS.meaningBoundExceeded };
697
+ }
698
+ return {
699
+ cue: { ...cue, termStart, termEnd, meaningStart, meaningEnd, term, meaning },
700
+ reason: null,
701
+ };
702
+ }
703
+ export function retractionInvalidatesCue(text, cue, otherCues) {
704
+ const nextSameTerm = otherCues.find((other) => other.termStart > cue.termStart &&
705
+ other.termEnd > other.termStart &&
706
+ stableGlossaryTermIdentity(text.slice(other.termStart, other.termEnd).trim()) ===
707
+ stableGlossaryTermIdentity(cue.term));
708
+ const limit = nextSameTerm
709
+ ? nextSameTerm.termStart
710
+ : Math.min(text.length, cue.sentenceEnd + 512);
711
+ const suffix = text.slice(cue.meaningEnd, limit);
712
+ const marker = RETRACTION_RE.exec(suffix);
713
+ if (marker) {
714
+ const after = suffix.slice(marker.index);
715
+ const sameSentence = marker.index < Math.max(0, cue.sentenceEnd - cue.meaningEnd);
716
+ if (termOccurs(after, cue.term) ||
717
+ /\b(?:that|the\s+(?:previous|earlier|above))\s+(?:definition|meaning|term)\b/iu.test(after) ||
718
+ (sameSentence && /\b(?:it|that)\b\s*(?:$|[.!?,;:])/iu.test(after))) {
719
+ return true;
720
+ }
721
+ }
722
+ if (!nextSameTerm)
723
+ return false;
724
+ return CORRECTION_RE.test(contextText(text, nextSameTerm));
725
+ }
726
+ /**
727
+ * Recognize explicit definitions in one source string without provenance or
728
+ * downstream admission. The returned text is copied exactly, while spans use
729
+ * UTF-8 byte offsets like the other deterministic source-span contracts.
730
+ */
731
+ export function discoverExplicitGlossaryCues(text) {
732
+ const bounds = candidateBounds();
733
+ const raws = rawCues(text);
734
+ const valid = [];
735
+ for (const raw of raws) {
736
+ const result = validCue(text, raw, bounds);
737
+ if (!result.cue)
738
+ continue;
739
+ if (retractionInvalidatesCue(text, result.cue, raws))
740
+ continue;
741
+ valid.push({ raw, cue: result.cue });
742
+ }
743
+ const grouped = new Map();
744
+ for (const item of valid) {
745
+ const identity = stableGlossaryTermIdentity(item.cue.term);
746
+ const group = grouped.get(identity) ?? [];
747
+ group.push(item);
748
+ grouped.set(identity, group);
749
+ }
750
+ const output = [];
751
+ for (const group of grouped.values()) {
752
+ if (new Set(group.map((item) => item.cue.meaning)).size > 1)
753
+ continue;
754
+ const item = [...group].sort((left, right) => rawCueOrder(left.raw, right.raw, text))[0];
755
+ const cue = item.cue;
756
+ output.push({
757
+ term: cue.term,
758
+ meaning: cue.meaning,
759
+ term_span: utf8Span(text, cue.termStart, cue.termEnd),
760
+ meaning_span: utf8Span(text, cue.meaningStart, cue.meaningEnd),
761
+ });
762
+ }
763
+ const unique = new Map();
764
+ for (const cue of output) {
765
+ unique.set(`${stableGlossaryTermIdentity(cue.term)}:${cue.meaning}:${cue.term_span.start}:${cue.meaning_span.start}`, cue);
766
+ }
767
+ return [...unique.values()].sort((left, right) => compareText(left.term, right.term) ||
768
+ compareText(left.meaning, right.meaning) ||
769
+ left.term_span.start - right.term_span.start);
770
+ }
771
+ /** Backwards-compatible narrow classifier, now backed by the deterministic cue parser. */
772
+ export function classifyExplicitGlossaryLanguage(text) {
773
+ const cues = discoverExplicitGlossaryCues(text);
774
+ if (cues.length === 0)
775
+ return null;
776
+ const identities = new Map();
777
+ for (const cue of cues) {
778
+ const identity = stableGlossaryTermIdentity(cue.term);
779
+ const meanings = identities.get(identity) ?? new Set();
780
+ meanings.add(cue.meaning);
781
+ identities.set(identity, meanings);
782
+ }
783
+ if ([...identities.values()].some((meanings) => meanings.size > 1))
784
+ return null;
785
+ const first = cues[0];
786
+ return { term: first.term, meaning: first.meaning };
787
+ }
788
+ export { mineExplicitGlossaryCandidates, mineExplicitGlossaryEvidence, minePersonalExplicitGlossaryCandidates, } from "./personalGlossaryExplicitMining.js";