agentera 3.0.0-dev.72 → 3.0.0-dev.75

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 (93) 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/analysis/verification-policy.yaml +8 -13
  13. package/bundle/references/artifacts/glossary-entry-contract.yaml +1533 -35
  14. package/bundle/references/cli/update-channels.yaml +2 -2
  15. package/bundle/references/cli/vocabulary.md +1 -1
  16. package/bundle/references/meta/retained-reference-authority.yaml +16 -1
  17. package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +11 -7
  18. package/bundle/skills/agentera/capabilities/profile/schemas/exit.yaml +6 -3
  19. package/bundle/skills/agentera/capabilities/profile/schemas/validation.yaml +13 -7
  20. package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +5 -0
  21. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +7 -0
  22. package/dist/.agentera-build-source.json +8 -0
  23. package/dist/analytics/extractCorpus/copilotSessions.js +13 -4
  24. package/dist/analytics/extractCorpus/core.js +223 -3
  25. package/dist/analytics/extractCorpus/corpus.js +18 -1
  26. package/dist/analytics/extractCorpus/cursorSessions.js +22 -4
  27. package/dist/analytics/extractCorpus/evidenceTiers.js +123 -17
  28. package/dist/analytics/extractCorpus/filesystemSources.js +4 -0
  29. package/dist/analytics/extractCorpus/index.js +2 -2
  30. package/dist/analytics/extractCorpus/jsonlSessions.js +52 -3
  31. package/dist/analytics/extractCorpus/sqliteSessions.js +12 -3
  32. package/dist/analytics/personalGlossaryAdmission.js +26 -46
  33. package/dist/analytics/personalGlossaryCandidateProjection.js +692 -0
  34. package/dist/analytics/personalGlossaryCandidateProjectionExcerpts.js +144 -0
  35. package/dist/analytics/personalGlossaryCandidateReadView.js +23 -0
  36. package/dist/analytics/personalGlossaryCurrentGeneration.js +25 -0
  37. package/dist/analytics/personalGlossaryDecision.js +203 -0
  38. package/dist/analytics/personalGlossaryExplicit.js +788 -0
  39. package/dist/analytics/personalGlossaryExplicitMining.js +285 -0
  40. package/dist/analytics/personalGlossaryExplicitScope.js +41 -0
  41. package/dist/analytics/personalGlossaryExplicitSegments.js +418 -0
  42. package/dist/analytics/personalGlossaryExplicitTypes.js +26 -0
  43. package/dist/analytics/personalGlossaryLexicalClassifier.js +175 -0
  44. package/dist/analytics/personalGlossaryProfile.js +13 -3
  45. package/dist/analytics/personalGlossaryRecurrence.js +618 -0
  46. package/dist/analytics/personalGlossaryReviewRecordStorage.js +428 -0
  47. package/dist/analytics/personalGlossaryReviewRecords.js +332 -0
  48. package/dist/capabilities/index.js +5 -1
  49. package/dist/capabilities/profile/instructions.js +50 -15
  50. package/dist/cli/commands/personalGlossaryCandidateReads.js +744 -0
  51. package/dist/cli/commands/personalGlossaryDecision.js +349 -0
  52. package/dist/cli/commands/personalGlossaryPublish.js +484 -0
  53. package/dist/cli/commands/personalGlossaryReviewRecordReads.js +209 -0
  54. package/dist/cli/commands/personalGlossaryReviewRecords.js +623 -0
  55. package/dist/cli/commands/prime/briefOrientation.js +118 -19
  56. package/dist/cli/commands/report.js +18 -2
  57. package/dist/cli/commands/schema.js +179 -0
  58. package/dist/cli/commands/verify.js +51 -9
  59. package/dist/cli/dispatch/lifecycle.js +51 -6
  60. package/dist/cli/help.js +48 -2
  61. package/dist/cli/migrationRequired.js +4 -1
  62. package/dist/core/atomicWriter.js +4 -1
  63. package/dist/core/developmentInvocation.js +4 -1
  64. package/dist/core/yaml.js +53 -1
  65. package/dist/eval/glossaryEvaluation.js +751 -0
  66. package/dist/eval/glossaryEvaluationProcess.js +29 -0
  67. package/dist/eval/glossaryEvaluationRunner.js +571 -0
  68. package/dist/eval/glossaryEvaluationSuccessReport.js +297 -0
  69. package/dist/registries/activationTuples.js +31 -3
  70. package/dist/registries/evidenceTierContract.js +4 -0
  71. package/dist/registries/explicitSegmentGrammarContract.js +619 -0
  72. package/dist/registries/glossaryCandidateContractPaths.js +6 -0
  73. package/dist/registries/glossaryCandidateContracts.js +820 -0
  74. package/dist/registries/glossaryCandidateDecisionAuthority.js +119 -0
  75. package/dist/registries/glossaryCandidateDecisionContract.js +62 -0
  76. package/dist/registries/glossaryCandidateProjectionAuthority.js +197 -0
  77. package/dist/registries/glossaryCandidateProjectionContract.js +104 -0
  78. package/dist/registries/glossaryEntryContract.js +105 -103
  79. package/dist/registries/glossaryEntryTemporal.js +12 -0
  80. package/dist/registries/glossaryMiningAuthority.js +764 -0
  81. package/dist/registries/glossaryProfileFullContract.js +24 -0
  82. package/dist/registries/glossaryReviewRecordsAuthority.js +426 -0
  83. package/dist/registries/glossaryReviewRecordsContract.js +129 -0
  84. package/dist/registries/glossaryTermIdentity.js +122 -0
  85. package/dist/registries/packagePublication.js +3 -3
  86. package/dist/registries/personalGlossaryContracts.js +1 -0
  87. package/dist/release/releaseMetadata.js +15 -8
  88. package/dist/upgrade/npxPlatformStatus.js +2 -1
  89. package/dist/upgrade/projectIntegration.js +7 -1
  90. package/dist/validate/activationArtifactEvidence.js +18 -4
  91. package/dist/validate/activationEvidenceManifest.js +101 -22
  92. package/package.json +2 -4
  93. package/dist/cli/commands/personalGlossary.js +0 -176
@@ -0,0 +1,744 @@
1
+ import { personalGlossaryCandidateProjectionPath, } from "../../analytics/personalGlossaryCandidateProjection.js";
2
+ import { readCurrentPersonalGlossaryCandidateProjection } from "../../analytics/personalGlossaryCurrentGeneration.js";
3
+ import { currentPersonalGlossaryCandidateReadView, } from "../../analytics/personalGlossaryCandidateReadView.js";
4
+ import { shellQuoteArgument } from "../../core/shell.js";
5
+ import { canonicalGlossaryJson, compareGlossaryUnicodeStrings, glossaryCanonicalSha256, } from "../../registries/glossaryTermIdentity.js";
6
+ import { glossaryEntryAuthorityPath } from "../../registries/glossaryEntryContract.js";
7
+ import { personalGlossaryCandidateProjectionContract } from "../../registries/glossaryCandidateProjectionContract.js";
8
+ import { decodeListCursor, encodeListCursor, projectedListSnapshot, } from "../../state/listCursor.js";
9
+ import { emitInvalidInput } from "../errors.js";
10
+ import { emitStructured } from "../structured.js";
11
+ const COLLECTION = "personal_glossary_candidates";
12
+ const CURSOR_VERSION = 1;
13
+ const OCCURRENCE_SCHEMA_VERSION = "agentera.personalGlossaryCandidateOccurrence.v1";
14
+ const MAX_CURSOR_UTF8_BYTES = 4_096;
15
+ const SHA256 = /^[a-f0-9]{64}$/u;
16
+ function mapping(value) {
17
+ return value !== null && typeof value === "object" && !Array.isArray(value);
18
+ }
19
+ function contract() {
20
+ const value = personalGlossaryCandidateProjectionContract();
21
+ const sourceFamilies = value.candidateReadSourceFamilies;
22
+ const provenanceKinds = value.candidateReadProvenanceKinds;
23
+ if (value.candidateReadCommand !== "agentera report personal-glossary-candidates" ||
24
+ value.candidateReadSchemaVersion !== "agentera.personalGlossaryCandidateRetrieval.v1" ||
25
+ value.candidateReadDefaultLimit !== 20 ||
26
+ value.candidateReadMaximumLimit !== 50 ||
27
+ value.candidateReadOrder !== "candidate_id_then_candidate_revision_then_capsule_sha256" ||
28
+ value.candidateReadListProjectionBindingField !== "candidate_projection_sha256" ||
29
+ JSON.stringify(sourceFamilies) !== JSON.stringify(["explicit", "recurring"]) ||
30
+ JSON.stringify(provenanceKinds) !==
31
+ JSON.stringify([
32
+ "personal_explicit_definition",
33
+ "personal_inferred_conversation",
34
+ "personal_inferred_usage",
35
+ ]) ||
36
+ JSON.stringify(value.candidateReadScopes) !== JSON.stringify(["personal", "ambiguous"]) ||
37
+ value.candidateReadMaxSerializedUtf8Bytes !== 32_768 ||
38
+ value.candidateReadCursorVocabulary !== "opaque_snapshot_cursor" ||
39
+ JSON.stringify(value.candidateReadCursorBinding) !==
40
+ JSON.stringify([
41
+ "collection",
42
+ "generation",
43
+ "policy_version",
44
+ "filters",
45
+ "limit",
46
+ "order",
47
+ "snapshot",
48
+ ]) ||
49
+ value.candidateReadCursorInvalidBehavior !== "cursor_invalid" ||
50
+ value.candidateReadCursorUnavailableBehavior !== "cursor_snapshot_unavailable" ||
51
+ JSON.stringify(value.candidateReadExactRequiredBindings) !==
52
+ JSON.stringify(["candidate_id", "candidate_revision", "generation", "policy_version"]) ||
53
+ value.candidateReadExactProjectionBindingField !== "candidate_projection_sha256" ||
54
+ value.candidateReadExactOccurrencesMax !== 100 ||
55
+ value.candidateReadSafeContextMaxUtf8Bytes !== 500 ||
56
+ value.candidateReadExactMaxSerializedUtf8Bytes !== 32_768 ||
57
+ value.candidateReadCursorAuthority !==
58
+ "references/artifacts/state-storage-authority.yaml#entity_target.public_retrieval.policy.cursor" ||
59
+ value.candidateReadSafeContextViewAuthority !== "personal_mining_authority.privacy.retention" ||
60
+ value.candidateReadSafeContextRetentionDays !== 30 ||
61
+ value.candidateReadSafeContextViewExpiry !== "expires_at_lte_read_time_is_unavailable" ||
62
+ value.candidateReadSafeContextViewMutation !== "forbidden" ||
63
+ value.candidateReadSafeContextViewSnapshot !==
64
+ "effective_availability_bound_to_opaque_cursor_snapshot" ||
65
+ value.candidateReadCurrentGenerationSource !==
66
+ "current.json_readable_bounded_evidence_tier_generation" ||
67
+ value.candidateReadCurrentGenerationProjectionBinding !== "exact_match_required" ||
68
+ value.candidateReadCurrentGenerationUnavailableBehavior !==
69
+ "current_generation_unavailable" ||
70
+ value.candidateReadCurrentGenerationStaleProjectionBehavior !== "projection_stale") {
71
+ throw new TypeError("personal glossary candidate retrieval contract is invalid");
72
+ }
73
+ return {
74
+ command: value.candidateReadCommand,
75
+ schemaVersion: value.candidateReadSchemaVersion,
76
+ defaultLimit: value.candidateReadDefaultLimit,
77
+ maximumLimit: value.candidateReadMaximumLimit,
78
+ order: value.candidateReadOrder,
79
+ sourceFamilies,
80
+ provenanceKinds,
81
+ scopes: value.candidateReadScopes,
82
+ maxSerializedUtf8Bytes: value.candidateReadMaxSerializedUtf8Bytes,
83
+ exactOccurrencesMax: value.candidateReadExactOccurrencesMax,
84
+ safeContextMaxUtf8Bytes: value.candidateReadSafeContextMaxUtf8Bytes,
85
+ exactMaxSerializedUtf8Bytes: value.candidateReadExactMaxSerializedUtf8Bytes,
86
+ cursorAuthority: value.candidateReadCursorAuthority,
87
+ cursorVocabulary: value.candidateReadCursorVocabulary,
88
+ cursorBinding: value.candidateReadCursorBinding,
89
+ cursorInvalidBehavior: value.candidateReadCursorInvalidBehavior,
90
+ cursorUnavailableBehavior: value.candidateReadCursorUnavailableBehavior,
91
+ exactBindings: value.candidateReadExactRequiredBindings,
92
+ safeContextViewAuthority: value.candidateReadSafeContextViewAuthority,
93
+ safeContextRetentionDays: value.candidateReadSafeContextRetentionDays,
94
+ safeContextViewExpiry: value.candidateReadSafeContextViewExpiry,
95
+ safeContextViewMutation: value.candidateReadSafeContextViewMutation,
96
+ safeContextViewSnapshot: value.candidateReadSafeContextViewSnapshot,
97
+ };
98
+ }
99
+ function listSyntax(value) {
100
+ return `${value.command} list [--source-family explicit|recurring] [--provenance-kind KIND] [--scope personal|ambiguous] [--limit N] [--cursor TOKEN] --format json`;
101
+ }
102
+ function exactSyntax(value) {
103
+ return `${value.command} get --candidate-id ID --candidate-revision REVISION --generation GENERATION --policy-version POLICY --format json`;
104
+ }
105
+ function invalid(io, body) {
106
+ return emitInvalidInput(io, { format: "json", body });
107
+ }
108
+ function failure(io, command, syntax, example, body) {
109
+ emitStructured({
110
+ schemaVersion: "agentera.personalGlossaryCandidateRetrieval.v1",
111
+ command,
112
+ status: "fail",
113
+ error: { ...body, syntax, example },
114
+ }, "json", io.out ?? ((text) => process.stdout.write(text)));
115
+ return 1;
116
+ }
117
+ function argvPart(argument) {
118
+ const separator = argument.indexOf("=");
119
+ return separator < 0
120
+ ? { name: argument }
121
+ : { name: argument.slice(0, separator), inline: argument.slice(separator + 1) };
122
+ }
123
+ function boundedText(value, maximum) {
124
+ return value.length > 0 && Buffer.byteLength(value, "utf8") <= maximum;
125
+ }
126
+ function parseList(argv, value) {
127
+ let limit = value.defaultLimit;
128
+ let cursor;
129
+ let sourceFamily;
130
+ let provenanceKind;
131
+ let scope;
132
+ const seen = new Set();
133
+ for (let index = 0; index < argv.length; index += 1) {
134
+ const argument = argv[index];
135
+ const { name, inline } = argvPart(argument);
136
+ if (!name.startsWith("--")) {
137
+ return {
138
+ class: "unrecognized_argument",
139
+ message: `unrecognized arguments: ${argument}`,
140
+ syntax: listSyntax(value),
141
+ };
142
+ }
143
+ if (![
144
+ "--format",
145
+ "--limit",
146
+ "--cursor",
147
+ "--source-family",
148
+ "--provenance-kind",
149
+ "--scope",
150
+ ].includes(name)) {
151
+ return {
152
+ class: "unrecognized_argument",
153
+ message: `unrecognized arguments: ${argument}`,
154
+ syntax: listSyntax(value),
155
+ };
156
+ }
157
+ if (seen.has(name)) {
158
+ return {
159
+ class: "mutually_exclusive",
160
+ message: `${name} may only be supplied once`,
161
+ syntax: listSyntax(value),
162
+ };
163
+ }
164
+ seen.add(name);
165
+ const option = inline ?? argv[++index];
166
+ if (!option || option.startsWith("--")) {
167
+ return {
168
+ class: "missing_argument",
169
+ message: `${name} requires a value`,
170
+ syntax: `${name} VALUE`,
171
+ };
172
+ }
173
+ if (name === "--format") {
174
+ if (option !== "json") {
175
+ return {
176
+ class: "invalid_choice",
177
+ message: `argument --format: invalid choice: '${option}' (choose from 'json')`,
178
+ valid_values: ["json"],
179
+ };
180
+ }
181
+ continue;
182
+ }
183
+ if (name === "--limit") {
184
+ if (!/^(?:0|[1-9]\d*)$/u.test(option) || !Number.isSafeInteger(Number(option))) {
185
+ return {
186
+ class: "invalid_int",
187
+ message: "--limit must be an integer",
188
+ valid_values: [`1..${value.maximumLimit}`],
189
+ syntax: listSyntax(value),
190
+ };
191
+ }
192
+ limit = Number(option);
193
+ if (limit < 1 || limit > value.maximumLimit) {
194
+ return {
195
+ class: "invalid_request",
196
+ message: `list limit must be 1..${value.maximumLimit}`,
197
+ valid_values: [`1..${value.maximumLimit}`],
198
+ syntax: listSyntax(value),
199
+ };
200
+ }
201
+ continue;
202
+ }
203
+ if (name === "--cursor") {
204
+ if (Buffer.byteLength(option, "utf8") > MAX_CURSOR_UTF8_BYTES) {
205
+ return {
206
+ class: "invalid_request",
207
+ message: `--cursor exceeds its ${MAX_CURSOR_UTF8_BYTES}-byte bound`,
208
+ syntax: listSyntax(value),
209
+ };
210
+ }
211
+ cursor = option;
212
+ continue;
213
+ }
214
+ if (name === "--source-family") {
215
+ if (!value.sourceFamilies.includes(option)) {
216
+ return {
217
+ class: "invalid_choice",
218
+ message: `argument --source-family: invalid choice: '${option}'`,
219
+ valid_values: value.sourceFamilies,
220
+ };
221
+ }
222
+ sourceFamily = option;
223
+ continue;
224
+ }
225
+ if (name === "--provenance-kind") {
226
+ if (!value.provenanceKinds.includes(option)) {
227
+ return {
228
+ class: "invalid_choice",
229
+ message: `argument --provenance-kind: invalid choice: '${option}'`,
230
+ valid_values: value.provenanceKinds,
231
+ };
232
+ }
233
+ provenanceKind = option;
234
+ continue;
235
+ }
236
+ if (!value.scopes.includes(option)) {
237
+ return {
238
+ class: "invalid_choice",
239
+ message: `argument --scope: invalid choice: '${option}'`,
240
+ valid_values: value.scopes,
241
+ };
242
+ }
243
+ scope = option;
244
+ }
245
+ return { limit, cursor, sourceFamily, provenanceKind, scope };
246
+ }
247
+ function parseExact(argv, value) {
248
+ const fields = {};
249
+ const names = {
250
+ "--candidate-id": "candidateId",
251
+ "--candidate-revision": "candidateRevision",
252
+ "--generation": "generation",
253
+ "--policy-version": "policyVersion",
254
+ };
255
+ const seen = new Set();
256
+ for (let index = 0; index < argv.length; index += 1) {
257
+ const argument = argv[index];
258
+ const { name, inline } = argvPart(argument);
259
+ if (!["--format", ...Object.keys(names)].includes(name)) {
260
+ return {
261
+ class: "unrecognized_argument",
262
+ message: `unrecognized arguments: ${argument}`,
263
+ syntax: exactSyntax(value),
264
+ };
265
+ }
266
+ if (seen.has(name)) {
267
+ return {
268
+ class: "mutually_exclusive",
269
+ message: `${name} may only be supplied once`,
270
+ syntax: exactSyntax(value),
271
+ };
272
+ }
273
+ seen.add(name);
274
+ const option = inline ?? argv[++index];
275
+ if (!option || option.startsWith("--")) {
276
+ return {
277
+ class: "missing_argument",
278
+ message: `${name} requires a value`,
279
+ syntax: `${name} VALUE`,
280
+ };
281
+ }
282
+ if (name === "--format") {
283
+ if (option !== "json") {
284
+ return {
285
+ class: "invalid_choice",
286
+ message: `argument --format: invalid choice: '${option}' (choose from 'json')`,
287
+ valid_values: ["json"],
288
+ };
289
+ }
290
+ continue;
291
+ }
292
+ fields[names[name]] = option;
293
+ }
294
+ for (const [flag, field] of Object.entries(names)) {
295
+ if (!fields[field]) {
296
+ return {
297
+ class: "missing_argument",
298
+ message: `${flag} is required`,
299
+ syntax: exactSyntax(value),
300
+ };
301
+ }
302
+ }
303
+ if (!SHA256.test(fields.candidateId)) {
304
+ return {
305
+ class: "invalid_request",
306
+ message: "--candidate-id must be a lowercase SHA-256 identity",
307
+ valid_values: ["64 lowercase hexadecimal characters"],
308
+ syntax: exactSyntax(value),
309
+ };
310
+ }
311
+ if (!SHA256.test(fields.candidateRevision)) {
312
+ return {
313
+ class: "invalid_request",
314
+ message: "--candidate-revision must be a lowercase SHA-256 identity",
315
+ valid_values: ["64 lowercase hexadecimal characters"],
316
+ syntax: exactSyntax(value),
317
+ };
318
+ }
319
+ for (const [flag, field] of [
320
+ ["--generation", "generation"],
321
+ ["--policy-version", "policyVersion"],
322
+ ]) {
323
+ if (!boundedText(fields[field], 256)) {
324
+ return {
325
+ class: "invalid_request",
326
+ message: `${flag} must be a non-empty value within 256 UTF-8 bytes`,
327
+ syntax: exactSyntax(value),
328
+ };
329
+ }
330
+ }
331
+ return fields;
332
+ }
333
+ function filters(options) {
334
+ return {
335
+ source_family: options.sourceFamily ?? null,
336
+ provenance_kind: options.provenanceKind ?? null,
337
+ scope: options.scope ?? null,
338
+ };
339
+ }
340
+ function candidateKey(candidate) {
341
+ return [
342
+ candidate.capsule.candidate_id,
343
+ candidate.capsule.candidate_revision,
344
+ candidate.capsule.capsule_sha256,
345
+ ].join("\u0000");
346
+ }
347
+ function candidateOrder(left, right) {
348
+ return (compareGlossaryUnicodeStrings(left.capsule.candidate_id, right.capsule.candidate_id) ||
349
+ compareGlossaryUnicodeStrings(left.capsule.candidate_revision, right.capsule.candidate_revision) ||
350
+ compareGlossaryUnicodeStrings(left.capsule.capsule_sha256, right.capsule.capsule_sha256));
351
+ }
352
+ function candidateSummary(candidate) {
353
+ return {
354
+ candidate_id: candidate.capsule.candidate_id,
355
+ candidate_revision: candidate.capsule.candidate_revision,
356
+ term: candidate.capsule.term,
357
+ scope: candidate.capsule.scope,
358
+ provenance_kind: candidate.capsule.provenance_kind,
359
+ source_family: candidate.source_family,
360
+ occurrence_count: candidate.capsule.evidence.length,
361
+ safe_context_available: candidate.safe_excerpt !== null,
362
+ };
363
+ }
364
+ function projectionSummary(view) {
365
+ const report = view.projection.report;
366
+ const safeContextOmissions = Object.values(report.excerpts.omissions).reduce((total, count) => total + count, 0);
367
+ const safeContextExpired = report.excerpts.expired + view.expiredSafeContexts;
368
+ const safeContextAvailable = view.candidates.filter((candidate) => candidate.safe_excerpt !== null).length;
369
+ const safeContextUnavailable = safeContextOmissions + safeContextExpired;
370
+ if (safeContextAvailable + safeContextUnavailable !== view.candidates.length) {
371
+ throw new TypeError("candidate safe-context read view does not reconcile");
372
+ }
373
+ return {
374
+ retained_count: report.retained_count,
375
+ dropped_count: report.dropped_count,
376
+ source_families: report.source_families.map((family) => ({ ...family })),
377
+ projects: { ...report.projects },
378
+ coverage: { ...report.coverage, reasons: [...report.coverage.reasons] },
379
+ abstentions: {
380
+ candidate_selection: {
381
+ count: report.dropped_count,
382
+ reasons: [...report.coverage.reasons],
383
+ },
384
+ safe_context: {
385
+ available: safeContextAvailable,
386
+ count: safeContextUnavailable,
387
+ expired: safeContextExpired,
388
+ omissions: { ...report.excerpts.omissions },
389
+ },
390
+ },
391
+ };
392
+ }
393
+ function opaqueOccurrence(candidate, evidence) {
394
+ const occurrence = {
395
+ occurrence_id: glossaryCanonicalSha256({
396
+ schema_version: OCCURRENCE_SCHEMA_VERSION,
397
+ candidate_id: candidate.capsule.candidate_id,
398
+ candidate_revision: candidate.capsule.candidate_revision,
399
+ generation: candidate.capsule.generation,
400
+ policy_version: candidate.capsule.policy_version,
401
+ evidence,
402
+ }),
403
+ };
404
+ for (const field of ["source_kind", "signal_type", "author_class"]) {
405
+ if (typeof evidence[field] === "string")
406
+ occurrence[field] = evidence[field];
407
+ }
408
+ return occurrence;
409
+ }
410
+ function serializedBytes(value) {
411
+ return Buffer.byteLength(`${JSON.stringify(value, null, 2)}\n`, "utf8");
412
+ }
413
+ function listFlags(options) {
414
+ return [
415
+ options.sourceFamily ? ` --source-family ${shellQuoteArgument(options.sourceFamily)}` : "",
416
+ options.provenanceKind
417
+ ? ` --provenance-kind ${shellQuoteArgument(options.provenanceKind)}`
418
+ : "",
419
+ options.scope ? ` --scope ${shellQuoteArgument(options.scope)}` : "",
420
+ ].join("");
421
+ }
422
+ function currentProjection(io, value, operation) {
423
+ const result = readCurrentPersonalGlossaryCandidateProjection();
424
+ if (result.status === "current" && result.projection) {
425
+ try {
426
+ return currentPersonalGlossaryCandidateReadView(result.projection);
427
+ }
428
+ catch {
429
+ // Do not return a partially reconciled read view.
430
+ }
431
+ }
432
+ const unavailable = result.status === "current_generation_unavailable"
433
+ ? {
434
+ class: "current_generation_unavailable",
435
+ message: "the current bounded evidence tier generation is unavailable",
436
+ recovery: "Run `npx -y agentera@next report refresh --consent local-history`, then retry; no projection bytes were changed.",
437
+ }
438
+ : result.status === "projection_stale"
439
+ ? {
440
+ class: "projection_stale",
441
+ message: "the candidate projection is stale for the current bounded evidence tier generation",
442
+ recovery: "Run `npx -y agentera@next report refresh --consent local-history`, then retry; no projection bytes were changed.",
443
+ }
444
+ : {
445
+ class: "projection_unavailable",
446
+ message: "the current personal glossary candidate projection is unavailable or invalid",
447
+ recovery: "Create or repair a current bounded candidate projection, then retry; no projection bytes were changed.",
448
+ };
449
+ failure(io, `${value.command} ${operation}`, operation === "list" ? listSyntax(value) : exactSyntax(value), operation === "list"
450
+ ? `${value.command} list --limit ${value.defaultLimit} --format json`
451
+ : exactSyntax(value), unavailable);
452
+ return null;
453
+ }
454
+ function listCandidates(io, options, value) {
455
+ const view = currentProjection(io, value, "list");
456
+ if (!view)
457
+ return 1;
458
+ const projection = view.projection;
459
+ const selectedFilters = filters(options);
460
+ const candidates = view.candidates
461
+ .filter((candidate) => (!options.sourceFamily || candidate.source_family === options.sourceFamily) &&
462
+ (!options.provenanceKind || candidate.capsule.provenance_kind === options.provenanceKind) &&
463
+ (!options.scope || candidate.capsule.scope === options.scope))
464
+ .sort(candidateOrder);
465
+ const snapshotId = projectedListSnapshot({
466
+ schemaVersion: value.schemaVersion,
467
+ command: `${value.command} list`,
468
+ collection: COLLECTION,
469
+ generation: projection.generation,
470
+ policy_version: projection.policy_version,
471
+ projection_sha256: projection.projection_sha256,
472
+ safe_context_view_sha256: view.safeContextViewSha256,
473
+ filters: selectedFilters,
474
+ order: value.order,
475
+ });
476
+ let start = 0;
477
+ if (options.cursor) {
478
+ let cursor;
479
+ try {
480
+ cursor = decodeListCursor(options.cursor, personalGlossaryCandidateProjectionPath(), glossaryEntryAuthorityPath());
481
+ }
482
+ catch {
483
+ return failure(io, `${value.command} list`, listSyntax(value), `${value.command} list --limit ${value.defaultLimit} --format json`, {
484
+ class: "cursor_invalid",
485
+ message: "candidate-list cursor is malformed or belongs to another local profile",
486
+ recovery: "Copy next_cursor exactly, or omit --cursor to restart from the current projection; no projection bytes were changed.",
487
+ });
488
+ }
489
+ if (cursor.version !== CURSOR_VERSION ||
490
+ cursor.collection !== COLLECTION ||
491
+ cursor.limit !== options.limit ||
492
+ !mapping(cursor.filters) ||
493
+ canonicalGlossaryJson(cursor.filters) !== canonicalGlossaryJson(selectedFilters)) {
494
+ return failure(io, `${value.command} list`, listSyntax(value), `${value.command} list --limit ${value.defaultLimit} --format json`, {
495
+ class: "cursor_invalid",
496
+ message: "candidate-list cursor filters or limit do not match this request",
497
+ recovery: "Repeat the original filters and limit, or omit --cursor to restart from the current projection; no projection bytes were changed.",
498
+ });
499
+ }
500
+ if (cursor.generation !== projection.generation ||
501
+ cursor.policy_version !== projection.policy_version ||
502
+ cursor.order !== value.order ||
503
+ cursor.snapshot_id !== snapshotId ||
504
+ typeof cursor.after !== "string") {
505
+ return failure(io, `${value.command} list`, listSyntax(value), `${value.command} list --limit ${value.defaultLimit} --format json`, {
506
+ class: "cursor_snapshot_unavailable",
507
+ message: "candidate-list cursor cannot resume the current projection snapshot",
508
+ recovery: "Omit --cursor to restart from the current projection; no projection bytes were changed.",
509
+ });
510
+ }
511
+ const position = candidates.findIndex((candidate) => candidateKey(candidate) === cursor.after);
512
+ if (position < 0) {
513
+ return failure(io, `${value.command} list`, listSyntax(value), `${value.command} list --limit ${value.defaultLimit} --format json`, {
514
+ class: "cursor_snapshot_unavailable",
515
+ message: "candidate-list cursor continuation is unavailable",
516
+ recovery: "Omit --cursor to restart from the current projection; no projection bytes were changed.",
517
+ });
518
+ }
519
+ start = position + 1;
520
+ }
521
+ const entries = candidates.slice(start, start + options.limit);
522
+ const remaining = candidates.length - start - entries.length;
523
+ const nextCursor = remaining > 0 && entries.length > 0
524
+ ? encodeListCursor({
525
+ version: CURSOR_VERSION,
526
+ collection: COLLECTION,
527
+ generation: projection.generation,
528
+ policy_version: projection.policy_version,
529
+ filters: selectedFilters,
530
+ limit: options.limit,
531
+ order: value.order,
532
+ snapshot_id: snapshotId,
533
+ after: candidateKey(entries.at(-1)),
534
+ }, personalGlossaryCandidateProjectionPath(), glossaryEntryAuthorityPath())
535
+ : undefined;
536
+ const response = {
537
+ schemaVersion: value.schemaVersion,
538
+ command: `${value.command} list`,
539
+ status: remaining > 0 || projection.report.coverage.status === "degraded" ? "degraded" : "ok",
540
+ generation: projection.generation,
541
+ policy_version: projection.policy_version,
542
+ candidate_projection_sha256: projection.projection_sha256,
543
+ entries: entries.map(candidateSummary),
544
+ counts: {
545
+ total: candidates.length,
546
+ candidate: candidates.length,
547
+ returned: entries.length,
548
+ remaining,
549
+ omitted: remaining,
550
+ continuation: remaining,
551
+ },
552
+ filters: selectedFilters,
553
+ snapshot: {
554
+ id: snapshotId,
555
+ first_page: !options.cursor,
556
+ order: value.order,
557
+ has_more: remaining > 0,
558
+ candidate_count: candidates.length,
559
+ },
560
+ summary: projectionSummary(view),
561
+ source: {
562
+ kind: "user_local_candidate_projection",
563
+ owner: projection.owner,
564
+ },
565
+ source_contract: {
566
+ authority: "references/artifacts/glossary-entry-contract.yaml",
567
+ cursor: value.cursorVocabulary,
568
+ cursor_authority: value.cursorAuthority,
569
+ cursor_binding: [...value.cursorBinding],
570
+ cursor_invalid_behavior: value.cursorInvalidBehavior,
571
+ cursor_unavailable_behavior: value.cursorUnavailableBehavior,
572
+ safe_context_view: {
573
+ authority: value.safeContextViewAuthority,
574
+ retention_days: value.safeContextRetentionDays,
575
+ expiry: value.safeContextViewExpiry,
576
+ mutation: value.safeContextViewMutation,
577
+ snapshot: value.safeContextViewSnapshot,
578
+ },
579
+ },
580
+ retrieval: {
581
+ get: `${value.command} get --candidate-id ID --candidate-revision REVISION --generation GENERATION --policy-version POLICY --format json`,
582
+ ...(nextCursor
583
+ ? {
584
+ continue: `${value.command} list${listFlags(options)} --limit ${options.limit} --cursor ${nextCursor} --format json`,
585
+ }
586
+ : {}),
587
+ },
588
+ ...(remaining > 0
589
+ ? {
590
+ omitted: true,
591
+ omitted_count: remaining,
592
+ omission_reason: "page_limit",
593
+ next_cursor: nextCursor,
594
+ }
595
+ : {}),
596
+ };
597
+ if (serializedBytes(response) > value.maxSerializedUtf8Bytes) {
598
+ return failure(io, `${value.command} list`, listSyntax(value), `${value.command} list --limit ${value.defaultLimit} --format json`, {
599
+ class: "output_bound_exceeded",
600
+ message: `candidate-list response exceeds its ${value.maxSerializedUtf8Bytes}-byte bound`,
601
+ recovery: "Request fewer rows and retry; no partial candidate rows or projection bytes were returned.",
602
+ });
603
+ }
604
+ emitStructured(response, "json", io.out ?? ((text) => process.stdout.write(text)));
605
+ return 0;
606
+ }
607
+ function exactCandidate(io, options, value) {
608
+ const view = currentProjection(io, value, "get");
609
+ if (!view)
610
+ return 1;
611
+ const projection = view.projection;
612
+ if (options.generation !== projection.generation ||
613
+ options.policyVersion !== projection.policy_version) {
614
+ return failure(io, `${value.command} get`, exactSyntax(value), exactSyntax(value), {
615
+ class: "current_binding_mismatch",
616
+ message: "candidate generation or policy binding is not current",
617
+ recovery: "List the current projection and retry with its exact generation and policy binding; no projection bytes were changed.",
618
+ });
619
+ }
620
+ const sameId = view.candidates.filter((candidate) => candidate.capsule.candidate_id === options.candidateId);
621
+ if (sameId.length === 0) {
622
+ return failure(io, `${value.command} get`, exactSyntax(value), exactSyntax(value), {
623
+ class: "not_found",
624
+ message: "candidate identity was not found in the current projection",
625
+ recovery: "List the current projection and retry with one returned candidate identity; no projection bytes were changed.",
626
+ });
627
+ }
628
+ const candidate = sameId.find((item) => item.capsule.candidate_revision === options.candidateRevision);
629
+ if (!candidate) {
630
+ return failure(io, `${value.command} get`, exactSyntax(value), exactSyntax(value), {
631
+ class: "current_binding_mismatch",
632
+ message: "candidate revision is not current for the requested identity",
633
+ recovery: "List the current projection and retry with its exact candidate revision; no projection bytes were changed.",
634
+ });
635
+ }
636
+ const occurrences = candidate.capsule.evidence.map((evidence) => opaqueOccurrence(candidate, evidence));
637
+ if (occurrences.length > value.exactOccurrencesMax) {
638
+ return failure(io, `${value.command} get`, exactSyntax(value), exactSyntax(value), {
639
+ class: "unsupported_state",
640
+ message: "current candidate exceeds its validated occurrence bound",
641
+ recovery: "Repair the private candidate projection before retrying; no projection bytes were changed.",
642
+ });
643
+ }
644
+ if (candidate.safe_excerpt !== null &&
645
+ Buffer.byteLength(candidate.safe_excerpt.text, "utf8") > value.safeContextMaxUtf8Bytes) {
646
+ return failure(io, `${value.command} get`, exactSyntax(value), exactSyntax(value), {
647
+ class: "unsupported_state",
648
+ message: "current candidate safe context exceeds its validated bound",
649
+ recovery: "Repair the private candidate projection before retrying; no projection bytes were changed.",
650
+ });
651
+ }
652
+ const response = {
653
+ schemaVersion: value.schemaVersion,
654
+ command: `${value.command} get`,
655
+ status: "ok",
656
+ generation: projection.generation,
657
+ policy_version: projection.policy_version,
658
+ candidate_projection_sha256: projection.projection_sha256,
659
+ entry: {
660
+ candidate_id: candidate.capsule.candidate_id,
661
+ candidate_revision: candidate.capsule.candidate_revision,
662
+ capsule_sha256: candidate.capsule.capsule_sha256,
663
+ term: candidate.capsule.term,
664
+ meaning: candidate.capsule.meaning,
665
+ scope: candidate.capsule.scope,
666
+ provenance_kind: candidate.capsule.provenance_kind,
667
+ source_family: candidate.source_family,
668
+ evidence_complete: candidate.capsule.evidence_complete,
669
+ evidence_set_sha256: candidate.capsule.evidence_set_sha256,
670
+ occurrence_count: occurrences.length,
671
+ occurrences,
672
+ safe_context: candidate.safe_excerpt === null ? null : { ...candidate.safe_excerpt },
673
+ },
674
+ source: {
675
+ kind: "user_local_candidate_projection",
676
+ owner: projection.owner,
677
+ },
678
+ source_contract: {
679
+ authority: "references/artifacts/glossary-entry-contract.yaml",
680
+ bindings: [...value.exactBindings],
681
+ max_serialized_utf8_bytes: value.exactMaxSerializedUtf8Bytes,
682
+ safe_context_view: {
683
+ authority: value.safeContextViewAuthority,
684
+ retention_days: value.safeContextRetentionDays,
685
+ expiry: value.safeContextViewExpiry,
686
+ mutation: value.safeContextViewMutation,
687
+ },
688
+ },
689
+ };
690
+ if (serializedBytes(response) > value.exactMaxSerializedUtf8Bytes) {
691
+ return failure(io, `${value.command} get`, exactSyntax(value), exactSyntax(value), {
692
+ class: "output_bound_exceeded",
693
+ message: `candidate exact-read response exceeds its ${value.exactMaxSerializedUtf8Bytes}-byte bound`,
694
+ recovery: "Repair the private candidate projection before retrying; no partial candidate data was returned.",
695
+ });
696
+ }
697
+ emitStructured(response, "json", io.out ?? ((text) => process.stdout.write(text)));
698
+ return 0;
699
+ }
700
+ /** Read one current user-local candidate projection without prompting or mutating it. */
701
+ export function runPersonalGlossaryCandidateReadsCommand(argv, io) {
702
+ let value;
703
+ try {
704
+ value = contract();
705
+ }
706
+ catch {
707
+ return failure(io, "agentera report personal-glossary-candidates", "agentera report personal-glossary-candidates {list,get} --format json", "agentera report personal-glossary-candidates list --limit 20 --format json", {
708
+ class: "unsupported_state",
709
+ message: "personal glossary candidate retrieval contract is unavailable",
710
+ recovery: "Restore the bundled glossary authority, then retry; no projection bytes were changed.",
711
+ });
712
+ }
713
+ const operation = argv[0];
714
+ if (operation !== "list" && operation !== "get") {
715
+ return invalid(io, {
716
+ class: operation ? "unsupported_target" : "missing_argument",
717
+ message: operation
718
+ ? `unsupported personal glossary candidate operation: ${operation}`
719
+ : "candidate operation is required",
720
+ valid_values: ["list", "get"],
721
+ syntax: `${value.command} {list,get} --format json`,
722
+ example: `${value.command} list --limit ${value.defaultLimit} --format json`,
723
+ recovery: "Choose list or get and retry; no projection bytes were changed.",
724
+ });
725
+ }
726
+ if (operation === "list") {
727
+ const parsed = parseList(argv.slice(1), value);
728
+ if ("class" in parsed) {
729
+ return invalid(io, {
730
+ ...parsed,
731
+ recovery: "Correct the bounded list request and retry; no projection bytes were changed.",
732
+ });
733
+ }
734
+ return listCandidates(io, parsed, value);
735
+ }
736
+ const parsed = parseExact(argv.slice(1), value);
737
+ if ("class" in parsed) {
738
+ return invalid(io, {
739
+ ...parsed,
740
+ recovery: "Correct the exact candidate binding and retry; no projection bytes were changed.",
741
+ });
742
+ }
743
+ return exactCandidate(io, parsed, value);
744
+ }