@unbrained/pm-cli 2026.8.6 → 2026.8.8

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 (269) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +65 -0
  3. package/dist/cli/main.js +6 -3
  4. package/dist/cli/register-annotations.d.ts +11 -0
  5. package/dist/cli/register-annotations.js +195 -0
  6. package/dist/cli/register-history-author.js +7 -30
  7. package/dist/cli/register-mutation.js +25 -206
  8. package/dist/cli/register-operations.js +11 -2
  9. package/dist/cli/runtime-compatibility-boundary.d.ts +17 -0
  10. package/dist/cli/runtime-compatibility-boundary.js +43 -0
  11. package/dist/cli/stats-analytics-json.d.ts +9 -0
  12. package/dist/cli/stats-analytics-json.js +69 -0
  13. package/dist/cli-bundle/bundle-manifest.json +156 -692
  14. package/dist/cli-bundle/chunks/chunk-2ZMXX3Q6.js +2068 -0
  15. package/dist/cli-bundle/chunks/chunk-ALDHXLPS.js +3 -0
  16. package/dist/cli-bundle/chunks/chunk-CBLA33YK.js +8 -0
  17. package/dist/cli-bundle/chunks/chunk-GKA54IJM.js +5 -0
  18. package/dist/cli-bundle/chunks/chunk-KRY7SRUO.js +2 -0
  19. package/dist/cli-bundle/chunks/chunk-MNJ4NNSY.js +26 -0
  20. package/dist/cli-bundle/chunks/chunk-NUXRRNAB.js +77 -0
  21. package/dist/cli-bundle/chunks/chunk-OJPCFDDY.js +13 -0
  22. package/dist/cli-bundle/chunks/chunk-PFSD3DAN.js +194 -0
  23. package/dist/cli-bundle/chunks/chunk-QWRRFYK3.js +2 -0
  24. package/dist/cli-bundle/chunks/chunk-SQ7QZFQ6.js +2 -0
  25. package/dist/cli-bundle/chunks/{register-list-query-QH7ONPKU.js → register-list-query-WFZAMJHZ.js} +8 -8
  26. package/dist/cli-bundle/chunks/register-mutation-SOKKOTHQ.js +20 -0
  27. package/dist/cli-bundle/chunks/register-operations-5XQETS6E.js +2 -0
  28. package/dist/cli-bundle/chunks/register-setup-2HL5ZHMK.js +2 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-54EU2UVO.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/{chunk-N7QN3NE6.js → chunk-5FLBTZJQ.js} +2 -2
  31. package/dist/cli-bundle/focused-chunks/chunk-5GJ57YKN.js +2 -0
  32. package/dist/cli-bundle/focused-chunks/{chunk-YLPZHWX7.js → chunk-ANTMGYLI.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/chunk-CMDS3KYT.js +14 -0
  34. package/dist/cli-bundle/focused-chunks/{chunk-EYO4F74Z.js → chunk-DMKOMTTY.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/{chunk-GSW2Y7VZ.js → chunk-F5B3HBEZ.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/{chunk-27DK4A5O.js → chunk-FI6DWU2M.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/{chunk-CL3NB7VW.js → chunk-FNOKJG6N.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/{chunk-EAXXF2HW.js → chunk-FPDYXZBK.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/{chunk-XNVXIQ4B.js → chunk-GWI6YIKJ.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/{chunk-6KDHXURF.js → chunk-HCUC62AB.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/{chunk-GEYU23YS.js → chunk-HGNKPSCB.js} +22 -22
  42. package/dist/cli-bundle/focused-chunks/chunk-IAS7PMLG.js +2 -0
  43. package/dist/cli-bundle/focused-chunks/{chunk-TQMFQYXR.js → chunk-PL2EYDJK.js} +29 -29
  44. package/dist/cli-bundle/focused-chunks/chunk-SM2PJGP3.js +2 -0
  45. package/dist/cli-bundle/focused-chunks/{chunk-4JC3AOGS.js → chunk-TCSAPH33.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/chunk-U646T7Z6.js +18 -0
  47. package/dist/cli-bundle/focused-chunks/chunk-YKPE4PPY.js +2 -0
  48. package/dist/cli-bundle/focused-chunks/{chunk-GIERI4YU.js → chunk-YSAHYUXX.js} +2 -2
  49. package/dist/cli-bundle/focused-chunks/chunk-ZOLDTUU7.js +2 -0
  50. package/dist/cli-bundle/main.js +13 -13
  51. package/dist/cli-bundle/sdk-authoring.js +1 -1
  52. package/dist/cli-bundle/sdk-contracts.js +1 -1
  53. package/dist/cli-bundle/sdk-core.js +38 -37
  54. package/dist/cli-bundle/sdk-governance.js +1 -1
  55. package/dist/cli-bundle/sdk-graph.js +1 -1
  56. package/dist/cli-bundle/sdk-merge.js +1 -1
  57. package/dist/cli-bundle/sdk-query.js +1 -1
  58. package/dist/cli-bundle/sdk-runtime.js +1 -1
  59. package/dist/cli-bundle/sdk-testing.js +1 -1
  60. package/dist/cli-bundle/sdk.js +2 -1
  61. package/dist/cli.js +14 -4
  62. package/dist/core/extensions/reserved-host-flags.js +3 -2
  63. package/dist/core/history/workspace-history.d.ts +22 -2
  64. package/dist/core/history/workspace-history.js +60 -36
  65. package/dist/core/output/output.d.ts +2 -0
  66. package/dist/core/output/output.js +10 -2
  67. package/dist/core/sentry/helpers.d.ts +10 -0
  68. package/dist/core/sentry/helpers.js +59 -4
  69. package/dist/core/shared/command-types.d.ts +2 -0
  70. package/dist/core/shared/command-types.js +2 -2
  71. package/dist/core/shared/constants.d.ts +2 -0
  72. package/dist/core/shared/constants.js +4 -2
  73. package/dist/mcp/tool-definitions.js +6 -2
  74. package/dist/sdk/annotations.d.ts +40 -0
  75. package/dist/sdk/annotations.js +61 -9
  76. package/dist/sdk/author-attribution.d.ts +12 -0
  77. package/dist/sdk/author-attribution.js +87 -9
  78. package/dist/sdk/cli-bootstrap.d.ts +2 -0
  79. package/dist/sdk/cli-bootstrap.js +7 -2
  80. package/dist/sdk/cli-contracts/command-exit-contracts.d.ts +118 -0
  81. package/dist/sdk/cli-contracts/command-exit-contracts.js +152 -0
  82. package/dist/sdk/cli-contracts/completeness.js +54 -5
  83. package/dist/sdk/cli-contracts/flag-contracts.js +6 -2
  84. package/dist/sdk/cli-contracts/registration-helpers.js +3 -2
  85. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +11 -0
  86. package/dist/sdk/cli-contracts/runtime-contracts.js +41 -8
  87. package/dist/sdk/cli-contracts/tool-parameter-tables.js +129 -10
  88. package/dist/sdk/cli-contracts/tool-schema.d.ts +1 -1
  89. package/dist/sdk/cli-contracts/tool-schema.js +22 -3
  90. package/dist/sdk/cli-program.js +3 -2
  91. package/dist/sdk/comments.d.ts +7 -0
  92. package/dist/sdk/comments.js +2 -2
  93. package/dist/sdk/completion.js +12 -5
  94. package/dist/sdk/contracts.d.ts +2 -0
  95. package/dist/sdk/contracts.js +3 -2
  96. package/dist/sdk/core.d.ts +3 -1
  97. package/dist/sdk/core.js +4 -3
  98. package/dist/sdk/environment/host-environment-errors.d.ts +27 -0
  99. package/dist/sdk/environment/host-environment-errors.js +124 -0
  100. package/dist/sdk/environment/project-runtime-compatibility.d.ts +65 -0
  101. package/dist/sdk/environment/project-runtime-compatibility.js +385 -0
  102. package/dist/sdk/environment/source-context.d.ts +23 -0
  103. package/dist/sdk/environment/source-context.js +29 -0
  104. package/dist/sdk/error-code-catalog.d.ts +6 -0
  105. package/dist/sdk/error-code-catalog.js +62 -8
  106. package/dist/sdk/extension.js +9 -11
  107. package/dist/sdk/generated-error-code-catalog.js +739 -3
  108. package/dist/sdk/governance/health.d.ts +11 -3
  109. package/dist/sdk/governance/health.js +5 -5
  110. package/dist/sdk/history-analytics.d.ts +141 -0
  111. package/dist/sdk/history-analytics.js +283 -0
  112. package/dist/sdk/improvement-ledger-validation.d.ts +3 -0
  113. package/dist/sdk/improvement-ledger-validation.js +63 -0
  114. package/dist/sdk/improvement-ledger.d.ts +139 -0
  115. package/dist/sdk/improvement-ledger.js +261 -0
  116. package/dist/sdk/index.d.ts +13 -5
  117. package/dist/sdk/index.js +15 -7
  118. package/dist/sdk/learnings.d.ts +15 -0
  119. package/dist/sdk/learnings.js +2 -2
  120. package/dist/sdk/lifecycle/close-many.d.ts +6 -1
  121. package/dist/sdk/lifecycle/close-many.js +43 -5
  122. package/dist/sdk/lifecycle/plan.js +6 -3
  123. package/dist/sdk/lifecycle/update-many.d.ts +2 -1
  124. package/dist/sdk/lifecycle/update-many.js +29 -5
  125. package/dist/sdk/merge/index.d.ts +1 -1
  126. package/dist/sdk/merge/index.js +3 -3
  127. package/dist/sdk/merge/install.d.ts +21 -0
  128. package/dist/sdk/merge/install.js +58 -20
  129. package/dist/sdk/notes.d.ts +7 -0
  130. package/dist/sdk/notes.js +2 -2
  131. package/dist/sdk/read-output-budget.js +24 -22
  132. package/dist/sdk/read-output-contracts.d.ts +10 -2
  133. package/dist/sdk/read-output-contracts.js +204 -66
  134. package/dist/sdk/read-output-rows.d.ts +29 -0
  135. package/dist/sdk/read-output-rows.js +100 -0
  136. package/dist/sdk/read-output-session.d.ts +69 -0
  137. package/dist/sdk/read-output-session.js +200 -0
  138. package/dist/sdk/runtime-input.d.ts +2 -0
  139. package/dist/sdk/runtime-input.js +14 -2
  140. package/dist/sdk/runtime-primitives.d.ts +1 -1
  141. package/dist/sdk/runtime-primitives.js +3 -3
  142. package/dist/sdk/runtime-stats-options.d.ts +9 -0
  143. package/dist/sdk/runtime-stats-options.js +38 -0
  144. package/dist/sdk/runtime.d.ts +1 -0
  145. package/dist/sdk/runtime.js +13 -50
  146. package/dist/sdk/stats.d.ts +40 -0
  147. package/dist/sdk/stats.js +112 -29
  148. package/dist/sdk/test/execution.js +4 -2
  149. package/dist/sdk/test/parsers.js +5 -2
  150. package/dist/sdk/workspace-snapshot.d.ts +9 -0
  151. package/dist/sdk/workspace-snapshot.js +48 -16
  152. package/docs/AGENT_RUNTIME_PRIMITIVES.md +11 -4
  153. package/docs/IMPROVEMENT_ANALYTICS.md +103 -0
  154. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +5 -5
  155. package/docs/README.md +2 -0
  156. package/docs/READ_OUTPUT_CONTRACTS.md +60 -1
  157. package/docs/RELEASING.md +27 -2
  158. package/docs/SCRIPTING.md +27 -1
  159. package/docs/SDK_CONTEXT_INTEGRITY.md +66 -0
  160. package/docs/SDK_RUNTIME_BOUNDARIES.md +58 -0
  161. package/docs/SELF_DESCRIBING_CONTEXT_CONTRACTS.md +13 -0
  162. package/docs/TESTING.md +17 -0
  163. package/marketplace.json +2 -2
  164. package/package.json +6 -4
  165. package/packages/pm-beads/package.json +1 -1
  166. package/packages/pm-calendar/package.json +1 -1
  167. package/packages/pm-command-kit/package.json +1 -1
  168. package/packages/pm-digital-twin/package.json +1 -1
  169. package/packages/pm-governance-audit/package.json +1 -1
  170. package/packages/pm-guide-shell/package.json +1 -1
  171. package/packages/pm-kanban/package.json +1 -1
  172. package/packages/pm-lifecycle-hooks/package.json +1 -1
  173. package/packages/pm-linked-test-adapters/package.json +1 -1
  174. package/packages/pm-search-advanced/package.json +1 -1
  175. package/packages/pm-templates/package.json +1 -1
  176. package/packages/pm-todos/package.json +1 -1
  177. package/packages/pm-vcs/package.json +1 -1
  178. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  179. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  180. package/sdk/public-surface.json +875 -827
  181. package/dist/cli-bundle/chunks/append-XAJ5Z4XS.js +0 -2
  182. package/dist/cli-bundle/chunks/chunk-343QETLI.js +0 -2
  183. package/dist/cli-bundle/chunks/chunk-3KFVGGRE.js +0 -2
  184. package/dist/cli-bundle/chunks/chunk-3ZJRFYY2.js +0 -23
  185. package/dist/cli-bundle/chunks/chunk-4AVFKHHC.js +0 -2
  186. package/dist/cli-bundle/chunks/chunk-4OVGQW22.js +0 -2
  187. package/dist/cli-bundle/chunks/chunk-5PBP2ZP3.js +0 -2
  188. package/dist/cli-bundle/chunks/chunk-5ZKQPA44.js +0 -2
  189. package/dist/cli-bundle/chunks/chunk-A3XQ7VPU.js +0 -2
  190. package/dist/cli-bundle/chunks/chunk-A6KGKEWT.js +0 -2
  191. package/dist/cli-bundle/chunks/chunk-AJE3AHPD.js +0 -8
  192. package/dist/cli-bundle/chunks/chunk-AOBDLU4T.js +0 -2
  193. package/dist/cli-bundle/chunks/chunk-AYRUGRNS.js +0 -3
  194. package/dist/cli-bundle/chunks/chunk-BP6EMEDP.js +0 -5
  195. package/dist/cli-bundle/chunks/chunk-BXCSXK4J.js +0 -2
  196. package/dist/cli-bundle/chunks/chunk-C3YABSJK.js +0 -2
  197. package/dist/cli-bundle/chunks/chunk-CA3BURYF.js +0 -2
  198. package/dist/cli-bundle/chunks/chunk-CHOQ2VAW.js +0 -2
  199. package/dist/cli-bundle/chunks/chunk-D7I3TUGV.js +0 -2
  200. package/dist/cli-bundle/chunks/chunk-DQGJ2RWT.js +0 -3
  201. package/dist/cli-bundle/chunks/chunk-DWOAOZHZ.js +0 -2
  202. package/dist/cli-bundle/chunks/chunk-E2ZSLTOP.js +0 -2
  203. package/dist/cli-bundle/chunks/chunk-E7BFPLUZ.js +0 -5
  204. package/dist/cli-bundle/chunks/chunk-F4NREH3G.js +0 -2
  205. package/dist/cli-bundle/chunks/chunk-FB7G2HY2.js +0 -2
  206. package/dist/cli-bundle/chunks/chunk-FDBRXV25.js +0 -2
  207. package/dist/cli-bundle/chunks/chunk-GU3TSEHD.js +0 -2
  208. package/dist/cli-bundle/chunks/chunk-HS7OVAUN.js +0 -164
  209. package/dist/cli-bundle/chunks/chunk-JC6FKIFM.js +0 -2
  210. package/dist/cli-bundle/chunks/chunk-JFZIS6JF.js +0 -2
  211. package/dist/cli-bundle/chunks/chunk-JQ4NZ5EB.js +0 -2
  212. package/dist/cli-bundle/chunks/chunk-L6LGKJX3.js +0 -2
  213. package/dist/cli-bundle/chunks/chunk-LEPJJ6QC.js +0 -2
  214. package/dist/cli-bundle/chunks/chunk-M357ZCOR.js +0 -2
  215. package/dist/cli-bundle/chunks/chunk-M4VEE3FK.js +0 -2
  216. package/dist/cli-bundle/chunks/chunk-MCS73BKB.js +0 -3
  217. package/dist/cli-bundle/chunks/chunk-MDTH7SAE.js +0 -19
  218. package/dist/cli-bundle/chunks/chunk-NE5VRDAI.js +0 -2
  219. package/dist/cli-bundle/chunks/chunk-OXY3SJ3N.js +0 -2
  220. package/dist/cli-bundle/chunks/chunk-PECV7L5T.js +0 -2
  221. package/dist/cli-bundle/chunks/chunk-Q3VOP62W.js +0 -2
  222. package/dist/cli-bundle/chunks/chunk-QJ7C3JIW.js +0 -2
  223. package/dist/cli-bundle/chunks/chunk-R6LSRGPS.js +0 -8
  224. package/dist/cli-bundle/chunks/chunk-R6RKEYYW.js +0 -2
  225. package/dist/cli-bundle/chunks/chunk-RAFUN7IX.js +0 -2005
  226. package/dist/cli-bundle/chunks/chunk-RSIC5OPE.js +0 -2
  227. package/dist/cli-bundle/chunks/chunk-RUAU5OSH.js +0 -2
  228. package/dist/cli-bundle/chunks/chunk-SD3YXU2U.js +0 -56
  229. package/dist/cli-bundle/chunks/chunk-SV3YQ4RY.js +0 -2
  230. package/dist/cli-bundle/chunks/chunk-SVV3DQES.js +0 -2
  231. package/dist/cli-bundle/chunks/chunk-SXECIECW.js +0 -5
  232. package/dist/cli-bundle/chunks/chunk-T45OHXDW.js +0 -8
  233. package/dist/cli-bundle/chunks/chunk-TPKDJ5WL.js +0 -2
  234. package/dist/cli-bundle/chunks/chunk-UVVXHGYS.js +0 -3
  235. package/dist/cli-bundle/chunks/chunk-V663653R.js +0 -20
  236. package/dist/cli-bundle/chunks/chunk-WSI5KZQJ.js +0 -55
  237. package/dist/cli-bundle/chunks/chunk-XPOQQJTX.js +0 -13
  238. package/dist/cli-bundle/chunks/chunk-YCCDXBNU.js +0 -2
  239. package/dist/cli-bundle/chunks/chunk-ZFBWCKYF.js +0 -2
  240. package/dist/cli-bundle/chunks/close-DI66JMSB.js +0 -2
  241. package/dist/cli-bundle/chunks/close-many-VFKT6U3O.js +0 -2
  242. package/dist/cli-bundle/chunks/comments-E73HIEZJ.js +0 -2
  243. package/dist/cli-bundle/chunks/copy-WBYVLCPC.js +0 -2
  244. package/dist/cli-bundle/chunks/create-TG67IVFI.js +0 -2
  245. package/dist/cli-bundle/chunks/delete-6XKUVVQN.js +0 -2
  246. package/dist/cli-bundle/chunks/deps-AJUGP2OH.js +0 -2
  247. package/dist/cli-bundle/chunks/docs-QOCLDRKJ.js +0 -2
  248. package/dist/cli-bundle/chunks/files-3ST5TY64.js +0 -2
  249. package/dist/cli-bundle/chunks/focus-R2AWS63Y.js +0 -2
  250. package/dist/cli-bundle/chunks/history-compact-7BJFML4S.js +0 -2
  251. package/dist/cli-bundle/chunks/history-redact-WJN3HYXR.js +0 -2
  252. package/dist/cli-bundle/chunks/history-repair-XDNZUB7F.js +0 -2
  253. package/dist/cli-bundle/chunks/learnings-SZMUPVNA.js +0 -2
  254. package/dist/cli-bundle/chunks/profile-KL53JIAC.js +0 -2
  255. package/dist/cli-bundle/chunks/register-mutation-OCRQNZK4.js +0 -20
  256. package/dist/cli-bundle/chunks/register-operations-HIBJXRGB.js +0 -2
  257. package/dist/cli-bundle/chunks/register-setup-QWBQ6HBS.js +0 -2
  258. package/dist/cli-bundle/chunks/restore-FXA5QPM7.js +0 -2
  259. package/dist/cli-bundle/chunks/schema-QJ27OTRV.js +0 -2
  260. package/dist/cli-bundle/chunks/update-7GBO4SXG.js +0 -2
  261. package/dist/cli-bundle/chunks/update-many-VF6ZKQUO.js +0 -2
  262. package/dist/cli-bundle/focused-chunks/chunk-74CWP3A4.js +0 -2
  263. package/dist/cli-bundle/focused-chunks/chunk-7AZDTGPA.js +0 -2
  264. package/dist/cli-bundle/focused-chunks/chunk-7YTZ7A4E.js +0 -2
  265. package/dist/cli-bundle/focused-chunks/chunk-LKQXXO62.js +0 -2
  266. package/dist/cli-bundle/focused-chunks/chunk-MBBMTDD3.js +0 -12
  267. package/dist/cli-bundle/focused-chunks/chunk-ME4XVOBJ.js +0 -8
  268. package/dist/cli-bundle/focused-chunks/chunk-QL5H3AQH.js +0 -2
  269. package/dist/cli-bundle/focused-chunks/chunk-YHDCGUIW.js +0 -14
package/dist/sdk/stats.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Implements the pm stats command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !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]="e8916552-697e-565c-91e1-4829ecb584af")}catch(e){}}();
7
+ !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]="a4de53c1-bb22-591f-a064-142c642a2443")}catch(e){}}();
8
8
  import fs from "node:fs/promises";
9
9
  import path from "node:path";
10
10
  import { getActiveExtensionRegistrations, runActiveOnReadHooks, } from "../core/extensions/index.js";
@@ -21,6 +21,9 @@ import { nowIso } from "../core/shared/time.js";
21
21
  import { listAllItemMetadataLight, listAllItemMetadataWithBody, } from "../core/store/item-store.js";
22
22
  import { getSettingsPath, resolvePmRoot } from "../core/store/paths.js";
23
23
  import { readSettings } from "../core/store/settings.js";
24
+ import { recordImprovementObservation, readImprovementLedger, } from "./improvement-ledger.js";
25
+ import { projectFleetAttributionAnalytics, projectProvenanceCoverageAnalytics, readHistoryAnalyticsWindow, } from "./history-analytics.js";
26
+ import { parseTestRunMeasurements } from "./test/measurements.js";
24
27
  function zeroByType(itemTypes) {
25
28
  return itemTypes.reduce((acc, value) => {
26
29
  acc[value] = 0;
@@ -73,6 +76,88 @@ export const _testOnly = {
73
76
  countNonEmptyLines,
74
77
  readHistoryStreamContents,
75
78
  };
79
+ async function recordStatsObservations(global, options) {
80
+ const recorded = [];
81
+ const observedAt = nowIso();
82
+ for (const measurement of parseTestRunMeasurements(options.observe, observedAt)) {
83
+ recorded.push(await recordImprovementObservation({
84
+ metric: measurement.name,
85
+ value: measurement.value,
86
+ direction: options.direction,
87
+ unit: measurement.unit,
88
+ threshold: measurement.threshold,
89
+ source: options.measurementSource,
90
+ itemId: options.measurementItem,
91
+ revision: options.measurementRevision,
92
+ observedAt,
93
+ author: options.author,
94
+ message: options.message,
95
+ }, global));
96
+ }
97
+ return recorded;
98
+ }
99
+ function requestedBreakdowns(items, options, classifier) {
100
+ const breakdowns = {};
101
+ if (options.byAssignee) {
102
+ breakdowns.assignee = groupItemsByDimension(items, "assignee", classifier);
103
+ }
104
+ if (options.byTag) {
105
+ breakdowns.tag = groupItemsByDimension(items, "tag", classifier, {
106
+ tagPrefix: options.tagPrefix,
107
+ });
108
+ }
109
+ if (options.byPriority) {
110
+ breakdowns.priority = groupItemsByDimension(items, "priority", classifier);
111
+ }
112
+ return breakdowns;
113
+ }
114
+ async function requestedHistoryAnalytics(pmRoot, items, settings, terminalStatuses, options) {
115
+ const historyOptions = {
116
+ since: options.since,
117
+ eventLimit: options.eventLimit,
118
+ minimumSample: options.minimumSample,
119
+ };
120
+ const sharedWindow = options.provenanceCoverage || options.fleetAttribution
121
+ ? await readHistoryAnalyticsWindow(pmRoot, historyOptions)
122
+ : undefined;
123
+ return {
124
+ provenanceCoverage: options.provenanceCoverage && sharedWindow
125
+ ? projectProvenanceCoverageAnalytics(sharedWindow, settings.agent_identity?.harness_signals, historyOptions)
126
+ : undefined,
127
+ fleetAttribution: options.fleetAttribution && sharedWindow
128
+ ? projectFleetAttributionAnalytics(sharedWindow, items, terminalStatuses, historyOptions)
129
+ : undefined,
130
+ };
131
+ }
132
+ function assembleStatsResult(totals, byType, byStatus, optional) {
133
+ const hasBreakdowns = Object.keys(optional.breakdowns).length > 0;
134
+ return {
135
+ totals,
136
+ by_type: byType,
137
+ by_status: byStatus,
138
+ ...(optional.metadataCoverage
139
+ ? { metadata_coverage: optional.metadataCoverage }
140
+ : {}),
141
+ ...(hasBreakdowns ? { breakdowns: optional.breakdowns } : {}),
142
+ ...(optional.storage ? { storage: optional.storage } : {}),
143
+ ...(optional.fieldUtilization
144
+ ? { field_utilization: optional.fieldUtilization }
145
+ : {}),
146
+ ...(optional.improvementLedger
147
+ ? { improvement_ledger: optional.improvementLedger }
148
+ : {}),
149
+ ...(optional.recordedObservations.length > 0
150
+ ? { recorded_observations: optional.recordedObservations }
151
+ : {}),
152
+ ...(optional.provenanceCoverage
153
+ ? { provenance_coverage: optional.provenanceCoverage }
154
+ : {}),
155
+ ...(optional.fleetAttribution
156
+ ? { fleet_attribution: optional.fleetAttribution }
157
+ : {}),
158
+ generated_at: nowIso(),
159
+ };
160
+ }
76
161
  /** Implements run stats for the public runtime surface of this module. */
77
162
  export async function runStats(global, options = {}) {
78
163
  const pmRoot = resolvePmRoot(process.cwd(), global.path);
@@ -80,6 +165,7 @@ export async function runStats(global, options = {}) {
80
165
  throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
81
166
  }
82
167
  const settings = await readSettings(pmRoot);
168
+ const recordedObservations = await recordStatsObservations(global, options);
83
169
  const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
84
170
  const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
85
171
  // Field utilization needs the heavy collections (notes/learnings/files/docs/
@@ -116,36 +202,33 @@ export async function runStats(global, options = {}) {
116
202
  const metadataCoverage = options.metadataCoverage
117
203
  ? computeMetadataCoverage(items, classifier)
118
204
  : undefined;
119
- const breakdowns = {};
120
- if (options.byAssignee) {
121
- breakdowns.assignee = groupItemsByDimension(items, "assignee", classifier);
122
- }
123
- if (options.byTag) {
124
- breakdowns.tag = groupItemsByDimension(items, "tag", classifier, {
125
- tagPrefix: options.tagPrefix,
126
- });
127
- }
128
- if (options.byPriority) {
129
- breakdowns.priority = groupItemsByDimension(items, "priority", classifier);
130
- }
131
- const hasBreakdowns = Object.keys(breakdowns).length > 0;
205
+ const breakdowns = requestedBreakdowns(items, options, classifier);
132
206
  const fieldUtilization = options.fieldUtilization
133
207
  ? computeContentFieldUtilization(items)
134
208
  : undefined;
135
- return {
136
- totals: {
137
- items: items.length,
138
- history_streams: streams.length,
139
- history_entries: historyEntries,
140
- },
141
- by_type: byType,
142
- by_status: byStatus,
143
- ...(metadataCoverage ? { metadata_coverage: metadataCoverage } : {}),
144
- ...(hasBreakdowns ? { breakdowns } : {}),
145
- ...(storage ? { storage } : {}),
146
- ...(fieldUtilization ? { field_utilization: fieldUtilization } : {}),
147
- generated_at: nowIso(),
148
- };
209
+ const improvementLedger = options.measurements === true || recordedObservations.length > 0
210
+ ? await readImprovementLedger({
211
+ pmRoot,
212
+ metric: options.metric,
213
+ itemId: options.measurementItem,
214
+ limit: options.measurementLimit,
215
+ })
216
+ : undefined;
217
+ const { provenanceCoverage, fleetAttribution } = await requestedHistoryAnalytics(pmRoot, items, settings, statusRegistry.terminal_statuses, options);
218
+ return assembleStatsResult({
219
+ items: items.length,
220
+ history_streams: streams.length,
221
+ history_entries: historyEntries,
222
+ }, byType, byStatus, {
223
+ metadataCoverage,
224
+ breakdowns,
225
+ storage,
226
+ fieldUtilization,
227
+ improvementLedger,
228
+ recordedObservations,
229
+ provenanceCoverage,
230
+ fleetAttribution,
231
+ });
149
232
  }
150
233
  //# sourceMappingURL=stats.js.map
151
- //# debugId=e8916552-697e-565c-91e1-4829ecb584af
234
+ //# debugId=a4de53c1-bb22-591f-a064-142c642a2443
@@ -4,7 +4,7 @@
4
4
  * Implements the pm test command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !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]="7d05534b-3cf7-506c-8626-4bd4a42cbc80")}catch(e){}}();
7
+ !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]="2c0646bf-44d7-521f-aea1-b0683d88a8fa")}catch(e){}}();
8
8
  import { spawn } from "node:child_process";
9
9
  import { cp, mkdir, mkdtemp, open, readdir, rm } from "node:fs/promises";
10
10
  import { tmpdir } from "node:os";
@@ -28,6 +28,7 @@ import { LINKED_TEST_PM_CONTEXT_MODE_VALUES as PM_CONTEXT_MODE_VALUES, LINKED_TE
28
28
  import { parseOnlyIndexValue, resolveLinkedTestRunSelection, } from "../../core/test/run-selectors.js";
29
29
  import { SCOPE_VALUES } from "../../types/index.js";
30
30
  import { diffTestRunMeasurements, parseTestRunMeasurements, queryTestRunMeasurementsBelow, } from "./measurements.js";
31
+ import { SOURCE_CONTEXT_ACCESS_ENV } from "../environment/source-context.js";
31
32
  const TEST_OUTPUT_MAX_BUFFER_BYTES = 20 * 1024 * 1024;
32
33
  const DEFAULT_LINKED_TEST_TIMEOUT_FORCE_KILL_DELAY_MS = 3000;
33
34
  const DEFAULT_LINKED_TEST_HEARTBEAT_INTERVAL_MS = 10000;
@@ -1398,6 +1399,7 @@ function buildLinkedTestExecutionEnv(params) {
1398
1399
  process.env.PM_SOURCE_WORKSPACE_ROOT ?? process.cwd();
1399
1400
  executionEnv.PM_SOURCE_PM_PATH =
1400
1401
  params.executionContext.source_project_pm_path;
1402
+ executionEnv[SOURCE_CONTEXT_ACCESS_ENV] = "read_only";
1401
1403
  return executionEnv;
1402
1404
  }
1403
1405
  function buildLinkedTestPassedResult(linkedTest, executionContext, execution) {
@@ -1872,4 +1874,4 @@ export const _testOnlyTestCommand = {
1872
1874
  summarizeRunResultStatuses,
1873
1875
  };
1874
1876
  //# sourceMappingURL=execution.js.map
1875
- //# debugId=7d05534b-3cf7-506c-8626-4bd4a42cbc80
1877
+ //# debugId=2c0646bf-44d7-521f-aea1-b0683d88a8fa
@@ -4,13 +4,14 @@
4
4
  * Implements the pm linked test parsers command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !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]="f592ba2b-fc8d-5930-a2a5-d02835c457a3")}catch(e){}}();
7
+ !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]="74163bed-a473-543e-ae6a-0d5091b3b3a9")}catch(e){}}();
8
8
  import { parseOptionalNumber } from "../../core/item/parse.js";
9
9
  import { EXIT_CODE } from "../../core/shared/constants.js";
10
10
  import { PmCliError } from "../../core/shared/errors.js";
11
11
  import { splitCommaList } from "../../core/shared/split-comma-list.js";
12
12
  import { STRUCTURED_LINKED_TEST_KEYS } from "./entry.js";
13
13
  import { SCOPE_VALUES } from "../../types/index.js";
14
+ import { SOURCE_CONTEXT_ACCESS_ENV, SOURCE_CONTEXT_WRITE_OVERRIDE_ENV, } from "../environment/source-context.js";
14
15
  /**
15
16
  * Shared linked-test field parsers used by the `create` and `test` commands.
16
17
  *
@@ -30,6 +31,8 @@ export const LINKED_TEST_PROTECTED_ENV_KEYS = new Set([
30
31
  "PM_GLOBAL_PATH",
31
32
  "PM_SOURCE_PM_PATH",
32
33
  "PM_SOURCE_WORKSPACE_ROOT",
34
+ SOURCE_CONTEXT_ACCESS_ENV,
35
+ SOURCE_CONTEXT_WRITE_OVERRIDE_ENV,
33
36
  "FORCE_COLOR",
34
37
  ]);
35
38
  /** Public contract for linked test env name pattern, shared by SDK and presentation-layer consumers. */
@@ -521,4 +524,4 @@ export const _testOnlyLinkedTestParsers = {
521
524
  parseLinkedTestJsonEntry,
522
525
  };
523
526
  //# sourceMappingURL=parsers.js.map
524
- //# debugId=f592ba2b-fc8d-5930-a2a5-d02835c457a3
527
+ //# debugId=74163bed-a473-543e-ae6a-0d5091b3b3a9
@@ -29,6 +29,7 @@ declare class WorkspaceLockHeartbeat {
29
29
  /** Internal heartbeat constructor exposed only for deterministic lock tests. */
30
30
  export declare const _testOnlyWorkspaceSnapshot: {
31
31
  WorkspaceLockHeartbeat: typeof WorkspaceLockHeartbeat;
32
+ withSnapshotFilesystemGuard: typeof withSnapshotFilesystemGuard;
32
33
  };
33
34
  /** Immutable manifest stored with every content-addressed snapshot object. */
34
35
  export interface WorkspaceSnapshotManifest {
@@ -117,6 +118,14 @@ export interface RestoreWorkspaceSnapshotResult {
117
118
  /** Stable audit operation appended after staging and before activation. */
118
119
  audit_operation: "workspace_snapshot_restore";
119
120
  }
121
+ /**
122
+ * Runs a bounded snapshot filesystem stage, converting host environment faults
123
+ * into declared refusals.
124
+ *
125
+ * The operation label is a fixed identifier, never a path: snapshot failures
126
+ * are reported without disclosing workspace topology.
127
+ */
128
+ declare function withSnapshotFilesystemGuard<T>(operation: string, run: () => Promise<T>): Promise<T>;
120
129
  /**
121
130
  * Publish a prepared immutable object, treating a concurrent winning publish
122
131
  * as successful deduplication.
@@ -5,7 +5,7 @@
5
5
  * state while excluding clone-local caches, locks, and recovery journals.
6
6
  */
7
7
 
8
- !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]="3e32be7c-f8a4-547d-8c19-456b4567982e")}catch(e){}}();
8
+ !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]="cdde844a-a14a-501d-a45b-18cbd1dd9008")}catch(e){}}();
9
9
  import crypto from "node:crypto";
10
10
  import { cp, lstat, mkdir, readFile, readdir, rename, rm, writeFile, } from "node:fs/promises";
11
11
  import path from "node:path";
@@ -15,6 +15,7 @@ import { acquireLock } from "../core/lock/lock.js";
15
15
  import { getLockPath } from "../core/store/paths.js";
16
16
  import { EXIT_CODE } from "../core/shared/constants.js";
17
17
  import { PmCliError } from "../core/shared/errors.js";
18
+ import { withHostEnvironmentBoundary } from "./environment/host-environment-errors.js";
18
19
  /** Current content-addressed workspace snapshot manifest schema identifier. */
19
20
  export const SNAPSHOT_SCHEMA = "https://schema.unbrained.dev/pm/workspace-snapshot/v1";
20
21
  const SNAPSHOT_RUNTIME_PATH = path.join("runtime", "workspace-snapshots");
@@ -27,6 +28,11 @@ const EXCLUDED_ROOT_NAMES = new Set([
27
28
  ]);
28
29
  const SNAPSHOT_TARGET_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
29
30
  const WORKSPACE_WRITER_LOCK_ID = "sdk-workspace-transaction";
31
+ const SNAPSHOT_HOST_FAULT_CODES = {
32
+ capacity: { code: "workspace_snapshot_storage_exhausted" },
33
+ permission: { code: "workspace_snapshot_permission_denied" },
34
+ resource: { code: "workspace_snapshot_resource_exhausted" },
35
+ };
30
36
  const DEFAULT_ATOMIC_OPERATIONS = {
31
37
  renameEntry: rename,
32
38
  removeEntry: async (target) => {
@@ -97,6 +103,7 @@ class WorkspaceLockHeartbeat {
97
103
  /** Internal heartbeat constructor exposed only for deterministic lock tests. */
98
104
  export const _testOnlyWorkspaceSnapshot = {
99
105
  WorkspaceLockHeartbeat,
106
+ withSnapshotFilesystemGuard,
100
107
  };
101
108
  async function collectAuthoritativeFiles(root, relative = "") {
102
109
  const entries = await readdir(path.join(root, relative), {
@@ -193,6 +200,27 @@ function isErrno(error, code) {
193
200
  "code" in error &&
194
201
  error.code === code);
195
202
  }
203
+ /**
204
+ * Runs a bounded snapshot filesystem stage, converting host environment faults
205
+ * into declared refusals.
206
+ *
207
+ * The operation label is a fixed identifier, never a path: snapshot failures
208
+ * are reported without disclosing workspace topology.
209
+ */
210
+ async function withSnapshotFilesystemGuard(operation, run) {
211
+ return withHostEnvironmentBoundary(`workspace_snapshot_${operation}`, run, {
212
+ codes: {
213
+ capacity: SNAPSHOT_HOST_FAULT_CODES.capacity.code,
214
+ permission: SNAPSHOT_HOST_FAULT_CODES.permission.code,
215
+ resource: SNAPSHOT_HOST_FAULT_CODES.resource.code,
216
+ },
217
+ why: "Snapshot create and restore copy authoritative tracker state before activation, so the stage needs capacity and write access up front.",
218
+ nextSteps: [
219
+ "Reclaim rebuildable tracker cache space with pm gc, then retry.",
220
+ ],
221
+ suggestedRetry: "pm gc --json",
222
+ });
223
+ }
196
224
  function snapshotStore(pmRoot) {
197
225
  return path.join(pmRoot, SNAPSHOT_RUNTIME_PATH);
198
226
  }
@@ -302,14 +330,16 @@ export async function createWorkspaceSnapshot(pmRoot, options = {}) {
302
330
  }
303
331
  if (!deduplicated) {
304
332
  const temporaryRoot = path.join(store, "objects", `.create-${process.pid}-${crypto.randomUUID()}`);
305
- await mkdir(path.join(temporaryRoot, "files"), { recursive: true });
306
- for (const [index, file] of manifest.files.entries()) {
307
- const target = path.join(temporaryRoot, "files", file);
308
- await mkdir(path.dirname(target), { recursive: true });
309
- await writeFile(target, contents[index]);
310
- }
311
- await writeFile(path.join(temporaryRoot, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
312
- await mkdir(path.dirname(objectRoot), { recursive: true });
333
+ await withSnapshotFilesystemGuard("create_object", async () => {
334
+ await mkdir(path.join(temporaryRoot, "files"), { recursive: true });
335
+ for (const [index, file] of manifest.files.entries()) {
336
+ const target = path.join(temporaryRoot, "files", file);
337
+ await mkdir(path.dirname(target), { recursive: true });
338
+ await writeFile(target, contents[index]);
339
+ }
340
+ await writeFile(path.join(temporaryRoot, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
341
+ await mkdir(path.dirname(objectRoot), { recursive: true });
342
+ });
313
343
  deduplicated = await publishWorkspaceSnapshotObject(temporaryRoot, objectRoot);
314
344
  }
315
345
  if (options.name !== undefined) {
@@ -467,12 +497,14 @@ export async function restoreWorkspaceSnapshotWithRecovery(pmRoot, target, optio
467
497
  const base = path.basename(pmRoot);
468
498
  staging = path.join(parent, `.${base}.restore-${crypto.randomUUID()}`);
469
499
  const backup = path.join(parent, `.${base}.backup-${crypto.randomUUID()}`);
470
- await mkdir(staging, { recursive: true });
471
- await cp(source, staging, { recursive: true, force: false });
472
- await mkdir(path.join(staging, "runtime"), { recursive: true });
473
- await cp(store, path.join(staging, SNAPSHOT_RUNTIME_PATH), {
474
- recursive: true,
475
- force: false,
500
+ await withSnapshotFilesystemGuard("restore_stage", async () => {
501
+ await mkdir(staging, { recursive: true });
502
+ await cp(source, staging, { recursive: true, force: false });
503
+ await mkdir(path.join(staging, "runtime"), { recursive: true });
504
+ await cp(store, path.join(staging, SNAPSHOT_RUNTIME_PATH), {
505
+ recursive: true,
506
+ force: false,
507
+ });
476
508
  });
477
509
  const audit = await appendWorkspaceAuditEvent({
478
510
  pmRoot: staging,
@@ -564,4 +596,4 @@ export async function deleteWorkspaceSnapshot(pmRoot, target) {
564
596
  return { deleted: "object", target };
565
597
  }
566
598
  //# sourceMappingURL=workspace-snapshot.js.map
567
- //# debugId=3e32be7c-f8a4-547d-8c19-456b4567982e
599
+ //# debugId=cdde844a-a14a-501d-a45b-18cbd1dd9008
@@ -25,6 +25,8 @@ historical projections, and versioned legacy identity are tracked by
25
25
  [pm-ffz0a9](../.agents/pm/issues/pm-ffz0a9.toon),
26
26
  [pm-v8gfi7](../.agents/pm/issues/pm-v8gfi7.toon), and
27
27
  [pm-3yxwv5](../.agents/pm/issues/pm-3yxwv5.toon).
28
+ [pm-alhqbz](../.agents/pm/issues/pm-alhqbz.toon) governs the nested source-write
29
+ boundary retained by linked-test package lifecycles.
28
30
 
29
31
  `pm` treats project management as context management. These primitives keep
30
32
  mutation provenance, source-workspace identity, extension flags, and bounded
@@ -198,10 +200,15 @@ Every extension command, parser, preflight hook, renderer, and service receives:
198
200
  Linked tests set `PM_SOURCE_WORKSPACE_ROOT` before replacing `PM_PATH` and
199
201
  `PM_GLOBAL_PATH`, and protect `PM_SOURCE_PM_PATH` as the read-only source
200
202
  tracker coordinate for source-repository lifecycle commands such as
201
- `pm merge install`. Extensions can therefore inspect source VCS metadata
202
- without writing real tracker state. Package code should prefer `pm_root_rel`
203
- in output and persisted evidence to avoid leaking host-specific absolute
204
- paths.
203
+ `pm merge install`. They also set `PM_SOURCE_CONTEXT_ACCESS=read_only`.
204
+ Validation may continue to inspect the source coordinates, but a nested
205
+ non-dry-run `pm merge install` selects the current package repository instead
206
+ of the source workspace. A maintainer can deliberately opt into the legacy
207
+ source mutation for one invocation with `PM_ALLOW_SOURCE_CONTEXT_WRITES=1`;
208
+ `resolveMergeInstallContext()` reports whether that override selects the source.
209
+ Both variables are protected from linked-test `env_set` and `env_clear`
210
+ metadata. Package code should prefer `pm_root_rel` in output and persisted
211
+ evidence to avoid leaking host-specific absolute paths.
205
212
 
206
213
  ## Strict extension flag descriptors
207
214
 
@@ -0,0 +1,103 @@
1
+ # Improvement Ledger and History Analytics
2
+
3
+ Tracker references: [pm-chahyq](../.agents/pm/features/pm-chahyq.toon), [pm-1wiugq](../.agents/pm/issues/pm-1wiugq.toon), [pm-gw6uyq](../.agents/pm/features/pm-gw6uyq.toon)
4
+
5
+ `pm` treats project management as context management. Improvement observations and fleet analytics therefore remain attached to authoritative project context: observations are audited workspace state, while provenance and outcome analytics are bounded projections of immutable history.
6
+
7
+ ## Agent Quick Context
8
+
9
+ - Record quantitative evidence with `pm stats --analytics '{...}'`; do not hand-edit `.agents/pm/improvement-ledger.json`.
10
+ - Request ledger and history projections through the single typed `--analytics` JSON object. This keeps the agent-facing command contract bounded while the SDK and MCP retain their fully typed fields.
11
+ - Ledger reads are newest-first and bounded; trends still use every matching retained observation.
12
+ - Use an explicit metric direction. `lower` is the default; `target` requires a threshold and measures convergence toward it.
13
+ - Use `provenanceCoverage` to find declared-but-inert or undeclared provenance dimensions.
14
+ - Use `fleetAttribution` for observational comparisons only. It must never authorize, assign, route, rank, or evaluate an individual agent.
15
+ - Bound history work with `since`, `eventLimit`, and `minimumSample`.
16
+
17
+ ## Audited improvement observations
18
+
19
+ ```bash
20
+ pm stats \
21
+ --analytics "$(jq -cn --arg revision "$(git rev-parse HEAD)" '{
22
+ observe: ["quality.coverage.lines=100,unit=percent,threshold=100"],
23
+ direction: "higher",
24
+ measurementSource: "pnpm coverage",
25
+ measurementItem: "pm-example",
26
+ measurementRevision: $revision,
27
+ measurements: true
28
+ }')" \
29
+ --json
30
+ ```
31
+
32
+ Each observation records a content identity, metric, finite value, direction, timestamp, revision provenance, author, and optional unit, threshold, source, and owning item. When no revision is supplied, `pm` uses Git HEAD when available and otherwise records an explicit `unversioned` marker.
33
+
34
+ Retries are idempotent by revision, metric, source, and owner. Reusing that key with different numeric or metric-contract data fails with a conflict instead of silently rewriting history. A metric keeps one direction, unit, and—when target-directed—target threshold throughout its series.
35
+
36
+ ```bash
37
+ pm stats --analytics '{"measurements":true,"metric":"quality.coverage.lines","measurementLimit":20}'
38
+ ```
39
+
40
+ The `improvement_ledger` result contains:
41
+
42
+ - `observations`: a bounded newest-first page;
43
+ - `total` and `truncated`: the complete match count and omission state;
44
+ - `trends`: baseline, latest, delta, improvement state, and sample count per metric;
45
+ - `source: audited_workspace_singleton`: the state provenance receipt.
46
+
47
+ The singleton is written through the workspace lock and `_workspace` hash-chained audit stream. Compaction may reduce historical patches, but it does not remove current ledger state.
48
+
49
+ ## Provenance coverage
50
+
51
+ ```bash
52
+ pm stats --analytics '{"provenanceCoverage":true,"since":"-30d","eventLimit":10000,"minimumSample":5}' --json
53
+ ```
54
+
55
+ This projection compares configured harness signal descriptors with observed immutable history. It reports descriptor coverage, observed/unavailable/legacy-missing values, inert dimensions with sufficient explicit samples, undeclared dimensions, and stable warning codes. The live corpus is the positive control; deliberately missing descriptors and unavailable values provide negative controls in tests.
56
+
57
+ ## Fleet attribution
58
+
59
+ ```bash
60
+ pm stats --analytics '{"fleetAttribution":true,"since":"-30d","eventLimit":10000,"minimumSample":5}' --json
61
+ ```
62
+
63
+ Fleet attribution groups bounded events by harness, model, and author source. Each group reports state and annotation events, terminal transitions, reopens, and issues linked with `discovered_from` to closed work. Rates remain `null` until the close denominator reaches `minimum_sample`.
64
+
65
+ The result always includes `policy: observational_only_not_for_authorization_or_routing`. Missing dimensions are `unavailable`, small denominators are `insufficient`, and a `window` receipt states the lower bound, consumed event count, truncation, and continuation cursor.
66
+
67
+ ## SDK
68
+
69
+ ```ts
70
+ import {
71
+ readImprovementLedger,
72
+ recordImprovementObservation,
73
+ runFleetAttributionAnalytics,
74
+ runProvenanceCoverageAnalytics,
75
+ } from "@unbrained/pm-cli/sdk";
76
+
77
+ await recordImprovementObservation(
78
+ {
79
+ metric: "quality.coverage.lines",
80
+ value: 100,
81
+ direction: "higher",
82
+ unit: "percent",
83
+ revision: process.env.GIT_COMMIT,
84
+ },
85
+ { path: ".agents/pm" },
86
+ );
87
+
88
+ const ledger = await readImprovementLedger({
89
+ pmRoot: ".agents/pm",
90
+ metric: "quality.coverage.lines",
91
+ limit: 20,
92
+ });
93
+ ```
94
+
95
+ `PmClient.stats()` exposes the combined typed contract. The lower-level functions support packages that need one primitive without constructing a command transport. MCP uses the same action schema; its direction field is named `improvementDirection` to avoid collision with dependency-graph traversal direction. The CLI deliberately folds the new analytics fields into one validated `--analytics` object so its help and contract surface stay within the enforced agent-token budget.
96
+
97
+ ## Safety and interpretation
98
+
99
+ - Treat observations as evidence, not a universal objective function. Record the producing gate and owning item so context survives.
100
+ - Compare like-for-like metric contracts; a changed unit, direction, or target should use a new metric name.
101
+ - Do not infer causality from attribution. The projection describes recorded history and relationship links only.
102
+ - A truncated window is incomplete evidence. Resume or increase the bound before publishing conclusions.
103
+ - Keep credentials, private payloads, host identifiers, and customer data out of metric names, sources, messages, and item links.
@@ -213,19 +213,19 @@ metadata as a continuation negative control. The checked-in report is
213
213
 
214
214
  | Intent | 2-item tokens / budget | 2,243-item tokens / budget | Current-scale rows | Degradation |
215
215
  | ------ | ---------------------- | -------------------------- | ------------------ | ----------- |
216
- | `context:orient` | 707 / 2,400 | 1,000 / 2,400 | 3 | bounded sections |
216
+ | `context:orient` | 735 / 2,400 | 1,028 / 2,400 | 3 | bounded sections |
217
217
  | `get:inspect` | 401 / 3,200 | 415 / 3,200 | item envelope | standard item |
218
- | `list:triage` | 393 / 3,200 | 3,181 / 3,200 | 70 | budget-derived rows |
218
+ | `list:triage` | 443 / 3,200 | 3,189 / 3,200 | 69 | budget-derived rows |
219
219
  | `next:execute` | 395 / 1,200 | 1,171 / 1,200 | 14 | budget-derived rows |
220
- | `search:discover` | 301 / 1,800 | 1,763 / 1,800 | 28 | budget-derived rows |
220
+ | `search:discover` | 350 / 1,800 | 1,761 / 1,800 | 27 | budget-derived rows |
221
221
 
222
222
  Whole-answer cursor cost is measured against the unprojected single call for
223
223
  the identical ordered row set:
224
224
 
225
225
  | Family | Rows | Pages | Optimized bytes/row | Optimized walk | Repeated-metadata control | Unbounded call | Walk / unbounded |
226
226
  | ------ | ---- | ----- | ------------------- | -------------- | ------------------------- | -------------- | ---------------- |
227
- | `list:triage` | 1,998 | 30 | 172.29 | 344,234 B | 379,875 B | 1,558,741 B | 0.2208 |
228
- | `search:discover` | 1,998 | 64 | 201.24 | 402,075 B | 454,869 B | 1,681,847 B | 0.2391 |
227
+ | `list:triage` | 1,998 | 31 | 172.93 | 345,519 B | 387,489 B | 1,558,741 B | 0.2217 |
228
+ | `search:discover` | 1,998 | 66 | 202.50 | 404,591 B | 470,046 B | 1,681,847 B | 0.2406 |
229
229
 
230
230
  These are corpus-generated figures, not live tracker payloads. The generated
231
231
  corpus contains 2,243 items; the `status:all` query intentionally excludes 245
package/docs/README.md CHANGED
@@ -53,8 +53,10 @@ pm guide release --json
53
53
  - [Agent Provenance ADR Amendment](AGENT_PROVENANCE_ADR.md) - extensible model, effort, role, and host provenance with privacy and compatibility boundaries.
54
54
  - [SDK Agent Session and Episode Context](SDK_AGENT_SESSION_CONTEXT.md) - inherited role/topic context, cross-process episode identity, and deterministic history grouping.
55
55
  - [SDK Context Coordination](SDK_CONTEXT_COORDINATION.md) - durable mutation events, bounded duplicate governance, and scale-safe package primitives.
56
+ - [Improvement Ledger and History Analytics](IMPROVEMENT_ANALYTICS.md) - audited quantitative observations, live provenance coverage, and bounded observational fleet outcomes.
56
57
  - [SDK Evidence Traceability and Integrity](SDK_EVIDENCE_TRACEABILITY.md) - reverse source-to-item lookup, atomic evidence replacement, no-op history, linked-test collision classification, and telemetry drain receipts.
57
58
  - [SDK Context Integrity Primitives](SDK_CONTEXT_INTEGRITY_PRIMITIVES.md) - batch duplicate discovery, structured errors, Plan evidence/lifecycle, sparse settings, tombstones, linked-test output, relocation diagnostics, and scoped output services.
59
+ - [SDK Context Integrity](SDK_CONTEXT_INTEGRITY.md) - truthful `get` projections, bounded annotation mutations, shared author coordinates, health provider boundaries, and changeset-aware replication gates.
58
60
  - [Reproducible Workspaces and Snapshots](REPRODUCIBLE_WORKSPACES.md) - deterministic SDK recipes and content-addressed authoritative tracker restore points.
59
61
  - [Portable Corpus Shapes](CORPUS_SHAPES.md) - versioned SDK populations for realistic benchmarks, evaluations, and package tests.
60
62
  - [Agent UX Contracts](AGENT_UX_CONTRACTS.md) - ordering-cycle advisories, graph count units, collision safety, compact context, ownership wording, and recovery behavior.
@@ -1,6 +1,6 @@
1
1
  # Universal Read Output Contracts
2
2
 
3
- Tracker references: [pm-hb7ug8](../.agents/pm/features/pm-hb7ug8.toon) and [pm-cxr0jb](../.agents/pm/features/pm-cxr0jb.toon).
3
+ Tracker references: [pm-hb7ug8](../.agents/pm/features/pm-hb7ug8.toon), [pm-cxr0jb](../.agents/pm/features/pm-cxr0jb.toon), [pm-hid9g1](../.agents/pm/features/pm-hid9g1.toon), and [pm-sb0tns](../.agents/pm/issues/pm-sb0tns.toon).
4
4
 
5
5
  ## Agent Quick Context
6
6
 
@@ -15,6 +15,58 @@ Every built-in read surface uses four output dimensions: what to include, how mu
15
15
 
16
16
  The contract covers `list`, `context`, `search`, `get`, `next`, `health`, `deps`, `graph`, `history`, `activity`, `validate`, `events`, `contracts`, `comments`, `notes`, `files`, `docs`, `stats`, and `aggregate`, including list aliases and `ctx`.
17
17
 
18
+ Row shaping follows each envelope's `row_contract.row_keys`, including
19
+ dot-delimited nested arrays and object maps such as `graph.nodes`. Include,
20
+ amount, repeat suppression, and cost compaction therefore operate on the same
21
+ machine-declared rows; they do not rely on command-specific top-level keys.
22
+
23
+ ## Cross-Call Context Sessions
24
+
25
+ `--output-session <json>` / `outputSession` composes the four per-call
26
+ dimensions across a request group. The caller supplies versioned state and
27
+ passes the returned `read_session.next_state` to the next read:
28
+
29
+ ```json
30
+ {
31
+ "version": 1,
32
+ "id": "orientation",
33
+ "token_budget": 4000,
34
+ "spent_tokens": 0,
35
+ "seen_item_ids": []
36
+ }
37
+ ```
38
+
39
+ The session ceiling and an explicit `--output-budget` both bind; the smaller
40
+ remaining allowance wins. Rows for item facts already present in the caller's
41
+ context become `{ "id": "pm-a1b2", "context_ref":
42
+ "session:orientation:pm-a1b2" }` instead of repeating prose. References retain
43
+ stable item identity and can be restored with `pm get <item-id> --brief` when
44
+ the prior context is unavailable. The receipt reports estimated and charged
45
+ tokens separately when the remaining group allowance is smaller than the
46
+ minimum control envelope, plus the accumulated spend, remaining capacity,
47
+ newly served items, and suppressed repeats.
48
+
49
+ Session state is deliberately caller-carried: CLI processes, SDK clients, MCP
50
+ hosts, and packages share the same deterministic primitive without a hidden
51
+ daemon or mutable cache. Validation rejects unknown fields, invalid identifiers,
52
+ unsupported schema versions, unsafe integers, and spend beyond the declared
53
+ ceiling before a read executes.
54
+
55
+ The mandatory orientation calibration runs `context`, `list`, `search`, `get`,
56
+ and `next` against both a two-item tracker and a 2,243-item tracker. Its
57
+ cross-call ceilings are strict: complete serialized bytes and cumulative spend
58
+ may only shrink, while repeat suppression may only hold or improve. The gate
59
+ also fixes the expected unique-fact shape:
60
+
61
+ | Tracker tier | Group spend / budget | Seen items | Suppressed repeats | Delivered bytes |
62
+ | ------------ | -------------------- | ---------- | ------------------ | --------------- |
63
+ | 2 items | 3,663 / 20,000 | 2 | 3 | 14,646 |
64
+ | 2,243 items | 9,999 / 20,000 | 106 | 7 | 39,985 |
65
+
66
+ These are deterministic synthetic-corpus measurements from
67
+ `scripts/release/context-intent-calibration.json`; they contain no hosted
68
+ tracker content.
69
+
18
70
  ## Precedence and Compatibility
19
71
 
20
72
  Resolution is deterministic: canonical controls win over command-local compatibility options, which win over intent defaults, which win over command defaults. Existing options such as `--fields`, `--limit`, `--token-budget`, `--format`, `--brief`, and `--full` remain accepted. Contract output marks them as hidden compatibility aliases and supplies a migration hint; traversal, cursor, side-effect, and streaming controls instead receive an explicit behavior-preservation hint because a static output control cannot replace their semantics. Callers that omit the canonical controls receive the byte-identical established result.
@@ -41,6 +93,13 @@ const result = await pm.list({
41
93
  outputInclude: "id,title,status",
42
94
  outputLimit: 10,
43
95
  outputBudget: 800,
96
+ outputSession: {
97
+ version: 1,
98
+ id: "orientation",
99
+ token_budget: 4000,
100
+ spent_tokens: 0,
101
+ seen_item_ids: [],
102
+ },
44
103
  });
45
104
  ```
46
105