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,618 @@
1
+ import { loadYamlMappingFile } from "../core/yaml.js";
2
+ import { glossaryEntryAuthorityPath, personalGlossaryAdmissionContract, } from "../registries/glossaryEntryContract.js";
3
+ import { createGlossaryEvidenceCapsule, } from "../registries/glossaryCandidateContracts.js";
4
+ import { PERSONAL_GLOSSARY_MINING_POLICY_VERSION, validateConversationProvenance, } from "../registries/glossaryMiningAuthority.js";
5
+ import { compareGlossaryUnicodeStrings, stableGlossaryTermIdentity, } from "../registries/glossaryTermIdentity.js";
6
+ import { readSignalTier, resolveEvidenceAnchor, } from "./extractCorpus/evidenceTiers.js";
7
+ import { assessTiers, recoveryForState } from "./extractCorpus/tierReader.js";
8
+ import { classifyRecurringLexicalToken } from "./personalGlossaryLexicalClassifier.js";
9
+ /** The only semantic text put in a recurrence capsule before host review. */
10
+ export const RECURRING_MEANING_PENDING_REVIEW = "meaning pending semantic review";
11
+ export const RECURRING_REASONS = {
12
+ agentOnlyEvidence: "agent_only_evidence",
13
+ copiedContent: "copied_content",
14
+ provenanceMissing: "provenance_missing",
15
+ insufficientIndependentOrigins: "insufficient_independent_origins",
16
+ insufficientProjects: "insufficient_projects",
17
+ insufficientSessions: "insufficient_sessions",
18
+ conversationEvidenceIncomplete: "conversation_evidence_incomplete",
19
+ conversationEvidenceBoundExceeded: "conversation_evidence_bound_exceeded",
20
+ projectOnlyScope: "project_only_scope",
21
+ noiseTerm: "noise_term",
22
+ tooManyQualifyingOrigins: "too_many_qualifying_origins",
23
+ userAuthorshipRequired: "user_authorship_required",
24
+ conflictingMeaning: "conflicting_meaning",
25
+ uncertainScope: "uncertain_scope",
26
+ inferredUsageRequiresReview: "inferred_usage_requires_review",
27
+ };
28
+ const IDENTIFIER_CONTINUATION = "[\\p{ID_Continue}$\\u200C\\u200D]";
29
+ const TOKEN_RE = /[\p{L}\p{N}][\p{L}\p{N}\p{M}]*(?:[-_$\u200C\u200D][\p{L}\p{N}][\p{L}\p{N}\p{M}]*)*/gu;
30
+ const COMBINING_MARK_RE = /^\p{M}$/u;
31
+ const INTERNAL_SEPARATOR_RE = /^[-_$\u200C\u200D]$/u;
32
+ const SHA256_RE = /^[a-f0-9]{64}$/;
33
+ function mapping(value) {
34
+ return value !== null && typeof value === "object" && !Array.isArray(value)
35
+ ? value
36
+ : null;
37
+ }
38
+ function compareText(left, right) {
39
+ return compareGlossaryUnicodeStrings(left, right);
40
+ }
41
+ function nonEmpty(value) {
42
+ return typeof value === "string" && value.trim().length > 0;
43
+ }
44
+ function dataObject(record) {
45
+ return mapping(record.data) ?? {};
46
+ }
47
+ function semanticValues(record) {
48
+ const data = dataObject(record);
49
+ if (record.source_kind === "instruction_document") {
50
+ return typeof data.content === "string" ? [data.content] : [];
51
+ }
52
+ if (record.source_kind === "project_config_signal") {
53
+ return Array.isArray(data.signals)
54
+ ? data.signals.filter((value) => typeof value === "string")
55
+ : [];
56
+ }
57
+ if (record.source_kind === "conversation_turn") {
58
+ return ["text", "prompt", "content"].flatMap((field) => typeof data[field] === "string" ? [data[field]] : []);
59
+ }
60
+ return [];
61
+ }
62
+ function firstString(...values) {
63
+ return values.find((value) => nonEmpty(value)) ?? null;
64
+ }
65
+ function validDigest(value) {
66
+ return value !== null && SHA256_RE.test(value);
67
+ }
68
+ function resolvedSignal(signal, record) {
69
+ if (record.source_id !== signal.source_id || record.source_kind !== signal.source_kind)
70
+ return null;
71
+ const retainedProjectId = typeof record.project_id === "string" ? record.project_id : "";
72
+ if (signal.project_id !== retainedProjectId)
73
+ return null;
74
+ for (const [signalField, recordField] of [
75
+ ["origin_id", "origin_id"],
76
+ ["content_fingerprint", "content_fingerprint"],
77
+ ["session_id", "session_id"],
78
+ ["author_class", "author_class"],
79
+ ]) {
80
+ const signalValue = signal[signalField];
81
+ if (signalValue !== undefined && signalValue !== record[recordField])
82
+ return null;
83
+ }
84
+ const values = semanticValues(record);
85
+ if (values.length === 0)
86
+ return null;
87
+ return {
88
+ signal,
89
+ record,
90
+ values,
91
+ originId: firstString(signal.origin_id, record.origin_id),
92
+ contentFingerprint: firstString(signal.content_fingerprint, record.content_fingerprint),
93
+ sessionId: firstString(signal.session_id, record.session_id),
94
+ projectId: signal.project_id || String(record.project_id ?? ""),
95
+ authorClass: firstString(signal.author_class, record.author_class),
96
+ provenanceValid: validDigest(firstString(signal.origin_id, record.origin_id)) &&
97
+ validDigest(firstString(signal.content_fingerprint, record.content_fingerprint)),
98
+ };
99
+ }
100
+ function escapeRegExp(value) {
101
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
102
+ }
103
+ function hasCaselessTerm(text, term) {
104
+ const characters = [...term];
105
+ const prefix = characters[0] && new RegExp(`^${IDENTIFIER_CONTINUATION}$`, "u").test(characters[0])
106
+ ? `(?<!${IDENTIFIER_CONTINUATION})`
107
+ : "";
108
+ const last = characters.at(-1) ?? "";
109
+ const suffix = new RegExp(`^${IDENTIFIER_CONTINUATION}$`, "u").test(last)
110
+ ? `(?!${IDENTIFIER_CONTINUATION})`
111
+ : "";
112
+ return new RegExp(`${prefix}${escapeRegExp(term)}${suffix}`, "iu").test(text);
113
+ }
114
+ function hasCase(value) {
115
+ return value.toLowerCase() !== value.toUpperCase();
116
+ }
117
+ function startsWithUppercase(value) {
118
+ const first = [...value][0];
119
+ return first !== undefined && hasCase(first) && first.toUpperCase() === first;
120
+ }
121
+ function scalarBefore(text, index) {
122
+ if (index <= 0)
123
+ return "";
124
+ const lastUnit = text.charCodeAt(index - 1);
125
+ const previousUnit = text.charCodeAt(index - 2);
126
+ const start = lastUnit >= 0xdc00 && lastUnit <= 0xdfff && previousUnit >= 0xd800 && previousUnit <= 0xdbff
127
+ ? index - 2
128
+ : index - 1;
129
+ return text.slice(start, index);
130
+ }
131
+ function scalarAt(text, index) {
132
+ const codePoint = text.codePointAt(index);
133
+ return codePoint === undefined ? "" : String.fromCodePoint(codePoint);
134
+ }
135
+ function touchesMalformedCombiningSequence(text, start, end) {
136
+ if (COMBINING_MARK_RE.test(scalarBefore(text, start)))
137
+ return true;
138
+ const next = scalarAt(text, end);
139
+ return (INTERNAL_SEPARATOR_RE.test(next) && COMBINING_MARK_RE.test(scalarAt(text, end + next.length)));
140
+ }
141
+ function isNoiseToken(value, text, start, end) {
142
+ if ([...value].length < 3 || [...value].length > 64)
143
+ return true;
144
+ if (touchesMalformedCombiningSequence(text, start, end))
145
+ return true;
146
+ if (/^\d+$/.test(value) || classifyRecurringLexicalToken(value, text, start, end) !== null)
147
+ return true;
148
+ if (startsWithUppercase(value))
149
+ return true;
150
+ const previous = text[start - 1] ?? "";
151
+ const beforePrevious = text[start - 2] ?? "";
152
+ const next = text[end] ?? "";
153
+ if ([":", "=", "@", "#"].includes(previous))
154
+ return true;
155
+ if ([":", "=", "@", "#"].includes(next))
156
+ return true;
157
+ if (beforePrevious === "-" && previous === "-")
158
+ return true;
159
+ return false;
160
+ }
161
+ function candidateTokens(text) {
162
+ const tokens = [...text.matchAll(TOKEN_RE)].map((match) => ({
163
+ value: match[0],
164
+ start: match.index ?? 0,
165
+ end: (match.index ?? 0) + match[0].length,
166
+ }));
167
+ return tokens.filter((token) => !isNoiseToken(token.value, text, token.start, token.end));
168
+ }
169
+ function cueTerms(values) {
170
+ const terms = new Set();
171
+ for (const value of values) {
172
+ const tokens = candidateTokens(value);
173
+ for (const token of tokens)
174
+ terms.add(token.value);
175
+ for (let index = 1; index < tokens.length; index += 1) {
176
+ const previous = tokens[index - 1];
177
+ const current = tokens[index];
178
+ if (/^[\s]+$/.test(value.slice(previous.end, current.start))) {
179
+ const phrase = `${previous.value} ${current.value}`;
180
+ if (!isNoiseToken(phrase, phrase, 0, phrase.length))
181
+ terms.add(phrase);
182
+ }
183
+ }
184
+ }
185
+ return [...terms];
186
+ }
187
+ function meaningCues(term, values) {
188
+ const escaped = escapeRegExp(term);
189
+ const cues = new Set();
190
+ const patterns = [
191
+ new RegExp(`${escaped}\\s+(?:means|refers to|is)\\s+([^.!?\\n]+)`, "giu"),
192
+ new RegExp(`(?:means|refers to)\\s+${escaped}\\s+([^.!?\\n]+)`, "giu"),
193
+ ];
194
+ for (const value of values) {
195
+ for (const pattern of patterns) {
196
+ for (const match of value.matchAll(pattern)) {
197
+ const cue = match[1]?.trim();
198
+ if (cue)
199
+ cues.add(cue);
200
+ }
201
+ }
202
+ }
203
+ return cues;
204
+ }
205
+ function scopeMarkers(term, values) {
206
+ const escaped = escapeRegExp(term);
207
+ const projectPattern = new RegExp(`(?:project[- ]only|repository[- ]only|for this project|project\\s+(?:term|meaning|definition)\\s*[:=]?\\s*${escaped})`, "iu");
208
+ const personalPattern = new RegExp(`(?:personal[- ]only|for me|my\\s+(?:term|meaning|definition)\\s*[:=]?\\s*${escaped})`, "iu");
209
+ return {
210
+ project: values.some((value) => projectPattern.test(value)),
211
+ personal: values.some((value) => personalPattern.test(value)),
212
+ };
213
+ }
214
+ function isProjectOnly(records, occurrences, route) {
215
+ if (records.length === 0)
216
+ return false;
217
+ const dataScopes = records.map((item) => String(dataObject(item.record).scope ?? ""));
218
+ const explicitProjectScope = dataScopes.every((scope) => scope === "project" || scope === "project_only");
219
+ const projectConfigOnly = records.every((item) => item.signal.source_kind === "project_config_signal");
220
+ const oneProject = new Set(records.map((item) => item.projectId)).size === 1;
221
+ const markedProject = occurrences.length > 0 && occurrences.every((item) => item.projectScope && !item.personalScope);
222
+ if (route === "conversation")
223
+ return explicitProjectScope || markedProject;
224
+ return ((projectConfigOnly && oneProject) ||
225
+ (explicitProjectScope && projectConfigOnly) ||
226
+ markedProject);
227
+ }
228
+ function groupOccurrences(resolved, allowedSourceKinds, allowedSignalTypes, includeAgents) {
229
+ const groups = new Map();
230
+ for (const source of resolved) {
231
+ if (!allowedSourceKinds.has(source.signal.source_kind) ||
232
+ !allowedSignalTypes.has(source.signal.signal_type))
233
+ continue;
234
+ if (!includeAgents && source.authorClass !== null && source.authorClass !== "user")
235
+ continue;
236
+ const terms = cueTerms(source.values);
237
+ const seenInRecord = new Set();
238
+ for (const term of terms) {
239
+ const identity = stableGlossaryTermIdentity(term);
240
+ if (seenInRecord.has(identity))
241
+ continue;
242
+ seenInRecord.add(identity);
243
+ const scope = scopeMarkers(term, source.values);
244
+ const occurrence = {
245
+ term,
246
+ source,
247
+ meaningCues: meaningCues(term, source.values),
248
+ projectScope: scope.project,
249
+ personalScope: scope.personal,
250
+ };
251
+ const group = groups.get(identity) ?? { identity, terms: new Set(), occurrences: [] };
252
+ group.terms.add(term);
253
+ group.occurrences.push(occurrence);
254
+ groups.set(identity, group);
255
+ }
256
+ }
257
+ return groups;
258
+ }
259
+ function requestedIdentitySet(terms) {
260
+ const requested = new Map();
261
+ for (const value of terms ?? []) {
262
+ const term = value.trim();
263
+ if (!term)
264
+ continue;
265
+ requested.set(stableGlossaryTermIdentity(term), term);
266
+ }
267
+ return requested;
268
+ }
269
+ function orderedSignals(signals) {
270
+ return [...signals].sort((left, right) => compareText(left.signal.source_id, right.signal.source_id) ||
271
+ compareText(left.signal.evidence_anchor, right.signal.evidence_anchor));
272
+ }
273
+ function independentEvidence(records) {
274
+ const selected = [];
275
+ const sourceIds = new Set();
276
+ const anchors = new Set();
277
+ const origins = new Set();
278
+ const fingerprints = new Set();
279
+ let copied = false;
280
+ let provenanceMissing = false;
281
+ for (const record of orderedSignals(records)) {
282
+ if (!record.provenanceValid) {
283
+ provenanceMissing = true;
284
+ continue;
285
+ }
286
+ const duplicate = sourceIds.has(record.signal.source_id) ||
287
+ anchors.has(record.signal.evidence_anchor) ||
288
+ (record.originId !== null && origins.has(record.originId)) ||
289
+ (record.contentFingerprint !== null && fingerprints.has(record.contentFingerprint));
290
+ if (duplicate) {
291
+ copied = true;
292
+ continue;
293
+ }
294
+ sourceIds.add(record.signal.source_id);
295
+ anchors.add(record.signal.evidence_anchor);
296
+ if (record.originId !== null)
297
+ origins.add(record.originId);
298
+ if (record.contentFingerprint !== null)
299
+ fingerprints.add(record.contentFingerprint);
300
+ selected.push(record);
301
+ }
302
+ return { records: selected, copied, provenanceMissing };
303
+ }
304
+ function canonicalTerm(group, requested) {
305
+ if (requested !== undefined)
306
+ return requested;
307
+ return [...group.terms].sort(compareText)[0] ?? "";
308
+ }
309
+ function retainedEvidence(record) {
310
+ return {
311
+ sourceId: record.signal.source_id,
312
+ sourceKind: record.signal.source_kind,
313
+ signalType: record.signal.signal_type,
314
+ ...(record.sessionId === null ? {} : { sessionId: record.sessionId }),
315
+ projectId: record.projectId,
316
+ ...(record.contentFingerprint === null
317
+ ? {}
318
+ : { contentFingerprint: record.contentFingerprint }),
319
+ ...(record.authorClass === null ? {} : { authorClass: record.authorClass }),
320
+ };
321
+ }
322
+ function conversationVariant() {
323
+ const authority = loadYamlMappingFile(glossaryEntryAuthorityPath());
324
+ const variants = mapping(authority.provenance_variants);
325
+ return mapping(variants?.personal_inferred_conversation) ?? {};
326
+ }
327
+ function candidateEvidenceMax() {
328
+ try {
329
+ const authority = loadYamlMappingFile(glossaryEntryAuthorityPath());
330
+ const candidateContracts = mapping(authority.candidate_contracts);
331
+ const bounds = mapping(candidateContracts?.bounds);
332
+ const maximum = Number(bounds?.evidence_records_max);
333
+ return Number.isInteger(maximum) && maximum > 0 ? maximum : 0;
334
+ }
335
+ catch {
336
+ return 0;
337
+ }
338
+ }
339
+ function expectationFor(term, identity, input) {
340
+ return (input.conversationEvidenceByTerm?.get(identity) ??
341
+ input.conversationEvidenceByTerm?.get(term) ??
342
+ input.conversationEvidence);
343
+ }
344
+ function conversationFailureReason(records, agentRecords, expectation, generation, evidenceMax) {
345
+ const expectedAnchors = expectation?.qualifyingEvidenceAnchors;
346
+ if (evidenceMax === 0 ||
347
+ records.length > evidenceMax ||
348
+ (Array.isArray(expectedAnchors) && expectedAnchors.length > evidenceMax)) {
349
+ return RECURRING_REASONS.conversationEvidenceBoundExceeded;
350
+ }
351
+ if (records.length === 0) {
352
+ return agentRecords.length > 0
353
+ ? RECURRING_REASONS.agentOnlyEvidence
354
+ : RECURRING_REASONS.insufficientIndependentOrigins;
355
+ }
356
+ if (agentRecords.length > 0 && records.length < 3) {
357
+ return RECURRING_REASONS.userAuthorshipRequired;
358
+ }
359
+ const independent = independentEvidence(records);
360
+ if (independent.provenanceMissing)
361
+ return RECURRING_REASONS.provenanceMissing;
362
+ if (independent.copied)
363
+ return RECURRING_REASONS.copiedContent;
364
+ if (new Set(records.map((record) => record.sessionId).filter(nonEmpty)).size < 2) {
365
+ return RECURRING_REASONS.insufficientSessions;
366
+ }
367
+ if (new Set(records.map((record) => record.projectId).filter(nonEmpty)).size < 2) {
368
+ return RECURRING_REASONS.insufficientProjects;
369
+ }
370
+ if (records.length < 3 || new Set(records.map((record) => record.signal.source_id)).size < 3) {
371
+ return RECURRING_REASONS.insufficientIndependentOrigins;
372
+ }
373
+ if (!expectation || expectation.generation !== generation) {
374
+ return RECURRING_REASONS.conversationEvidenceIncomplete;
375
+ }
376
+ return null;
377
+ }
378
+ function validateConversationEvidence(records, expectation, generation) {
379
+ try {
380
+ if (expectation.generation !== generation)
381
+ return false;
382
+ const evidence = records.map((record) => ({
383
+ source_id: record.signal.source_id,
384
+ evidence_anchor: record.signal.evidence_anchor,
385
+ source_kind: record.signal.source_kind,
386
+ signal_type: record.signal.signal_type,
387
+ session_id: record.sessionId ?? "",
388
+ project_id: record.projectId,
389
+ content_fingerprint: record.contentFingerprint ?? "",
390
+ author_class: record.authorClass ?? "",
391
+ }));
392
+ const retainedHistory = new Map(records.map((record) => [record.signal.evidence_anchor, retainedEvidence(record)]));
393
+ const context = {
394
+ retainedHistory,
395
+ conversationEvidence: expectation,
396
+ };
397
+ return (validateConversationProvenance(evidence, { evidence_complete: true }, conversationVariant(), context).length === 0);
398
+ }
399
+ catch {
400
+ return false;
401
+ }
402
+ }
403
+ function evidenceForInstruction(records) {
404
+ return orderedSignals(records).map((record) => ({
405
+ source_id: record.signal.source_id,
406
+ evidence_anchor: record.signal.evidence_anchor,
407
+ source_kind: record.signal.source_kind,
408
+ }));
409
+ }
410
+ function evidenceForConversation(records) {
411
+ return orderedSignals(records).map((record) => ({
412
+ source_id: record.signal.source_id,
413
+ evidence_anchor: record.signal.evidence_anchor,
414
+ source_kind: record.signal.source_kind,
415
+ signal_type: record.signal.signal_type,
416
+ session_id: record.sessionId ?? "",
417
+ project_id: record.projectId,
418
+ content_fingerprint: record.contentFingerprint ?? "",
419
+ author_class: record.authorClass ?? "",
420
+ }));
421
+ }
422
+ function reviewReason(occurrences) {
423
+ const meanings = new Set(occurrences.flatMap((occurrence) => [...occurrence.meaningCues]));
424
+ if (meanings.size > 1)
425
+ return RECURRING_REASONS.conflictingMeaning;
426
+ const project = occurrences.some((occurrence) => occurrence.projectScope);
427
+ const personal = occurrences.some((occurrence) => occurrence.personalScope);
428
+ if (project || personal)
429
+ return RECURRING_REASONS.uncertainScope;
430
+ return RECURRING_REASONS.inferredUsageRequiresReview;
431
+ }
432
+ function makeCandidate(term, provenanceKind, evidence, records, generation, reason) {
433
+ const capsule = createGlossaryEvidenceCapsule({
434
+ term,
435
+ meaning: RECURRING_MEANING_PENDING_REVIEW,
436
+ scope: "ambiguous",
437
+ provenance_kind: provenanceKind,
438
+ evidence,
439
+ policy_version: PERSONAL_GLOSSARY_MINING_POLICY_VERSION,
440
+ generation,
441
+ });
442
+ return {
443
+ capsule,
444
+ outcome: "review_required",
445
+ reason,
446
+ project_ids: [...new Set(records.map((record) => record.projectId))].sort(compareText),
447
+ };
448
+ }
449
+ function abstention(term, reason) {
450
+ return {
451
+ term,
452
+ candidate_id: stableGlossaryTermIdentity(term),
453
+ reason,
454
+ };
455
+ }
456
+ function addAbstention(output, term, reason) {
457
+ const candidateId = stableGlossaryTermIdentity(term);
458
+ if (!output.has(candidateId))
459
+ output.set(candidateId, abstention(term, reason));
460
+ }
461
+ function rawTermPresent(term, resolved) {
462
+ return resolved.some((source) => source.values.some((value) => hasCaselessTerm(value, term)));
463
+ }
464
+ /**
465
+ * Mine recurring usage from the current bounded signal tier only.
466
+ *
467
+ * This function emits review-bound capsules or stable abstentions. It does not
468
+ * classify meaning, authorize admission, read project glossary state, persist a
469
+ * review, or scan full evidence beyond direct anchor resolution.
470
+ */
471
+ export function mineRecurringGlossaryCandidates(input, resolveAnchor = resolveEvidenceAnchor) {
472
+ const assessment = assessTiers(input.tiersDir, input.corpusPath);
473
+ if (!assessment.analyzable) {
474
+ return {
475
+ state: assessment.state,
476
+ generation: null,
477
+ candidates: [],
478
+ abstentions: [],
479
+ recovery: recoveryForState(assessment.state),
480
+ };
481
+ }
482
+ const tier = readSignalTier(input.tiersDir, { allowProvenanceGaps: true });
483
+ if (!tier) {
484
+ return {
485
+ state: "corrupt",
486
+ generation: null,
487
+ candidates: [],
488
+ abstentions: [],
489
+ recovery: recoveryForState("corrupt"),
490
+ };
491
+ }
492
+ const authority = personalGlossaryAdmissionContract();
493
+ const selected = tier.records
494
+ .filter((signal) => authority.inferredSignalTypes.includes(signal.signal_type) ||
495
+ authority.conversationSignalTypes.includes(signal.signal_type))
496
+ .sort((left, right) => compareText(left.source_id, right.source_id));
497
+ const resolved = [];
498
+ for (const signal of selected) {
499
+ const record = resolveAnchor(signal.evidence_anchor, input.tiersDir);
500
+ if (!record)
501
+ continue;
502
+ const value = resolvedSignal(signal, record);
503
+ if (value)
504
+ resolved.push(value);
505
+ }
506
+ const requested = requestedIdentitySet(input.requestedTerms);
507
+ const instructionKinds = new Set(authority.inferredSourceKinds);
508
+ const instructionTypes = new Set(authority.inferredSignalTypes);
509
+ const conversationKinds = new Set(authority.conversationSourceKinds);
510
+ const conversationTypes = new Set(authority.conversationSignalTypes);
511
+ const instructionGroups = groupOccurrences(resolved, instructionKinds, instructionTypes, false);
512
+ const conversationGroups = groupOccurrences(resolved, conversationKinds, conversationTypes, false);
513
+ const agentConversationGroups = groupOccurrences(resolved, conversationKinds, conversationTypes, true);
514
+ const evidenceMax = candidateEvidenceMax();
515
+ const allGroups = new Map();
516
+ for (const group of [...instructionGroups.values(), ...conversationGroups.values()]) {
517
+ const current = allGroups.get(group.identity) ?? {
518
+ identity: group.identity,
519
+ terms: new Set(),
520
+ occurrences: [],
521
+ };
522
+ for (const term of group.terms)
523
+ current.terms.add(term);
524
+ current.occurrences.push(...group.occurrences);
525
+ allGroups.set(group.identity, current);
526
+ }
527
+ const allResolved = resolved;
528
+ const abstentions = new Map();
529
+ const candidates = new Map();
530
+ const identities = new Set([...allGroups.keys(), ...requested.keys()]);
531
+ for (const identity of [...identities].sort(compareText)) {
532
+ const requestedTerm = requested.get(identity);
533
+ const instructionGroup = instructionGroups.get(identity);
534
+ const conversationGroup = conversationGroups.get(identity);
535
+ const agentGroup = agentConversationGroups.get(identity);
536
+ const group = allGroups.get(identity);
537
+ const term = canonicalTerm(group ?? { identity, terms: new Set(requestedTerm ? [requestedTerm] : []), occurrences: [] }, requestedTerm);
538
+ if (!term)
539
+ continue;
540
+ let deferredReason = null;
541
+ if (!group && requestedTerm && agentGroup && !instructionGroup) {
542
+ addAbstention(abstentions, requestedTerm, RECURRING_REASONS.agentOnlyEvidence);
543
+ continue;
544
+ }
545
+ if (!group && requestedTerm && rawTermPresent(requestedTerm, allResolved)) {
546
+ addAbstention(abstentions, requestedTerm, RECURRING_REASONS.noiseTerm);
547
+ continue;
548
+ }
549
+ if (conversationGroup) {
550
+ const userRecords = conversationGroup.occurrences.map((occurrence) => occurrence.source);
551
+ const agentRecords = agentGroup
552
+ ? agentGroup.occurrences
553
+ .map((occurrence) => occurrence.source)
554
+ .filter((record) => record.authorClass !== "user")
555
+ : [];
556
+ const expected = expectationFor(term, identity, input);
557
+ if (isProjectOnly(userRecords, conversationGroup.occurrences, "conversation")) {
558
+ addAbstention(abstentions, term, RECURRING_REASONS.projectOnlyScope);
559
+ continue;
560
+ }
561
+ const failure = conversationFailureReason(userRecords, agentRecords, expected, tier.manifest.generation, evidenceMax);
562
+ if (failure === null &&
563
+ expected &&
564
+ validateConversationEvidence(userRecords, expected, tier.manifest.generation)) {
565
+ const reason = reviewReason(conversationGroup.occurrences);
566
+ candidates.set(identity, makeCandidate(term, "personal_inferred_conversation", evidenceForConversation(userRecords), userRecords, tier.manifest.generation, reason));
567
+ continue;
568
+ }
569
+ deferredReason = failure ?? RECURRING_REASONS.conversationEvidenceIncomplete;
570
+ }
571
+ else if (agentGroup && !instructionGroup) {
572
+ addAbstention(abstentions, term, RECURRING_REASONS.agentOnlyEvidence);
573
+ continue;
574
+ }
575
+ if (instructionGroup) {
576
+ const records = instructionGroup.occurrences.map((occurrence) => occurrence.source);
577
+ if (isProjectOnly(records, instructionGroup.occurrences, "instruction")) {
578
+ addAbstention(abstentions, term, RECURRING_REASONS.projectOnlyScope);
579
+ continue;
580
+ }
581
+ const independent = independentEvidence(records);
582
+ if (independent.provenanceMissing) {
583
+ deferredReason = RECURRING_REASONS.provenanceMissing;
584
+ }
585
+ else if (independent.copied) {
586
+ deferredReason = RECURRING_REASONS.copiedContent;
587
+ }
588
+ else if (independent.records.length < 2) {
589
+ deferredReason = RECURRING_REASONS.insufficientIndependentOrigins;
590
+ }
591
+ else if (independent.records.length > 2) {
592
+ deferredReason = RECURRING_REASONS.tooManyQualifyingOrigins;
593
+ }
594
+ else {
595
+ candidates.set(identity, makeCandidate(term, "personal_inferred_usage", evidenceForInstruction(independent.records), independent.records, tier.manifest.generation, reviewReason(instructionGroup.occurrences)));
596
+ abstentions.delete(identity);
597
+ deferredReason = null;
598
+ }
599
+ }
600
+ if (!candidates.has(identity) && deferredReason !== null) {
601
+ addAbstention(abstentions, term, deferredReason);
602
+ }
603
+ }
604
+ return {
605
+ state: assessment.state,
606
+ generation: tier.manifest.generation,
607
+ candidates: [...candidates.entries()]
608
+ .sort(([left], [right]) => compareText(left, right))
609
+ .map(([, candidate]) => candidate),
610
+ abstentions: [...abstentions.entries()]
611
+ .sort(([left], [right]) => compareText(left, right))
612
+ .map(([, result]) => result),
613
+ recovery: null,
614
+ };
615
+ }
616
+ /** Short name used by later bounded mining callers. */
617
+ export const mineRecurringGlossaryEvidence = mineRecurringGlossaryCandidates;
618
+ export { PERSONAL_GLOSSARY_MINING_POLICY_VERSION };