@unbrained/pm-cli 2026.9.2 → 2026.9.4

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +44 -0
  3. package/dist/cli/after-command-context-usage.js +16 -3
  4. package/dist/cli-bundle/bundle-manifest.json +151 -151
  5. package/dist/cli-bundle/chunks/chunk-6XSKPNCF.js +230 -0
  6. package/dist/cli-bundle/chunks/chunk-A24WAVE6.js +8 -0
  7. package/dist/cli-bundle/chunks/{chunk-TAG4VC22.js → chunk-HI6BHPCU.js} +27 -27
  8. package/dist/cli-bundle/chunks/{chunk-Y7VOMSZT.js → chunk-I2AS367G.js} +2 -2
  9. package/dist/cli-bundle/chunks/{chunk-X4RCGLTX.js → chunk-OGBVU67G.js} +5 -5
  10. package/dist/cli-bundle/chunks/{chunk-DTGWEL23.js → chunk-RI4XNOYS.js} +2 -2
  11. package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +38 -0
  12. package/dist/cli-bundle/chunks/{chunk-FZ4Z2J4F.js → chunk-V72UV7QS.js} +76 -79
  13. package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +9 -0
  14. package/dist/cli-bundle/chunks/{chunk-T6H5BZCP.js → chunk-X6CMGV6L.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-3WICXLVT.js → chunk-YW4QK2G7.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-list-query-XYNX4ASQ.js → register-list-query-Z5YYSJJG.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-mutation-WFQ7MJTX.js → register-mutation-ELSP6I4I.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-operations-JVTI7JPP.js → register-operations-OH7R3ZT4.js} +2 -2
  19. package/dist/cli-bundle/chunks/{register-setup-KVNC5KPL.js → register-setup-E2AE72QG.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/{chunk-MEASX544.js → chunk-25UJBH4M.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-SVXJYD6G.js → chunk-3646BZ4W.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-4YRAVI43.js +12 -0
  23. package/dist/cli-bundle/focused-chunks/chunk-53IHPICX.js +3 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +176 -0
  25. package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +54 -0
  26. package/dist/cli-bundle/focused-chunks/{chunk-4O4CJAR6.js → chunk-6PWPQKDN.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/chunk-7N2TV7WZ.js +8 -0
  28. package/dist/cli-bundle/focused-chunks/chunk-AJR3OP2E.js +3 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-B3A6MJYE.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-BFDDJ2T2.js +2 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-BHUIRZWQ.js +9 -0
  32. package/dist/cli-bundle/focused-chunks/chunk-JADHHPY6.js +6 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-KB5OFSFD.js → chunk-MHKUIPJW.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/chunk-NBMJD6MK.js +2 -0
  35. package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +20 -0
  36. package/dist/cli-bundle/focused-chunks/{chunk-BEVHCEWG.js → chunk-P2GOAOQN.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-YARWJM2B.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 -31
  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 +43 -36
  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 +10 -10
  49. package/dist/core/diagnostics/remediation.js +4 -4
  50. package/dist/core/history/event-classification.d.ts +21 -0
  51. package/dist/core/history/event-classification.js +109 -0
  52. package/dist/core/history/event-index.d.ts +14 -3
  53. package/dist/core/history/event-index.js +543 -88
  54. package/dist/core/history/history.d.ts +37 -1
  55. package/dist/core/history/history.js +320 -71
  56. package/dist/core/history/replay.js +68 -34
  57. package/dist/core/history/workspace-history.js +7 -6
  58. package/dist/core/lock/lock.d.ts +32 -2
  59. package/dist/core/lock/lock.js +161 -20
  60. package/dist/core/output/output.js +31 -7
  61. package/dist/core/shared/author.d.ts +3 -1
  62. package/dist/core/shared/author.js +111 -23
  63. package/dist/core/shared/time.d.ts +2 -0
  64. package/dist/core/shared/time.js +8 -2
  65. package/dist/core/store/item-metadata-query-index.js +5 -5
  66. package/dist/sdk/context/recency.d.ts +9 -0
  67. package/dist/sdk/context/recency.js +34 -0
  68. package/dist/sdk/context/usage-egress.d.ts +9 -0
  69. package/dist/sdk/context/usage-egress.js +30 -0
  70. package/dist/sdk/context-intent-contracts.js +7 -3
  71. package/dist/sdk/context-relevance.d.ts +23 -0
  72. package/dist/sdk/context-relevance.js +94 -23
  73. package/dist/sdk/context-signal-store.d.ts +7 -3
  74. package/dist/sdk/context-signal-store.js +155 -42
  75. package/dist/sdk/context-usage.d.ts +54 -0
  76. package/dist/sdk/context-usage.js +245 -16
  77. package/dist/sdk/environment/agent-environment.d.ts +296 -0
  78. package/dist/sdk/environment/agent-environment.js +467 -0
  79. package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
  80. package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
  81. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
  82. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
  83. package/dist/sdk/governance/health.js +13 -44
  84. package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
  85. package/dist/sdk/governance/merge-receipt-history.js +207 -0
  86. package/dist/sdk/governance/provenance-health.js +9 -3
  87. package/dist/sdk/history-compact.js +14 -5
  88. package/dist/sdk/history-redact.js +58 -23
  89. package/dist/sdk/history-repair.js +16 -6
  90. package/dist/sdk/index.d.ts +4 -0
  91. package/dist/sdk/index.js +6 -2
  92. package/dist/sdk/init.d.ts +2 -0
  93. package/dist/sdk/init.js +18 -9
  94. package/dist/sdk/merge/driver.d.ts +3 -1
  95. package/dist/sdk/merge/driver.js +9 -9
  96. package/dist/sdk/merge/index.d.ts +1 -1
  97. package/dist/sdk/merge/index.js +3 -3
  98. package/dist/sdk/merge/receipts.d.ts +17 -6
  99. package/dist/sdk/merge/receipts.js +246 -75
  100. package/dist/sdk/merge/reconcile.d.ts +9 -7
  101. package/dist/sdk/merge/reconcile.js +175 -12
  102. package/dist/sdk/merge/three-way.d.ts +22 -2
  103. package/dist/sdk/merge/three-way.js +92 -25
  104. package/dist/sdk/query/context.d.ts +3 -1
  105. package/dist/sdk/query/context.js +21 -9
  106. package/dist/sdk/query/next.js +18 -9
  107. package/dist/sdk/runtime-input.d.ts +2 -0
  108. package/dist/sdk/runtime-input.js +25 -2
  109. package/dist/sdk/runtime-primitives.d.ts +2 -2
  110. package/dist/sdk/runtime-primitives.js +4 -4
  111. package/dist/sdk/runtime.d.ts +1 -0
  112. package/dist/sdk/runtime.js +17 -26
  113. package/dist/types.d.ts +27 -0
  114. package/dist/types.js +2 -2
  115. package/docs/AGENT_ENVIRONMENTS.md +47 -0
  116. package/docs/AGENT_PROVENANCE_ADR.md +17 -2
  117. package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
  118. package/docs/CONTEXT_RELEVANCE.md +44 -1
  119. package/docs/MERGE_SAFETY.md +119 -23
  120. package/docs/PERFORMANCE.md +6 -3
  121. package/docs/README.md +1 -0
  122. package/docs/READ_OUTPUT_CONTRACTS.md +9 -1
  123. package/docs/SDK.md +11 -8
  124. package/docs/SDK_CONTEXT.md +7 -0
  125. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
  126. package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
  127. package/docs/examples/sdk-agent-environment/README.md +14 -0
  128. package/docs/examples/sdk-agent-environment/package.json +8 -0
  129. package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
  130. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +5 -3
  131. package/marketplace.json +2 -2
  132. package/package.json +5 -5
  133. package/packages/pm-beads/package.json +1 -1
  134. package/packages/pm-calendar/package.json +1 -1
  135. package/packages/pm-command-kit/package.json +1 -1
  136. package/packages/pm-digital-twin/package.json +1 -1
  137. package/packages/pm-governance-audit/package.json +1 -1
  138. package/packages/pm-guide-shell/package.json +1 -1
  139. package/packages/pm-kanban/package.json +1 -1
  140. package/packages/pm-lifecycle-hooks/package.json +1 -1
  141. package/packages/pm-linked-test-adapters/package.json +1 -1
  142. package/packages/pm-search-advanced/package.json +1 -1
  143. package/packages/pm-templates/package.json +1 -1
  144. package/packages/pm-todos/package.json +1 -1
  145. package/packages/pm-vcs/package.json +1 -1
  146. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  147. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  148. package/sdk/public-surface.json +773 -46
  149. package/dist/cli-bundle/chunks/chunk-GOACULJW.js +0 -13
  150. package/dist/cli-bundle/chunks/chunk-NOPL37X4.js +0 -205
  151. package/dist/cli-bundle/chunks/chunk-UDW64J2W.js +0 -8
  152. package/dist/cli-bundle/chunks/chunk-ZLJIFSOE.js +0 -35
  153. package/dist/cli-bundle/focused-chunks/chunk-242AXYB2.js +0 -2
  154. package/dist/cli-bundle/focused-chunks/chunk-2FEZ5SKM.js +0 -3
  155. package/dist/cli-bundle/focused-chunks/chunk-6GCRSLPG.js +0 -2
  156. package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +0 -16
  157. package/dist/cli-bundle/focused-chunks/chunk-JEFKLWMM.js +0 -2
  158. package/dist/cli-bundle/focused-chunks/chunk-MKHG3T76.js +0 -20
  159. package/dist/cli-bundle/focused-chunks/chunk-O67VDLOT.js +0 -51
  160. package/dist/cli-bundle/focused-chunks/chunk-PF3E6Q36.js +0 -8
  161. package/dist/cli-bundle/focused-chunks/chunk-QDFJIRKN.js +0 -4
  162. package/dist/cli-bundle/focused-chunks/chunk-RMRCQPWU.js +0 -2
  163. package/dist/cli-bundle/focused-chunks/chunk-S4U5CN5Q.js +0 -2
  164. package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +0 -158
  165. package/dist/cli-bundle/focused-chunks/chunk-Z2USIBR2.js +0 -5
@@ -6,7 +6,7 @@
6
6
  * intents; it never enters item history and is safe to delete or rebuild.
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]="5f4ccd28-a001-54aa-8378-893025c69b96")}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]="997a09a2-cf0c-5384-b457-d9627d465b71")}catch(e){}}();
10
10
  import { randomUUID } from "node:crypto";
11
11
  import { appendFile, mkdir, readFile, rename, stat, writeFile, } from "node:fs/promises";
12
12
  import path from "node:path";
@@ -16,6 +16,154 @@ const DEFAULT_RETENTION_DAYS = 30;
16
16
  const DAY_MS = 86_400_000;
17
17
  const DEFAULT_COMPACTION_BYTES = 262_144;
18
18
  const CONTEXT_USAGE_LOCK_ID = "context-usage-ledger";
19
+ const CONTEXT_USAGE_SERVING_RECEIPT = Symbol.for("@unbrained/pm-cli/context-usage-serving-receipt");
20
+ function asObjectRecord(value) {
21
+ return typeof value === "object" && value !== null && !Array.isArray(value)
22
+ ? value
23
+ : null;
24
+ }
25
+ /** Attach a JSON-invisible serving receipt to a result for later egress. */
26
+ export function attachContextUsageServingReceipt(result, receipt) {
27
+ const record = asObjectRecord(result);
28
+ if (record !== null && receipt !== null) {
29
+ record[CONTEXT_USAGE_SERVING_RECEIPT] = receipt;
30
+ }
31
+ }
32
+ /** Copy an attached serving receipt across a result projection boundary. */
33
+ export function propagateContextUsageServingReceipt(source, target) {
34
+ const sourceRecord = asObjectRecord(source);
35
+ const targetRecord = asObjectRecord(target);
36
+ const receipt = sourceRecord?.[CONTEXT_USAGE_SERVING_RECEIPT];
37
+ if (targetRecord !== null && receipt !== undefined) {
38
+ targetRecord[CONTEXT_USAGE_SERVING_RECEIPT] = receipt;
39
+ }
40
+ }
41
+ function receiptFromResult(result) {
42
+ return (asObjectRecord(result)?.[CONTEXT_USAGE_SERVING_RECEIPT] ?? null);
43
+ }
44
+ function resultReceiptSaysOmitted(result) {
45
+ const record = asObjectRecord(result);
46
+ for (const key of ["read_output", "context_intent"]) {
47
+ const receipt = asObjectRecord(record?.[key]);
48
+ if (receipt?.result_omitted === true)
49
+ return true;
50
+ }
51
+ return false;
52
+ }
53
+ function addContextUsageId(ids, value) {
54
+ if (typeof value === "string" && value.trim().length > 0) {
55
+ ids.add(value.trim());
56
+ }
57
+ }
58
+ function addContextUsageDirectRows(ids, value) {
59
+ if (!Array.isArray(value))
60
+ return;
61
+ for (const row of value) {
62
+ addContextUsageId(ids, asObjectRecord(row)?.id);
63
+ }
64
+ }
65
+ function addContextUsageIdList(ids, value) {
66
+ if (!Array.isArray(value))
67
+ return;
68
+ for (const itemId of value)
69
+ addContextUsageId(ids, itemId);
70
+ }
71
+ function collectNextResultItemIds(record, ids) {
72
+ const rows = [
73
+ ...(asObjectRecord(record.recommended) === null
74
+ ? []
75
+ : [record.recommended]),
76
+ ...(Array.isArray(record.ready) ? record.ready : []),
77
+ ...(Array.isArray(record.decision_needed) ? record.decision_needed : []),
78
+ ...(Array.isArray(record.blocked) ? record.blocked : []),
79
+ ...(Array.isArray(record.held_by_others) ? record.held_by_others : []),
80
+ ];
81
+ for (const value of rows) {
82
+ const row = asObjectRecord(value);
83
+ addContextUsageId(ids, row?.id);
84
+ for (const blocker of Array.isArray(row?.blockers) ? row.blockers : []) {
85
+ addContextUsageId(ids, asObjectRecord(blocker)?.id);
86
+ }
87
+ for (const unblockedId of Array.isArray(row?.unblocks)
88
+ ? row.unblocks
89
+ : []) {
90
+ addContextUsageId(ids, unblockedId);
91
+ }
92
+ }
93
+ }
94
+ function collectContextResultItemIds(record, ids) {
95
+ for (const key of [
96
+ "high_level",
97
+ "low_level",
98
+ "blocked_fallback",
99
+ "recently_created",
100
+ "unparented",
101
+ "activity",
102
+ "progress",
103
+ "blockers",
104
+ "staleness",
105
+ ]) {
106
+ addContextUsageDirectRows(ids, record[key]);
107
+ }
108
+ for (const value of Array.isArray(record.blockers)
109
+ ? record.blockers
110
+ : []) {
111
+ addContextUsageId(ids, asObjectRecord(value)?.blocked_by);
112
+ }
113
+ collectContextNestedItemIds(record, ids);
114
+ }
115
+ function collectContextNestedItemIds(record, ids) {
116
+ for (const value of Array.isArray(record.hierarchy)
117
+ ? record.hierarchy
118
+ : []) {
119
+ const node = asObjectRecord(value);
120
+ addContextUsageId(ids, node?.id);
121
+ addContextUsageDirectRows(ids, node?.children);
122
+ }
123
+ for (const key of ["files", "workload"]) {
124
+ for (const value of Array.isArray(record[key]) ? record[key] : []) {
125
+ addContextUsageIdList(ids, asObjectRecord(value)?.items);
126
+ }
127
+ }
128
+ const agenda = asObjectRecord(record.agenda);
129
+ for (const value of Array.isArray(agenda?.events) ? agenda.events : []) {
130
+ addContextUsageId(ids, asObjectRecord(value)?.item_id);
131
+ }
132
+ const tests = asObjectRecord(record.tests);
133
+ addContextUsageIdList(ids, tests?.items_failing);
134
+ }
135
+ /**
136
+ * Collect every item identifier visible in a final context or next result.
137
+ * The traversal is shape-aware so summary counters and unrelated identifier
138
+ * fields cannot enter the privacy-minimal usage ledger.
139
+ */
140
+ export function collectContextUsageDeliveredItemIds(result, surface) {
141
+ const record = asObjectRecord(result);
142
+ if (record === null || resultReceiptSaysOmitted(result))
143
+ return [];
144
+ const ids = new Set();
145
+ if (surface === "next") {
146
+ collectNextResultItemIds(record, ids);
147
+ }
148
+ else {
149
+ collectContextResultItemIds(record, ids);
150
+ }
151
+ return [...ids];
152
+ }
153
+ /** Resolve and append one post-egress decision carried by a context result. */
154
+ export async function finalizeContextUsageDelivery(options) {
155
+ const receipt = receiptFromResult(options.result);
156
+ if (receipt === null)
157
+ return false;
158
+ const resultOmitted = resultReceiptSaysOmitted(options.result);
159
+ await recordContextUsageDelivery({
160
+ pmRoot: options.pmRoot,
161
+ receipt,
162
+ deliveredItemIds: collectContextUsageDeliveredItemIds(options.result, receipt.surface),
163
+ resultOmitted,
164
+ });
165
+ return true;
166
+ }
19
167
  /** Named validation failure emitted by context-usage SDK inputs. */
20
168
  export class ContextUsageValidationError extends TypeError {
21
169
  /** Stable machine-readable error code for package and CLI hosts. */
@@ -61,7 +209,7 @@ async function readEvents(options) {
61
209
  return [];
62
210
  }
63
211
  }
64
- async function appendEvents(options, events) {
212
+ async function appendEvents(options, events, deliveryServeId) {
65
213
  const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
66
214
  const retentionDays = options.retentionDays ?? DEFAULT_RETENTION_DAYS;
67
215
  if (!Number.isInteger(maxEvents) ||
@@ -75,6 +223,10 @@ async function appendEvents(options, events) {
75
223
  const releaseLock = await acquireLock(options.pmRoot, CONTEXT_USAGE_LOCK_ID, 30, `context-usage:${process.pid}`, false, false, 3_000);
76
224
  try {
77
225
  await mkdir(runtimeDirectory, { recursive: true });
226
+ if (deliveryServeId !== undefined &&
227
+ (await readEvents(options)).some((event) => event.kind === "delivery" && event.serve_id === deliveryServeId)) {
228
+ return;
229
+ }
78
230
  await appendFile(target, events.map((event) => `${JSON.stringify(event)}\n`).join(""), "utf8");
79
231
  const customBounds = options.maxEvents !== undefined || options.retentionDays !== undefined;
80
232
  if (!customBounds && (await stat(target)).size <= DEFAULT_COMPACTION_BYTES)
@@ -93,21 +245,70 @@ async function appendEvent(options, event) {
93
245
  }
94
246
  /** Records a propensity-complete context/next serving event. */
95
247
  export async function recordContextUsageServing(options) {
96
- if (process.env.PM_CONTEXT_USAGE_DISABLED === "1" ||
97
- options.enabled === false)
98
- return;
248
+ if ([
249
+ process.env.PM_CONTEXT_USAGE_DISABLED === "1",
250
+ options.enabled === false,
251
+ ].includes(true))
252
+ return null;
99
253
  if (!options.author.trim() ||
100
254
  options.rows.some((row) => !row.id.trim() || !Number.isInteger(row.rank) || row.rank < 1)) {
101
255
  throw new ContextUsageValidationError("Context usage serving requires an author and valid ranked rows");
102
256
  }
257
+ const normalizedRows = options.rows.map((row) => ({
258
+ ...row,
259
+ id: row.id.trim(),
260
+ }));
261
+ if (new Set(normalizedRows.map((row) => row.id)).size !== normalizedRows.length) {
262
+ throw new ContextUsageValidationError("Context usage serving requires unique normalized row ids");
263
+ }
264
+ const receipt = {
265
+ serve_id: randomUUID(),
266
+ author: options.author.trim(),
267
+ surface: options.surface,
268
+ rows: normalizedRows,
269
+ };
103
270
  await appendEvent(options, {
104
271
  kind: "serve",
272
+ schema_version: 2,
273
+ serve_id: receipt.serve_id,
105
274
  at: resolveNow(options).iso,
106
275
  author: options.author.trim(),
107
276
  surface: options.surface,
108
277
  profile: options.profile.trim() || "balanced",
109
- rows: options.rows,
278
+ rows: receipt.rows.map((row) => ({ ...row })),
279
+ result_omitted: false,
280
+ packed_item_ids: receipt.rows
281
+ .filter((row) => row.included)
282
+ .map((row) => row.id),
110
283
  });
284
+ return receipt;
285
+ }
286
+ /** Append the exact post-egress delivery decision for one serving receipt. */
287
+ export async function recordContextUsageDelivery(options) {
288
+ if (options.receipt === null ||
289
+ process.env.PM_CONTEXT_USAGE_DISABLED === "1" ||
290
+ options.enabled === false) {
291
+ return;
292
+ }
293
+ const rankedIds = new Set(options.receipt.rows.map((row) => row.id));
294
+ const deliveredItemIds = [
295
+ ...new Set(options.deliveredItemIds.map((id) => id.trim())),
296
+ ];
297
+ if (deliveredItemIds.some((id) => id.length === 0 || !rankedIds.has(id)) ||
298
+ (options.resultOmitted && deliveredItemIds.length > 0)) {
299
+ throw new ContextUsageValidationError("Context usage delivery requires emitted ranked ids and zero ids for an omitted result");
300
+ }
301
+ const delivery = {
302
+ kind: "delivery",
303
+ schema_version: 2,
304
+ serve_id: options.receipt.serve_id,
305
+ at: resolveNow(options).iso,
306
+ author: options.receipt.author,
307
+ surface: options.receipt.surface,
308
+ result_omitted: options.resultOmitted,
309
+ delivered_item_ids: deliveredItemIds,
310
+ };
311
+ await appendEvents(options, [delivery], delivery.serve_id);
111
312
  }
112
313
  /** Records one subsequent item read or mutation outcome. */
113
314
  export async function recordContextUsageTouch(options) {
@@ -165,6 +366,18 @@ function findTouchTimeInHorizon(touches, itemId, servedAt, horizonMs) {
165
366
  time >= servedAt &&
166
367
  time - servedAt <= horizonMs)?.time;
167
368
  }
369
+ function firstDeliveriesByServeId(events, author) {
370
+ const deliveries = new Map();
371
+ for (const event of events) {
372
+ if (event.kind === "delivery" &&
373
+ event.schema_version === 2 &&
374
+ event.author === author &&
375
+ !deliveries.has(event.serve_id)) {
376
+ deliveries.set(event.serve_id, event);
377
+ }
378
+ }
379
+ return deliveries;
380
+ }
168
381
  /**
169
382
  * Derives decayed served-then-touched affinity. A small exploration floor keeps
170
383
  * ignored and unseen items eligible, preventing a popularity feedback lock-in.
@@ -172,26 +385,41 @@ function findTouchTimeInHorizon(touches, itemId, servedAt, horizonMs) {
172
385
  export async function readContextUsageAffinity(options) {
173
386
  if (process.env.PM_CONTEXT_USAGE_DISABLED === "1" ||
174
387
  options.enabled === false)
175
- return { affinity: {}, positive_judgments: 0, serving_events: 0 };
388
+ return {
389
+ affinity: {},
390
+ positive_judgments: 0,
391
+ serving_events: 0,
392
+ untrusted_serving_events: 0,
393
+ };
176
394
  const events = await readEvents(options);
177
395
  const now = resolveNow(options).ms;
178
396
  const horizonMs = (options.horizonHours ?? 24) * 3_600_000;
179
- if (!Number.isFinite(horizonMs) || horizonMs <= 0)
397
+ if (![Number.isFinite(horizonMs), horizonMs > 0].every(Boolean))
180
398
  throw new ContextUsageValidationError("Context usage horizonHours must be positive");
181
399
  const author = options.author.trim();
182
400
  const touches = events
183
- .filter((event) => event.kind === "touch" && event.author === author)
401
+ .filter((event) => event.kind === "touch")
402
+ .filter((event) => event.author === author)
184
403
  .map((entry) => ({ entry, time: Date.parse(entry.at) }));
185
404
  const scores = new Map();
186
405
  let servingEvents = 0;
406
+ let untrustedServingEvents = 0;
187
407
  let positiveJudgments = 0;
188
- for (const event of events) {
189
- if (event.kind !== "serve" || event.author !== author)
190
- continue;
408
+ const deliveries = firstDeliveriesByServeId(events, author);
409
+ events
410
+ .filter((event) => event.kind === "serve")
411
+ .filter((event) => event.author === author)
412
+ .forEach((event) => {
413
+ if (![event.schema_version === 2, Boolean(event.serve_id)].every(Boolean)) {
414
+ untrustedServingEvents += 1;
415
+ return;
416
+ }
191
417
  servingEvents += 1;
192
418
  const servedAt = Date.parse(event.at);
419
+ const delivery = deliveries.get(event.serve_id);
420
+ const deliveredIds = new Set(delivery?.delivered_item_ids ?? []);
193
421
  for (const row of event.rows) {
194
- if (!row.included)
422
+ if (!deliveredIds.has(row.id))
195
423
  continue;
196
424
  const touchTime = findTouchTimeInHorizon(touches, row.id, servedAt, horizonMs);
197
425
  if (touchTime === undefined)
@@ -200,17 +428,18 @@ export async function readContextUsageAffinity(options) {
200
428
  const ageDays = Math.max(0, (now - touchTime) / DAY_MS);
201
429
  scores.set(row.id, (scores.get(row.id) ?? 0) + Math.exp(-ageDays / 14));
202
430
  }
203
- }
431
+ });
204
432
  const maximum = Math.max(0, ...scores.values());
205
433
  const affinity = Object.fromEntries([...scores.entries()].map(([id, score]) => [
206
434
  id,
207
- 0.05 + 0.95 * (maximum > 0 ? score / maximum : 0),
435
+ maximum === 0 ? 0.05 : 0.05 + (0.95 * score) / maximum,
208
436
  ]));
209
437
  return {
210
438
  affinity,
211
439
  positive_judgments: positiveJudgments,
212
440
  serving_events: servingEvents,
441
+ untrusted_serving_events: untrustedServingEvents,
213
442
  };
214
443
  }
215
444
  //# sourceMappingURL=context-usage.js.map
216
- //# debugId=5f4ccd28-a001-54aa-8378-893025c69b96
445
+ //# debugId=997a09a2-cf0c-5384-b457-d9627d465b71
@@ -0,0 +1,296 @@
1
+ import { type WorkspaceRecipe } from "../workspace-recipe.js";
2
+ /** Schema identifier for a portable observation contract. */
3
+ export declare const PM_OBSERVATION_SCHEMA: "pm.agent-observation/v1";
4
+ /** Schema identifier for a deterministic verdict contract. */
5
+ export declare const PM_VERDICT_SCHEMA: "pm.agent-verdict/v1";
6
+ /** Schema identifier for a bounded agent-environment episode. */
7
+ export declare const PM_EPISODE_SCHEMA: "pm.agent-episode/v1";
8
+ /** A JSON-compatible object used as immutable recorded workspace state. */
9
+ export type RecordedStateObject = Readonly<Record<string, unknown>>;
10
+ /** One declared read and its guaranteed token ceiling. */
11
+ export interface ObservationReadDeclaration {
12
+ /** Public SDK or CLI command name used to collect the read. */
13
+ command: string;
14
+ /** Maximum tokens this read may consume. */
15
+ budget_tokens: number;
16
+ }
17
+ /** Emitted-byte calibration for a representative observation corpus. */
18
+ export interface ObservationCalibration {
19
+ /** Stable measurement basis; serialized payload bytes are authoritative. */
20
+ basis: "emitted_bytes";
21
+ /** Historical emitted-byte measurements used to calibrate expectations. */
22
+ samples: readonly number[];
23
+ /** Expected serialized payload size before an observation is served. */
24
+ expected_emitted_bytes: number;
25
+ /** Accepted drift around the expected emitted-byte measurement. */
26
+ tolerance_bytes: number;
27
+ }
28
+ /** Declares whether observation cost varies with corpus size. */
29
+ export type ObservationCorpusDependence = {
30
+ readonly kind: "independent";
31
+ } | {
32
+ readonly kind: "bounded";
33
+ readonly dimension: string;
34
+ readonly maximum: number;
35
+ };
36
+ /** One ordered disclosure tier in an observation degradation ladder. */
37
+ export interface ObservationTier {
38
+ /** Stable tier identity included in every observation receipt. */
39
+ id: string;
40
+ /** Maximum tokens this tier is permitted to emit. */
41
+ max_tokens: number;
42
+ /** Dot-separated recorded-state fields disclosed by the tier. */
43
+ fields: readonly string[];
44
+ }
45
+ /** Portable declaration for a bounded, calibrated observation. */
46
+ export interface ObservationContract {
47
+ /** Versioned schema identity. */
48
+ schema: typeof PM_OBSERVATION_SCHEMA;
49
+ /** Stable contract identity. */
50
+ id: string;
51
+ /** Positive contract revision. */
52
+ version: number;
53
+ /** Reads whose ceilings form the guaranteed collection cost. */
54
+ reads: readonly ObservationReadDeclaration[];
55
+ /** Emitted-byte calibration visible before collection starts. */
56
+ calibration: ObservationCalibration;
57
+ /** Explicit corpus-size dependence of the cost model. */
58
+ corpus_dependence: ObservationCorpusDependence;
59
+ /** Ordered richest-to-smallest degradation ladder. */
60
+ tiers: readonly ObservationTier[];
61
+ }
62
+ /** Pre-call observation-cost description available to schedulers. */
63
+ export interface ObservationCostDescription {
64
+ /** Stable observation identity. */
65
+ contract_id: string;
66
+ /** Observation revision. */
67
+ contract_version: number;
68
+ /** Sum of all declared collection ceilings. */
69
+ guaranteed_ceiling_tokens: number;
70
+ /** Calibrated expected serialized payload size. */
71
+ expected_emitted_bytes: number;
72
+ /** Conservative token estimate derived from emitted bytes. */
73
+ expected_tokens: number;
74
+ /** Accepted emitted-byte calibration drift. */
75
+ tolerance_bytes: number;
76
+ /** Stable calibration basis. */
77
+ calibration_basis: "emitted_bytes";
78
+ /** Declared corpus-size dependence. */
79
+ corpus_dependence: ObservationCorpusDependence;
80
+ }
81
+ /** Audit receipt emitted for every served or refused observation. */
82
+ export interface ObservationReceipt {
83
+ /** Stable observation identity. */
84
+ contract_id: string;
85
+ /** Observation revision. */
86
+ contract_version: number;
87
+ /** Tier whose payload was returned, or null after truthful refusal. */
88
+ served_tier: string | null;
89
+ /** Whether a smaller tier was attempted or served. */
90
+ degraded: boolean;
91
+ /** Tier identities evaluated in deterministic order. */
92
+ attempted_tiers: readonly string[];
93
+ /** Exact serialized payload bytes, or null when nothing was served. */
94
+ emitted_bytes: number | null;
95
+ /** Conservative token estimate, or null when nothing was served. */
96
+ estimated_tokens: number | null;
97
+ /** Stable measurement basis. */
98
+ calibration_basis: "emitted_bytes";
99
+ }
100
+ /** Successful bounded observation with its explicit receipt. */
101
+ export interface ServedObservation<T> {
102
+ /** Discriminator for a payload-bearing response. */
103
+ status: "served";
104
+ /** Payload produced by the declared tier. */
105
+ payload: T;
106
+ /** Cost and degradation evidence. */
107
+ receipt: ObservationReceipt;
108
+ }
109
+ /** Refusal returned when no declared tier can fit the caller's ceiling. */
110
+ export interface RefusedObservation {
111
+ /** Discriminator for a payload-free refusal. */
112
+ status: "refused";
113
+ /** Stable reason callers can route without parsing prose. */
114
+ reason: "no_declared_tier_fits_budget";
115
+ /** Cost and degradation evidence. */
116
+ receipt: ObservationReceipt;
117
+ }
118
+ /** Complete outcome of attempting one declared observation. */
119
+ export type ObservationResult<T> = ServedObservation<T> | RefusedObservation;
120
+ /** Caller-imposed ceiling for one observation response. */
121
+ export interface ObservationRequest {
122
+ /** Maximum response tokens accepted by the caller. */
123
+ budget_tokens: number;
124
+ }
125
+ /** Predicate supported by the versioned total-verdict interpreter. */
126
+ export type VerdictPredicate = {
127
+ readonly id: string;
128
+ readonly kind: "field_equals";
129
+ readonly path: string;
130
+ readonly expected: unknown;
131
+ } | {
132
+ readonly id: string;
133
+ readonly kind: "field_present";
134
+ readonly path: string;
135
+ } | {
136
+ readonly id: string;
137
+ readonly kind: "field_absent";
138
+ readonly path: string;
139
+ };
140
+ /** Declarative composition applied to predicate outcomes. */
141
+ export interface VerdictComposition {
142
+ /** Whether every or at least one referenced predicate must satisfy. */
143
+ operator: "all" | "any";
144
+ /** Ordered predicate identities evaluated by the total verdict. */
145
+ predicate_ids: readonly string[];
146
+ }
147
+ /** Portable named and versioned total-verdict declaration. */
148
+ export interface VerdictContract {
149
+ /** Versioned schema identity. */
150
+ schema: typeof PM_VERDICT_SCHEMA;
151
+ /** Stable contract identity. */
152
+ id: string;
153
+ /** Positive contract revision. */
154
+ version: number;
155
+ /** Predicates evaluated exclusively over recorded state. */
156
+ predicates: readonly VerdictPredicate[];
157
+ /** Explicit deterministic predicate composition. */
158
+ composition: VerdictComposition;
159
+ }
160
+ /** Recorded state and stable snapshot identity supplied by an adapter. */
161
+ export interface WorkspaceRecordedState {
162
+ /** Stable identity of the exact state being evaluated. */
163
+ snapshot_id: string;
164
+ /** Recorded, JSON-compatible project state. */
165
+ state: RecordedStateObject;
166
+ }
167
+ /** Per-predicate evidence in a deterministic total verdict. */
168
+ export interface PredicateVerdict {
169
+ /** Declared predicate identity. */
170
+ id: string;
171
+ /** Whether the predicate is satisfied by recorded state. */
172
+ outcome: "satisfied" | "violated";
173
+ /** Snapshot-qualified JSON pointer to the inspected field. */
174
+ evidence_pointer: string;
175
+ /** Hash of the observed value without duplicating potentially private data. */
176
+ evidence_digest: string;
177
+ }
178
+ /** Total, reproducible result produced by a verdict contract. */
179
+ export interface VerdictResult {
180
+ /** Stable verdict identity. */
181
+ contract_id: string;
182
+ /** Verdict revision. */
183
+ contract_version: number;
184
+ /** Exact recorded-state snapshot evaluated. */
185
+ snapshot_id: string;
186
+ /** Total outcome, including an explicit empty-contract state. */
187
+ outcome: "satisfied" | "violated" | "not_applicable";
188
+ /** Satisfied fraction, or null when no predicates are declared. */
189
+ score: number | null;
190
+ /** Explicitly separates evaluation from reward or learning signals. */
191
+ policy: "verdict_only_no_reward";
192
+ /** Stable explanation for a not-applicable verdict. */
193
+ reason?: "no_predicates_declared";
194
+ /** Ordered predicate evidence. */
195
+ predicates: readonly PredicateVerdict[];
196
+ }
197
+ /** Hard limits enforced within one isolated episode. */
198
+ export interface EpisodeLimits {
199
+ /** Maximum ordinary project-management actions. */
200
+ max_actions: number;
201
+ /** Maximum observations, including the close-time grading read. */
202
+ max_observations: number;
203
+ /** Per-observation response ceiling. */
204
+ max_tokens_per_observation: number;
205
+ }
206
+ /** Portable specification shared by evaluation and training harnesses. */
207
+ export interface EpisodeSpecification {
208
+ /** Versioned schema identity. */
209
+ schema: typeof PM_EPISODE_SCHEMA;
210
+ /** Stable episode identity and session provenance key. */
211
+ id: string;
212
+ /** Positive episode revision. */
213
+ version: number;
214
+ /** Consumer intent without coupling the runtime to a training framework. */
215
+ mode: "benchmark" | "evaluation" | "training" | "scale_transfer";
216
+ /** Deterministic workspace construction recipe. */
217
+ recipe: WorkspaceRecipe;
218
+ /** Canonical pm items that define the task set. */
219
+ task_item_ids: readonly string[];
220
+ /** State fields that observations may disclose. */
221
+ observable_fields: readonly string[];
222
+ /** Grading-only fields that must never enter observations. */
223
+ withheld_fields: readonly string[];
224
+ /** Bounded observation declaration. */
225
+ observation: ObservationContract;
226
+ /** Recorded-state verdict declaration. */
227
+ verdict: VerdictContract;
228
+ /** Hard action and observation limits. */
229
+ limits: EpisodeLimits;
230
+ }
231
+ /** Reset receipt returned by an episode adapter. */
232
+ export interface EpisodeResetReceipt {
233
+ /** Adapter-defined isolated workspace identity. */
234
+ workspace_id: string;
235
+ /** Stable state identity after reset. */
236
+ state_id: string;
237
+ }
238
+ /** Action receipt returned by an episode adapter. */
239
+ export interface EpisodeActionReceipt<TOutput> {
240
+ /** Stable state identity after the action. */
241
+ state_id: string;
242
+ /** Adapter-defined ordinary command result. */
243
+ output: TOutput;
244
+ }
245
+ /** Host boundary for ordinary pm SDK actions and recorded-state reads. */
246
+ export interface EpisodeRuntimeAdapter<TAction, TOutput = unknown> {
247
+ /** Construct a fresh isolated workspace using the declared recipe. */
248
+ reset(recipe: WorkspaceRecipe): Promise<EpisodeResetReceipt>;
249
+ /** Execute one ordinary action, normally by calling `PmClient.run`. */
250
+ execute(action: TAction): Promise<EpisodeActionReceipt<TOutput>>;
251
+ /** Read immutable grading state from the workspace system of record. */
252
+ readRecordedState(): Promise<WorkspaceRecordedState>;
253
+ }
254
+ /** One deterministic entry in a portable episode trajectory. */
255
+ export interface EpisodeTrajectoryEntry {
256
+ /** Monotonic event number within the isolated episode. */
257
+ sequence: number;
258
+ /** Stable event classification. */
259
+ kind: "reset" | "observation" | "action" | "verdict" | "close";
260
+ /** Snapshot, state, or observation tier associated with the event. */
261
+ state_id: string;
262
+ }
263
+ /** Closed episode evidence without reward or framework-specific shaping. */
264
+ export interface ClosedEpisode {
265
+ /** Deterministic total verdict. */
266
+ verdict: VerdictResult;
267
+ /** Complete bounded trajectory. */
268
+ trajectory: readonly EpisodeTrajectoryEntry[];
269
+ /** Explicitly absent to prevent verdicts from becoming reward signals. */
270
+ reward?: never;
271
+ }
272
+ /** Isolated runtime returned for one declared agent episode. */
273
+ export interface PmEpisodeSession<TAction, TOutput> {
274
+ /** Observe recorded state through the declared disclosure ladder. */
275
+ observe(): Promise<ObservationResult<RecordedStateObject>>;
276
+ /** Execute one bounded ordinary project-management action. */
277
+ step(action: TAction): Promise<EpisodeActionReceipt<TOutput>>;
278
+ /** Evaluate the current recorded state without closing the session. */
279
+ score(): Promise<VerdictResult>;
280
+ /** Evaluate and close the episode, returning immutable evidence. */
281
+ close(): Promise<ClosedEpisode>;
282
+ }
283
+ /** Validate, clone, and freeze a portable observation declaration. */
284
+ export declare function defineObservationContract(declaration: ObservationContract): ObservationContract;
285
+ /** Describe guaranteed and calibrated observation cost before collection. */
286
+ export declare function describeObservationCost(contract: ObservationContract): ObservationCostDescription;
287
+ /** Serve the richest declared tier that fits both caller and tier ceilings. */
288
+ export declare function serveDeclaredObservation<T>(contract: ObservationContract, collect: (tier: ObservationTier) => Promise<T>, request: ObservationRequest): Promise<ObservationResult<T>>;
289
+ /** Validate, clone, and freeze a portable total-verdict declaration. */
290
+ export declare function defineVerdictContract(declaration: VerdictContract): VerdictContract;
291
+ /** Evaluate a total verdict exclusively from one immutable recorded snapshot. */
292
+ export declare function evaluateVerdictContract(contract: VerdictContract, recorded: WorkspaceRecordedState): VerdictResult;
293
+ /** Validate, clone, and freeze a portable episode specification. */
294
+ export declare function defineEpisodeSpecification(declaration: EpisodeSpecification): EpisodeSpecification;
295
+ /** Open one isolated, deterministic episode around an adapter's ordinary actions. */
296
+ export declare function openPmEpisode<TAction, TOutput = unknown>(specification: EpisodeSpecification, adapter: EpisodeRuntimeAdapter<TAction, TOutput>): Promise<PmEpisodeSession<TAction, TOutput>>;