@unbrained/pm-cli 2026.9.2 → 2026.9.3

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 (123) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +17 -0
  3. package/dist/cli/after-command-context-usage.js +16 -3
  4. package/dist/cli-bundle/bundle-manifest.json +154 -154
  5. package/dist/cli-bundle/chunks/{chunk-T6H5BZCP.js → chunk-34HASX3S.js} +2 -2
  6. package/dist/cli-bundle/chunks/chunk-AY324UJ5.js +9 -0
  7. package/dist/cli-bundle/chunks/{chunk-TAG4VC22.js → chunk-DKGP3QLE.js} +27 -27
  8. package/dist/cli-bundle/chunks/chunk-E5RBBCXO.js +230 -0
  9. package/dist/cli-bundle/chunks/{chunk-DTGWEL23.js → chunk-EDPIS7EB.js} +2 -2
  10. package/dist/cli-bundle/chunks/{chunk-X4RCGLTX.js → chunk-IEHHE6GA.js} +5 -5
  11. package/dist/cli-bundle/chunks/{chunk-UDW64J2W.js → chunk-L7B7343B.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-FZ4Z2J4F.js → chunk-LENC656S.js} +41 -41
  13. package/dist/cli-bundle/chunks/{chunk-ZLJIFSOE.js → chunk-LISESA7W.js} +33 -33
  14. package/dist/cli-bundle/chunks/{chunk-3WICXLVT.js → chunk-UPPM2FOZ.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-Y7VOMSZT.js → chunk-Y4PJ6HPK.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-list-query-XYNX4ASQ.js → register-list-query-W2ZTOMKP.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-mutation-WFQ7MJTX.js → register-mutation-RVND5NCG.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-operations-JVTI7JPP.js → register-operations-TTROIVC4.js} +2 -2
  19. package/dist/cli-bundle/chunks/{register-setup-KVNC5KPL.js → register-setup-D3ZFHY3L.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +176 -0
  21. package/dist/cli-bundle/focused-chunks/{chunk-SVXJYD6G.js → chunk-4HJFY4MB.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-4RRHC3S4.js +6 -0
  23. package/dist/cli-bundle/focused-chunks/chunk-4WR46RMG.js +9 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-5RFXUPN5.js +3 -0
  25. package/dist/cli-bundle/focused-chunks/chunk-BWKHEIVF.js +2 -0
  26. package/dist/cli-bundle/focused-chunks/{chunk-4O4CJAR6.js → chunk-D7KXFBXV.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/{chunk-MKHG3T76.js → chunk-GHCZ3YFR.js} +3 -3
  28. package/dist/cli-bundle/focused-chunks/{chunk-KB5OFSFD.js → chunk-GT7MXDLW.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-KZ2NB3ET.js +2 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-M5M6SVHS.js +3 -0
  32. package/dist/cli-bundle/focused-chunks/{chunk-BEVHCEWG.js → chunk-OL5EH6I3.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/{chunk-MEASX544.js → chunk-Q45NHPF2.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/{chunk-O67VDLOT.js → chunk-QKHCSUGJ.js} +42 -42
  35. package/dist/cli-bundle/focused-chunks/chunk-S27XMBIN.js +8 -0
  36. package/dist/cli-bundle/focused-chunks/chunk-TXE4TSHM.js +12 -0
  37. package/dist/cli-bundle/focused-chunks/chunk-UCLO3XDU.js +2 -0
  38. package/dist/cli-bundle/main.js +15 -15
  39. package/dist/cli-bundle/sdk-authoring.js +1 -1
  40. package/dist/cli-bundle/sdk-contracts.js +1 -1
  41. package/dist/cli-bundle/sdk-core.js +28 -28
  42. package/dist/cli-bundle/sdk-governance.js +1 -1
  43. package/dist/cli-bundle/sdk-graph.js +1 -1
  44. package/dist/cli-bundle/sdk-merge.js +35 -33
  45. package/dist/cli-bundle/sdk-query.js +1 -1
  46. package/dist/cli-bundle/sdk-runtime.js +1 -1
  47. package/dist/cli-bundle/sdk-testing.js +1 -1
  48. package/dist/cli-bundle/sdk.js +9 -9
  49. package/dist/core/history/event-classification.d.ts +21 -0
  50. package/dist/core/history/event-classification.js +109 -0
  51. package/dist/core/history/event-index.d.ts +14 -3
  52. package/dist/core/history/event-index.js +543 -88
  53. package/dist/core/history/history.js +47 -18
  54. package/dist/core/history/workspace-history.js +7 -6
  55. package/dist/core/lock/lock.d.ts +22 -2
  56. package/dist/core/lock/lock.js +121 -19
  57. package/dist/core/output/output.js +31 -7
  58. package/dist/core/shared/time.d.ts +2 -0
  59. package/dist/core/shared/time.js +8 -2
  60. package/dist/core/store/item-metadata-query-index.js +5 -5
  61. package/dist/sdk/context/recency.d.ts +9 -0
  62. package/dist/sdk/context/recency.js +34 -0
  63. package/dist/sdk/context/usage-egress.d.ts +9 -0
  64. package/dist/sdk/context/usage-egress.js +30 -0
  65. package/dist/sdk/context-intent-contracts.js +7 -3
  66. package/dist/sdk/context-relevance.d.ts +23 -0
  67. package/dist/sdk/context-relevance.js +94 -23
  68. package/dist/sdk/context-signal-store.d.ts +7 -3
  69. package/dist/sdk/context-signal-store.js +155 -42
  70. package/dist/sdk/context-usage.d.ts +54 -0
  71. package/dist/sdk/context-usage.js +245 -16
  72. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -14
  73. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
  74. package/dist/sdk/index.d.ts +2 -0
  75. package/dist/sdk/index.js +4 -2
  76. package/dist/sdk/init.d.ts +2 -0
  77. package/dist/sdk/init.js +18 -9
  78. package/dist/sdk/query/context.d.ts +3 -1
  79. package/dist/sdk/query/context.js +21 -9
  80. package/dist/sdk/query/next.js +18 -9
  81. package/dist/sdk/runtime-input.d.ts +2 -0
  82. package/dist/sdk/runtime-input.js +25 -2
  83. package/dist/sdk/runtime.d.ts +1 -0
  84. package/dist/sdk/runtime.js +17 -26
  85. package/dist/types.d.ts +2 -0
  86. package/dist/types.js +2 -2
  87. package/docs/CONTEXT_RELEVANCE.md +44 -1
  88. package/docs/PERFORMANCE.md +6 -3
  89. package/docs/READ_OUTPUT_CONTRACTS.md +9 -1
  90. package/docs/SDK.md +11 -8
  91. package/docs/SDK_CONTEXT.md +7 -0
  92. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -3
  93. package/marketplace.json +2 -2
  94. package/package.json +4 -4
  95. package/packages/pm-beads/package.json +1 -1
  96. package/packages/pm-calendar/package.json +1 -1
  97. package/packages/pm-command-kit/package.json +1 -1
  98. package/packages/pm-digital-twin/package.json +1 -1
  99. package/packages/pm-governance-audit/package.json +1 -1
  100. package/packages/pm-guide-shell/package.json +1 -1
  101. package/packages/pm-kanban/package.json +1 -1
  102. package/packages/pm-lifecycle-hooks/package.json +1 -1
  103. package/packages/pm-linked-test-adapters/package.json +1 -1
  104. package/packages/pm-search-advanced/package.json +1 -1
  105. package/packages/pm-templates/package.json +1 -1
  106. package/packages/pm-todos/package.json +1 -1
  107. package/packages/pm-vcs/package.json +1 -1
  108. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  109. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  110. package/sdk/public-surface.json +241 -24
  111. package/dist/cli-bundle/chunks/chunk-GOACULJW.js +0 -13
  112. package/dist/cli-bundle/chunks/chunk-NOPL37X4.js +0 -205
  113. package/dist/cli-bundle/focused-chunks/chunk-242AXYB2.js +0 -2
  114. package/dist/cli-bundle/focused-chunks/chunk-2FEZ5SKM.js +0 -3
  115. package/dist/cli-bundle/focused-chunks/chunk-6GCRSLPG.js +0 -2
  116. package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +0 -16
  117. package/dist/cli-bundle/focused-chunks/chunk-JEFKLWMM.js +0 -2
  118. package/dist/cli-bundle/focused-chunks/chunk-PF3E6Q36.js +0 -8
  119. package/dist/cli-bundle/focused-chunks/chunk-QDFJIRKN.js +0 -4
  120. package/dist/cli-bundle/focused-chunks/chunk-RMRCQPWU.js +0 -2
  121. package/dist/cli-bundle/focused-chunks/chunk-S4U5CN5Q.js +0 -2
  122. package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +0 -158
  123. package/dist/cli-bundle/focused-chunks/chunk-Z2USIBR2.js +0 -5
@@ -6,17 +6,19 @@
6
6
  * history, while stale, absent, or corrupt snapshots rebuild transparently.
7
7
  */
8
8
 
9
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3d0a558b-6a01-5bca-a2f7-21766be6d67c")}catch(e){}}();
9
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9b112b71-e0b8-5502-89f6-b4da6eb85f6b")}catch(e){}}();
10
10
  import { isFileAbsentError } from "../core/fs/fs-utils.js";
11
11
  import fs from "node:fs/promises";
12
12
  import path from "node:path";
13
13
  import { createHash, randomUUID } from "node:crypto";
14
14
  import { buildItemContextRelevanceCandidates, } from "./context-relevance.js";
15
15
  import { readItemMetadataDerivedIndexState } from "./item-metadata-index.js";
16
+ import { readLatestSubstantiveHistoryEvents } from "../core/history/event-index.js";
17
+ import { canonicalizeContextRecencyCoordinate } from "./context/recency.js";
16
18
  /** Current serialized feature-store envelope version. */
17
- export const CONTEXT_SIGNAL_STORE_FORMAT_VERSION = 1;
19
+ export const CONTEXT_SIGNAL_STORE_FORMAT_VERSION = 3;
18
20
  /** Current canonical signal-vector version. */
19
- export const CONTEXT_SIGNAL_SET_VERSION = 1;
21
+ export const CONTEXT_SIGNAL_SET_VERSION = 2;
20
22
  const STORED_CONTEXT_SIGNAL_NAMES = [
21
23
  "recency",
22
24
  "activity_density",
@@ -25,6 +27,11 @@ const STORED_CONTEXT_SIGNAL_NAMES = [
25
27
  "risk_pressure",
26
28
  "knowledge_density",
27
29
  ];
30
+ const CONTEXT_RECENCY_SOURCES = new Set([
31
+ "substantive_history",
32
+ "release_cohort",
33
+ "created_at",
34
+ ]);
28
35
  /** Canonical actionable contracts shared by CLI, SDK, MCP, and package hosts. */
29
36
  export const CONTEXT_SIGNAL_STORE_WARNING_DETAILS = {
30
37
  context_signal_store_invalid: {
@@ -50,7 +57,10 @@ function isRecord(value) {
50
57
  return typeof value === "object" && value !== null && !Array.isArray(value);
51
58
  }
52
59
  function isNormalizedSignal(value) {
53
- return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
60
+ return (typeof value === "number" &&
61
+ Number.isFinite(value) &&
62
+ value >= 0 &&
63
+ value <= 1);
54
64
  }
55
65
  function compactSignals(signals) {
56
66
  const compact = {};
@@ -88,7 +98,10 @@ function deriveGraphProximity(items) {
88
98
  }
89
99
  }
90
100
  const maximum = Math.max(0, ...degree.values());
91
- return Object.fromEntries(items.map((item) => [item.id, maximum === 0 ? 0 : degree.get(item.id) / maximum]));
101
+ return Object.fromEntries(items.map((item) => [
102
+ item.id,
103
+ maximum === 0 ? 0 : degree.get(item.id) / maximum,
104
+ ]));
92
105
  }
93
106
  function stableSnapshotOptions(items, options) {
94
107
  return {
@@ -101,19 +114,77 @@ function stableSnapshotOptions(items, options) {
101
114
  graphProximity: options.graphProximity ?? deriveGraphProximity(items),
102
115
  };
103
116
  }
117
+ function recencyEvidenceFingerprint(candidates) {
118
+ const hash = createHash("sha256");
119
+ for (const { id, signal_provenance: { recency: evidence }, } of [...candidates].sort((left, right) => left.id.localeCompare(right.id))) {
120
+ hash.update(JSON.stringify([
121
+ id,
122
+ evidence.source,
123
+ evidence.coordinate,
124
+ evidence.history_op ?? null,
125
+ evidence.event_class ?? null,
126
+ ]));
127
+ }
128
+ return `sha256:${hash.digest("hex")}`;
129
+ }
104
130
  function parseSnapshotItem(value) {
105
- if (!isRecord(value) || typeof value.id !== "string" || !isRecord(value.signals)) {
131
+ if (!isRecord(value))
132
+ return null;
133
+ const signalProvenance = value.signal_provenance;
134
+ if (![
135
+ typeof value.id === "string",
136
+ isRecord(value.signals),
137
+ isRecord(signalProvenance),
138
+ ].every(Boolean))
139
+ return null;
140
+ const recency = signalProvenance.recency;
141
+ if (!isRecord(recency))
142
+ return null;
143
+ const coordinate = canonicalizeContextRecencyCoordinate(recency.coordinate);
144
+ if (coordinate === null)
106
145
  return null;
107
- }
108
- const signals = {};
109
146
  const supportedSignals = new Set(STORED_CONTEXT_SIGNAL_NAMES);
110
- for (const [name, signal] of Object.entries(value.signals)) {
111
- if (!supportedSignals.has(name) || !isNormalizedSignal(signal)) {
112
- return null;
113
- }
114
- signals[name] = signal;
147
+ const signalEntries = Object.entries(value.signals);
148
+ if (!signalEntries.every(([name, signal]) => supportedSignals.has(name) && isNormalizedSignal(signal))) {
149
+ return null;
150
+ }
151
+ if (![
152
+ typeof recency.source === "string" &&
153
+ CONTEXT_RECENCY_SOURCES.has(recency.source),
154
+ ["undefined", "string"].includes(typeof recency.history_op),
155
+ [undefined, "substantive", "maintenance"].includes(recency.event_class),
156
+ recency.source === "substantive_history"
157
+ ? recency.event_class === undefined ||
158
+ recency.event_class === "substantive"
159
+ : recency.history_op === undefined && recency.event_class === undefined,
160
+ ].every(Boolean)) {
161
+ return null;
162
+ }
163
+ return {
164
+ id: value.id,
165
+ signals: Object.fromEntries(signalEntries),
166
+ signal_provenance: {
167
+ recency: {
168
+ source: recency.source,
169
+ coordinate,
170
+ history_op: recency.history_op,
171
+ event_class: recency.event_class,
172
+ },
173
+ },
174
+ };
175
+ }
176
+ function parseSnapshotItems(values, expectedRecencyEvidenceFingerprint) {
177
+ const items = Array.from(values, parseSnapshotItem);
178
+ if (items.some((item) => item === null))
179
+ return null;
180
+ const validItems = items;
181
+ if (new Set(validItems.map((item) => item.id)).size !== validItems.length ||
182
+ validItems.some((item) => item.id.trim().length === 0) ||
183
+ expectedRecencyEvidenceFingerprint !==
184
+ recencyEvidenceFingerprint(validItems)) {
185
+ return null;
115
186
  }
116
- return { id: value.id, signals: signals };
187
+ return validItems.sort((left, right) => left.id.localeCompare(right.id));
117
188
  }
118
189
  /** Validate an untrusted serialized snapshot without accepting partial envelopes. */
119
190
  export function parseContextSignalSnapshot(value) {
@@ -122,56 +193,68 @@ export function parseContextSignalSnapshot(value) {
122
193
  value.signal_set_version !== CONTEXT_SIGNAL_SET_VERSION ||
123
194
  typeof value.source_cursor !== "string" ||
124
195
  value.source_cursor.trim().length === 0 ||
196
+ typeof value.recency_evidence_fingerprint !== "string" ||
197
+ !/^sha256:[a-f0-9]{64}$/u.test(value.recency_evidence_fingerprint) ||
125
198
  typeof value.generated_at !== "string" ||
126
199
  !Number.isFinite(Date.parse(value.generated_at)) ||
127
200
  (value.source !== "derived_index" && value.source !== "scan_fallback") ||
128
201
  !Array.isArray(value.items)) {
129
202
  return null;
130
203
  }
131
- const items = value.items.map(parseSnapshotItem);
132
- if (items.some((item) => item === null)) {
204
+ const items = parseSnapshotItems(value.items, value.recency_evidence_fingerprint);
205
+ if (items === null)
133
206
  return null;
134
- }
135
- const validItems = items;
136
- if (new Set(validItems.map((item) => item.id)).size !== validItems.length ||
137
- validItems.some((item) => item.id.trim().length === 0)) {
138
- return null;
139
- }
140
207
  return {
141
208
  format_version: CONTEXT_SIGNAL_STORE_FORMAT_VERSION,
142
209
  signal_set_version: CONTEXT_SIGNAL_SET_VERSION,
143
210
  source_cursor: value.source_cursor,
211
+ recency_evidence_fingerprint: value.recency_evidence_fingerprint,
144
212
  generated_at: value.generated_at,
145
213
  source: value.source,
146
- items: validItems.sort((left, right) => left.id.localeCompare(right.id)),
214
+ items,
147
215
  };
148
216
  }
149
- /** Fold authoritative items into a deterministic, immutable signal snapshot. */
150
- export function buildContextSignalSnapshot(items, options) {
151
- if (typeof options.sourceCursor !== "string" || options.sourceCursor.trim().length === 0) {
217
+ function validateSnapshotOptions(options) {
218
+ if (typeof options.sourceCursor !== "string" ||
219
+ options.sourceCursor.trim().length === 0) {
152
220
  throw new TypeError("Context signal source cursor must be non-empty");
153
221
  }
154
222
  if (!Number.isFinite(Date.parse(options.now))) {
155
223
  throw new TypeError("Context signal snapshot clock must be a valid timestamp");
156
224
  }
157
- if (options.source !== "derived_index" && options.source !== "scan_fallback") {
225
+ if (options.source !== "derived_index" &&
226
+ options.source !== "scan_fallback") {
158
227
  throw new TypeError("Context signal snapshot source must be derived_index or scan_fallback");
159
228
  }
160
- const rows = buildItemContextRelevanceCandidates(items, stableSnapshotOptions(items, options))
161
- .map(({ id, signals }) => ({
229
+ }
230
+ function snapshotFromCandidates(candidates, options, fingerprint = recencyEvidenceFingerprint(candidates)) {
231
+ const candidateIds = candidates.map(({ id }) => id);
232
+ if (new Set(candidateIds).size !== candidateIds.length ||
233
+ candidateIds.some((id) => id.trim().length === 0)) {
234
+ throw new TypeError("Context signal snapshot item IDs must be unique and non-empty");
235
+ }
236
+ const rows = candidates
237
+ .map(({ id, signals, signal_provenance }) => ({
162
238
  id,
163
239
  signals: compactSignals(signals),
240
+ signal_provenance,
164
241
  }))
165
242
  .sort((left, right) => left.id.localeCompare(right.id));
166
243
  return Object.freeze({
167
244
  format_version: CONTEXT_SIGNAL_STORE_FORMAT_VERSION,
168
245
  signal_set_version: CONTEXT_SIGNAL_SET_VERSION,
169
246
  source_cursor: options.sourceCursor,
247
+ recency_evidence_fingerprint: fingerprint,
170
248
  generated_at: options.now,
171
249
  source: options.source,
172
250
  items: Object.freeze(rows.map((row) => Object.freeze(row))),
173
251
  });
174
252
  }
253
+ /** Fold authoritative items into a deterministic, immutable signal snapshot. */
254
+ export function buildContextSignalSnapshot(items, options) {
255
+ validateSnapshotOptions(options);
256
+ return snapshotFromCandidates(buildItemContextRelevanceCandidates(items, stableSnapshotOptions(items, options)), options);
257
+ }
175
258
  /** Filesystem adapter using same-directory atomic replacement. */
176
259
  export class JsonFileContextSignalStoreAdapter {
177
260
  filePath;
@@ -200,7 +283,10 @@ export class JsonFileContextSignalStoreAdapter {
200
283
  await fs.mkdir(directory, { recursive: true });
201
284
  const temporaryPath = path.join(directory, `.${path.basename(this.filePath)}.${randomUUID()}.tmp`);
202
285
  try {
203
- await fs.writeFile(temporaryPath, `${JSON.stringify(snapshot)}\n`, { encoding: "utf8", flag: "wx" });
286
+ await fs.writeFile(temporaryPath, `${JSON.stringify(snapshot)}\n`, {
287
+ encoding: "utf8",
288
+ flag: "wx",
289
+ });
204
290
  await fs.rename(temporaryPath, this.filePath);
205
291
  }
206
292
  finally {
@@ -222,6 +308,7 @@ export class ContextSignalStore {
222
308
  }
223
309
  /** Read matching signals or rebuild them from authoritative items. */
224
310
  async readOrRebuild(items, options) {
311
+ validateSnapshotOptions(options);
225
312
  const warnings = [];
226
313
  let snapshot = null;
227
314
  try {
@@ -234,10 +321,15 @@ export class ContextSignalStore {
234
321
  catch {
235
322
  warnings.push("context_signal_store_invalid");
236
323
  }
237
- const authoritativeIds = items.map((item) => item.id).sort((left, right) => left.localeCompare(right));
324
+ const authoritativeIds = items
325
+ .map((item) => item.id)
326
+ .sort((left, right) => left.localeCompare(right));
238
327
  const snapshotIds = snapshot?.items.map((item) => item.id) ?? [];
328
+ const authoritativeCandidates = buildItemContextRelevanceCandidates(items, stableSnapshotOptions(items, options));
329
+ const authoritativeFingerprint = recencyEvidenceFingerprint(authoritativeCandidates);
239
330
  const fresh = snapshot !== null &&
240
331
  snapshot.source_cursor === options.sourceCursor &&
332
+ snapshot.recency_evidence_fingerprint === authoritativeFingerprint &&
241
333
  snapshot.source === options.source &&
242
334
  snapshotIds.length === authoritativeIds.length &&
243
335
  snapshotIds.every((id, index) => id === authoritativeIds[index]);
@@ -249,7 +341,7 @@ export class ContextSignalStore {
249
341
  if (snapshot !== null) {
250
342
  warnings.push("context_signal_store_stale");
251
343
  }
252
- resolvedSnapshot = buildContextSignalSnapshot(items, options);
344
+ resolvedSnapshot = snapshotFromCandidates(authoritativeCandidates, options, authoritativeFingerprint);
253
345
  try {
254
346
  await this.adapter.write(resolvedSnapshot);
255
347
  }
@@ -257,13 +349,16 @@ export class ContextSignalStore {
257
349
  warnings.push("context_signal_store_write_failed");
258
350
  }
259
351
  }
260
- const signalsById = new Map(resolvedSnapshot.items.map((item) => [item.id, item.signals]));
261
- const dynamicCandidates = buildItemContextRelevanceCandidates(items, options);
352
+ const snapshotItemsById = new Map(resolvedSnapshot.items.map((item) => [item.id, item]));
262
353
  return {
263
354
  snapshot: resolvedSnapshot,
264
- candidates: dynamicCandidates.map((candidate) => ({
355
+ candidates: authoritativeCandidates.map((candidate) => ({
265
356
  ...candidate,
266
- signals: { ...candidate.signals, ...signalsById.get(candidate.id) },
357
+ signals: {
358
+ ...candidate.signals,
359
+ ...snapshotItemsById.get(candidate.id)?.signals,
360
+ },
361
+ signal_provenance: snapshotItemsById.get(candidate.id).signal_provenance,
267
362
  })),
268
363
  cache_status: fresh ? "fresh" : "rebuilt",
269
364
  warnings,
@@ -304,11 +399,29 @@ export async function readWorkspaceContextSignals(items, options) {
304
399
  const indexState = hasSourceCursor
305
400
  ? null
306
401
  : await readItemMetadataDerivedIndexState(options.pmRoot);
307
- const sourceCursor = options.sourceCursor ?? indexState?.source_cursor ?? fallbackWorkspaceCursor(items);
308
- const source = options.source ??
309
- (indexState === null ? "scan_fallback" : "derived_index");
310
- const store = new ContextSignalStore(new JsonFileContextSignalStoreAdapter(path.join(options.pmRoot, "runtime", options.storeKey ? `context-signals-${options.storeKey}.json` : "context-signals.json")));
311
- return store.readOrRebuild(items, { ...options, sourceCursor, source });
402
+ const sourceCursor = options.sourceCursor ??
403
+ indexState?.source_cursor ??
404
+ fallbackWorkspaceCursor(items);
405
+ const source = options.source ?? (indexState === null ? "scan_fallback" : "derived_index");
406
+ const store = new ContextSignalStore(new JsonFileContextSignalStoreAdapter(path.join(options.pmRoot, "runtime", options.storeKey
407
+ ? `context-signals-${options.storeKey}.json`
408
+ : "context-signals.json")));
409
+ const recencyEvidence = options.recencyEvidence ??
410
+ Object.fromEntries(Object.entries(await readLatestSubstantiveHistoryEvents(options.pmRoot, items.map((item) => item.id))).map(([id, event]) => [
411
+ id,
412
+ {
413
+ source: "substantive_history",
414
+ coordinate: event.entry.ts,
415
+ history_op: event.entry.op,
416
+ event_class: "substantive",
417
+ },
418
+ ]));
419
+ return store.readOrRebuild(items, {
420
+ ...options,
421
+ recencyEvidence,
422
+ sourceCursor,
423
+ source,
424
+ });
312
425
  }
313
426
  //# sourceMappingURL=context-signal-store.js.map
314
- //# debugId=3d0a558b-6a01-5bca-a2f7-21766be6d67c
427
+ //# debugId=9b112b71-e0b8-5502-89f6-b4da6eb85f6b
@@ -8,14 +8,40 @@ export interface ContextUsageServingRow {
8
8
  /** Whether the packer included the item. */
9
9
  included: boolean;
10
10
  }
11
+ /** Correlation receipt joining a pre-egress serve to its final delivery. */
12
+ export interface ContextUsageServingReceipt {
13
+ /** Versioned immutable serve identifier. */
14
+ serve_id: string;
15
+ /** Caller whose feedback model owns the event. */
16
+ author: string;
17
+ /** Read surface that assembled the candidates. */
18
+ surface: ContextRelevanceSurface;
19
+ /** Propensity-complete ranked rows assembled before egress. */
20
+ rows: readonly ContextUsageServingRow[];
21
+ }
11
22
  /** Append-only serving or subsequent-touch event. */
12
23
  export type ContextUsageEvent = {
13
24
  kind: "serve";
25
+ schema_version?: 2;
26
+ serve_id?: string;
14
27
  at: string;
15
28
  author: string;
16
29
  surface: ContextRelevanceSurface;
17
30
  profile: string;
18
31
  rows: ContextUsageServingRow[];
32
+ result_omitted?: boolean;
33
+ packed_item_ids?: string[];
34
+ /** Legacy pre-egress field ignored by the v2 affinity fold. */
35
+ delivered_item_ids?: string[];
36
+ } | {
37
+ kind: "delivery";
38
+ schema_version: 2;
39
+ serve_id: string;
40
+ at: string;
41
+ author: string;
42
+ surface: ContextRelevanceSurface;
43
+ result_omitted: boolean;
44
+ delivered_item_ids: string[];
19
45
  } | {
20
46
  kind: "touch";
21
47
  at: string;
@@ -44,7 +70,26 @@ export interface ContextUsageAffinity {
44
70
  positive_judgments: number;
45
71
  /** Number of retained serving events inspected. */
46
72
  serving_events: number;
73
+ /** Legacy serving events ignored because their inclusion cannot be trusted. */
74
+ untrusted_serving_events: number;
47
75
  }
76
+ /** Attach a JSON-invisible serving receipt to a result for later egress. */
77
+ export declare function attachContextUsageServingReceipt(result: unknown, receipt: ContextUsageServingReceipt | null): void;
78
+ /** Copy an attached serving receipt across a result projection boundary. */
79
+ export declare function propagateContextUsageServingReceipt(source: unknown, target: unknown): void;
80
+ /**
81
+ * Collect every item identifier visible in a final context or next result.
82
+ * The traversal is shape-aware so summary counters and unrelated identifier
83
+ * fields cannot enter the privacy-minimal usage ledger.
84
+ */
85
+ export declare function collectContextUsageDeliveredItemIds(result: unknown, surface: ContextRelevanceSurface): string[];
86
+ /** Resolve and append one post-egress decision carried by a context result. */
87
+ export declare function finalizeContextUsageDelivery(options: {
88
+ /** Tracker root owning the derived ledger. */
89
+ pmRoot: string;
90
+ /** Final projected response returned or rendered to the caller. */
91
+ result: unknown;
92
+ }): Promise<boolean>;
48
93
  /** Named validation failure emitted by context-usage SDK inputs. */
49
94
  export declare class ContextUsageValidationError extends TypeError {
50
95
  /** Stable machine-readable error code for package and CLI hosts. */
@@ -58,6 +103,15 @@ export declare function recordContextUsageServing(options: ContextUsageLedgerOpt
58
103
  surface: ContextRelevanceSurface;
59
104
  profile: string;
60
105
  rows: ContextUsageServingRow[];
106
+ }): Promise<ContextUsageServingReceipt | null>;
107
+ /** Append the exact post-egress delivery decision for one serving receipt. */
108
+ export declare function recordContextUsageDelivery(options: ContextUsageLedgerOptions & {
109
+ /** Correlation receipt returned by {@link recordContextUsageServing}. */
110
+ receipt: ContextUsageServingReceipt | null;
111
+ /** Item ids present in the final response payload. */
112
+ deliveredItemIds: readonly string[];
113
+ /** Whether the result was suppressed by the final output budget. */
114
+ resultOmitted: boolean;
61
115
  }): Promise<void>;
62
116
  /** Records one subsequent item read or mutation outcome. */
63
117
  export declare function recordContextUsageTouch(options: ContextUsageLedgerOptions & {